
从第一次在测试环境里跑起docker run hello-world到现在我前后折腾 Docker 少说也有五年。刚开始只是图省事不想在自己的电脑上装一堆乱七八糟的依赖后来发现这东西一旦用熟根本回不去了——环境统一、秒级启动、迁移方便团队协作也省了无数“在我机器上明明是好的”这种扯皮。今天这篇帖子我想把这么多年在 Docker 上摸爬滚打的经验整理一遍从安装到实战部署把那些文档里不会写、但实际一定会踩的坑都翻出来说一说。不管你是在 Windows 上刚接触 Docker Desktop 的新手还是已经在 Linux 服务器上跑容器的老手这篇文章都值得你花几分钟读一下。1. 为什么说 Docker 是未来我第一次跟同事安利 Docker 的时候对方问了一句“这不就是个轻量级虚拟机吗”当时我解释了半天后来发现用类比最容易讲明白。把 Docker 理解成“集装箱”就很形象。传统部署像是搬家公司把所有东西零散地堆进一辆大卡车换个车、换个司机东西的摆放方式就可能变而容器化部署是把应用和它需要的运行环境、依赖库、配置文件全部打包进一个标准集装箱里不管这箱子上船还是上车、运到哪个港口打开以后里面永远是你期待的那个样子。虚拟机是把整间屋子都搬过去容器只是把屋子里最需要的那套家具打包好——所以我们常说容器比虚拟机更轻、启动更快、资源占用更低。从实际价值来说Docker 至少解决了三件大事。第一件是环境一致性。开发环境和生产环境不一致的问题是运维和研发之间永恒的战争。你本地用 Python 3.10 跑得好好的服务器上是 3.6直接崩给你看。有了 Docker开发、测试、生产环境全用同一个镜像启动环境差异归零。第二件是部署效率。传统部署一台新机器装系统、装依赖、配环境、调参数熟练工也得一两个小时。Docker 只需要docker run几十秒搞定。而且横向扩容时多开几个容器就够了加机器跑不动的时代一去不返。第三件是资源利用率。一台 4G 内存的云主机跑三四个虚拟机基本就饱和了但跑十几个容器完全没压力。容器共享宿主机内核省掉了虚拟机的整套系统开销。那 Docker 适合谁覆盖面其实很广。后端开发用容器跑基础设施MySQL、Redis、GitLab前端也可以在容器里做构建运维用它做服务编排和隔离测试用它快速拉起一套完整的测试环境连做安全研究的朋友也会用 Docker 快速搭建 DVWA 这类靶场。这个工具链已经成了一项基础设施级别的技能。2. 不同环境下的 Docker 安装Windows、Linux 与国产化平台安装 Docker 这件事网上一搜教程一大把但我遇到的新手最常问的还是“装哪个版本”“为什么我装了启动不了”。这里我把主流平台的情况都梳理一遍。2.1 Windows 上装 Docker Desktop绕不开的虚拟化Windows 上官方推荐的是 Docker Desktop。但在安装之前你必须确认两件事CPU 是否支持虚拟化以及虚拟化功能是否已经开启。经常有人碰到这个报错Docker Desktop failed to start because virtualization support is not detected。翻译过来就是检测不到虚拟化支持。这个问题的根源通常有三个BIOS 里没开启 Intel VT-x 或 AMD-V。Windows 功能里的 Hyper-V 没有启用。使用的是 Windows 10/11 家庭版不支持 Hyper-V而是需要依赖 WSL 2。解决方法是重启进 BIOS找到类似SVM Mode或者Intel Virtualization Technology的选项开启后保存退出然后在“控制面板-程序-启用或关闭 Windows 功能”里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”最后打开 PowerShell 执行wsl --update更新 WSL 内核。装完之后新手还经常遇到一个迷之报错failed to connect to the docker api at npipe:////./pipe/dockerdesktop-linux-en。这个大概率是 Docker Desktop 版本和 WSL 内核不同步导致的。处理方式也不复杂在 PowerShell 里执行wsl --shutdown然后完全退出 Docker Desktop再重新启动如果还不行就更新 WSL 内核或者干脆卸载重装 Docker Desktop。2.2 Linux 上安装 Docker一条命令与换源那点事Linux 上的安装就清爽多了。以 Ubuntu 为例官方推荐用 apt 安装sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.ioCentOS 7 虽然已经进入 EOL 阶段但存量用户仍然不少。安装命令相对简单sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io这里要特别提醒一下 CentOS 7 用户老版本的 Docker 和新的 containerd 之间可能存在兼容性问题装完后如果发现容器启动失败第一件事就是journalctl -u docker看日志八成是版本不匹配升级内核或者把 Docker 升级到最新版本就行。2.3 国产 CPU 架构下的 Docker 安装最近几年国产化替代推进得很快龙芯、飞腾、鲲鹏这些平台的部署需求越来越多。以龙芯为例安装 Docker 涉及架构适配问题普通 x86 的二进制包是跑不了的。比较稳妥的做法是在系统仓库或者龙芯软件生态里找已经编译好的 Docker 包。比如在麒麟等国产系统上sudo yum install -y docker-ce docker-ce-cli containerd.io如果仓库里没有也可以从官方源拉取对应架构的静态包解压后将二进制放到/usr/bin下然后手动编写 systemd service 文件。这个过程稍微繁琐但关键点就两个一是确认架构uname -m二是确保内核开启了必要的 namespace 参数。国产系统上跑 Docker 的坑主要在镜像兼容性有些 x86 镜像在龙芯上没法跑必须用支持 LoongArch 的镜像源重新拉取或者自己构建。2.4 安装后第一件事配置镜像加速源装好 Docker 之后我建议你先做一件事——配置镜像加速源。不配的话从 Docker Hub 拉镜像那速度简直让人抓狂尤其是网络高峰期。在/etc/docker/daemon.json里写入{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }然后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker我个人的经验是加速源不是越全越好有时候源多了会互相轮询反而变慢。挑两三个稳定的就够。另外提醒一句加速源只能解决 Docker Hub 的镜像拉取问题如果你的公司内部有自建的 Harbor 或 Registry也记得加到daemon.json里或者使用带地址的完整拉取命令。3. 高频 Docker 命令真正有用的其实就这么多我见过有人把 Docker 命令整理成几十条的长文说实话没太大必要。日常开发运维真正高频使用的命令就那么二三十条背熟它们就够了。3.1 镜像生命周期管理镜像可以理解为容器的“模板”。常用命令# 拉取镜像 docker pull mysql:8.0 # 查看本地镜像 docker images # 删除镜像-f 强制 docker rmi mysql:8.0 # 构建镜像 docker build -t my-app:v1 .构建镜像的时候有个小细节-t后面的 tag 名尽量包含版本号别全用latest。原因很简单——如果用latest时间一长你自己都分不清这个镜像里装的是什么版本的代码。3.2 容器生命周期管理这是使用频率最高的一类命令。# 运行容器 docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql:8.0 # 查看运行中的容器 docker ps # 查看所有容器包括已停止 docker ps -a # 停止容器 docker stop mysql8 # 启动已停止的容器 docker start mysql8 # 删除容器 docker rm mysql8docker run的参数比较多我挑几个常用的解释一下-d后台运行不加的话容器挂在前台CtrlC 就停了。--name给容器起名不然后续操作全得靠容器的随机 ID非常难受。-p端口映射宿主机端口:容器端口。注意前面是宿主机端口后面是容器内部端口顺序反了是连不上的。-e设置环境变量。MySQL 的 root 密码、Redis 的 requirepass 都是通过环境变量传进去的。-v挂载数据卷后面单独讲。3.3 进入容器与日志排查容器跑起来之后怎么进去排查问题看日志# 查看日志-f 实时跟踪 docker logs -f mysql8 # 进入容器-it 交互式终端 docker exec -it mysql8 bash # 容器内执行命令 docker exec mysql8 mysql -uroot -p日志这东西一定要养成第一时间看的习惯。很多新手遇到容器起不来的问题第一反应是删了重建其实大部分报错在日志里都写得明明白白。docker logs是排查问题的第一入口。3.4 数据持久化的核心Volume 与 Bind Mount容器本身是“用完即弃”的。你今天docker run一个 MySQL往里写了一堆数据明天docker rm把它删了数据就全没了。所以必须使用数据卷Volume或者目录挂载Bind Mount把数据持久化到宿主机上。# 使用 Volume由 Docker 管理数据放在 /var/lib/docker/volumes/ 下 docker run -d --name mysql8 -v mysql-data:/var/lib/mysql mysql:8.0 # 使用 Bind Mount把宿主机目录直接挂到容器里 docker run -d --name mysql8 -v /mnt/mysql-data:/var/lib/mysql mysql:8.0两者的区别从运维角度来看Volume 更“干净”备份迁移时用docker run --volumes-from就能搞定Bind Mount 更透明你可以直接在宿主机上看到和修改数据文件。日常开发我倾向于用 Bind Mount因为用vi直接改配置文件非常方便生产环境则建议用 Volume 配合docker-compose管理更规范。4. 实战用 Docker 部署 MySQL 8.0 并跑通主从部署数据库是 Docker 最经典的使用场景之一。下面我从零开始把 MySQL 8.0 的单机部署和主从复制完整过一遍。4.1 单机 MySQL 8.0从拉取到连接一条命令就把 MySQL 8.0 跑起来是 Docker 最爽的地方docker run -d \ --name mysql8 \ --restartalways \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -v /data/mysql8/conf:/etc/mysql/conf.d \ -v /data/mysql8/data:/var/lib/mysql \ mysql:8.0注意--restartalways这个参数它让容器在 Docker 服务重启或容器意外退出时自动拉起。在生产环境部署 MySQL 这类关键服务建议务必加上。启动之后先用docker ps确认状态再docker logs -f mysql8观察初始化日志。等日志中出现ready for connections的字样就说明服务起来了。在宿主机上测试连接mysql -h127.0.0.1 -uroot -pRoot123456这里有个常见的坑如果你在容器run的时候指定了-v /data/mysql8/conf:/etc/mysql/conf.d而宿主机上的这个目录是空的MySQL 可能不会读取你后来添加的配置文件因为 Docker 会把空目录直接挂载覆盖掉镜像内原有的目录内容。解决办法是先把配置文件写在宿主机目录里再启动容器或者启动后用docker cp把配置文件拷进容器里再重启。这是新手最容易踩的坑之一挂载目录把容器内原本的文件“隐藏”了。4.2 主从复制两份配置文件的细节MySQL 主从复制在 Docker 环境下的核心思路和裸机安装是一模一样的只是配置文件的放置方式和网络通信方式变了。我通常会在宿主机上先建好配置目录再启动容器。主库配置文件/data/mysql8-master/conf/my.cnf[mysqld] server-id1 log-binmysql-bin binlog_formatROW从库配置文件/data/mysql8-slave/conf/my.cnf[mysqld] server-id2 log-binmysql-bin binlog_formatROW relay-logrelay-log注意server-id必须不同否则主从会报冲突。分别启动主库和从库docker run -d --name mysql-master \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -v /data/mysql8-master/conf:/etc/mysql/conf.d \ -v /data/mysql8-master/data:/var/lib/mysql \ mysql:8.0 docker run -d --name mysql-slave \ -p 3308:3306 \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -v /data/mysql8-slave/conf:/etc/mysql/conf.d \ -v /data/mysql8-slave/data:/var/lib/mysql \ mysql:8.0主库上创建复制账号CREATE USER repl% IDENTIFIED BY Repl123456; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES; SHOW MASTER STATUS;查看File和Position两个值它们是从库建立连接的关键信息。然后在从库上执行CHANGE MASTER TO MASTER_HOST宿主机IP, MASTER_PORT3307, MASTER_USERrepl, MASTER_PASSWORDRepl123456, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POSxxx; START SLAVE;最后查看状态SHOW SLAVE STATUS\G看到Slave_IO_Running: Yes和Slave_SQL_Running: Yes就成功了。4.3 权限与坑点汇总整个主从搭建过程中我最常遇到的问题有三个。一是Slave_IO_Running: Connecting也就是 IO 线程连不上主库。优先自查防火墙主库宿主机 3307 端口有没有对从库开放用telnet 宿主机IP 3307测试一下。另外确认MASTER_HOST填的是否是主库物理机的 IP别填成容器 IP——容器重启后 IP 会变不稳定。二是密码插件兼容性问题。MySQL 8.0 默认的认证插件是caching_sha2_password有些客户端工具连不上。解决办法是创建用户时指定CREATE USER repl% IDENTIFIED WITH mysql_native_password BY Repl123456;三是主从数据不一致的问题。主从同步不是万能的如果主库上不小心执行了DROP TABLE从库也会跟着删。别把主从当成备份方案容灾还得靠定期全量备份。5. Docker Compose生产环境部署的最佳姿势单容器用docker run没问题但一旦涉及多个服务比如微服务架构下有网关、注册中心、多个业务服务一个个手动docker run管理起来会非常痛苦。这时候就得用 Docker Compose。5.1 用 Compose 编排 Redis 主从哨兵Redis 部署中Compose 用得非常多。以 Redis 主从加哨兵为例docker-compose.yml可以这么写version: 3.8 services: redis-master: image: redis:7.0 container_name: redis-master restart: always ports: - 6379:6379 command: redis-server --requirepass Redis123456 --appendonly yes volumes: - redis-master-data:/data redis-slave: image: redis:7.0 container_name: redis-slave restart: always ports: - 6380:6379 command: redis-server --slaveof redis-master 6379 --masterauth Redis123456 --requirepass Redis123456 --appendonly yes volumes: - redis-slave-data:/data depends_on: - redis-master redis-sentinel: image: redis:7.0 container_name: redis-sentinel restart: always ports: - 26379:26379 command: redis-sentinel /etc/redis/sentinel.conf volumes: - ./sentinel.conf:/etc/redis/sentinel.conf depends_on: - redis-master - redis-slave volumes: redis-master-data: redis-slave-data:这个 Compose 文件里有一个非常容易被忽略的细节从库的--masterauth必须写否则主库开启了密码认证从库连不上去。同时--requirepass也是需要的不然从库的端口暴露在外面谁都能连。启动命令docker compose up -d看到三个容器的 STATUS 都是 Up 就说明编排成功了。需要更新配置时改完 Compose 文件后执行docker compose up -d会自动重新创建配置变化的容器。5.2 微服务打包镜像IDEA 里的一键构建微服务架构下单个服务打包成 Docker 镜像其实有两条路。一条是写 Dockerfile 后用docker build手动构建另一条是用 IDEA 的 Docker 插件直接一键打包。后者对开发者更友好。在 IDEA 里安装 Docker 插件配置好 Docker Desktop 的连接后右键项目模块选择 Build Image。不过要把镜像构建好项目里还是得有个像样的 Dockerfile。以 Spring Boot 项目为例我最常用的构建脚本是这样# 多阶段构建 FROM maven:3.8-openjdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:11-jre-slim WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]多阶段构建的精髓在于最终镜像里只保留了运行时需要的 JDK 和打好的 jar 包Maven 和源码全都被丢弃了镜像体积能小一大截。我见过有人图省事直接拿带 Maven 的镜像当运行镜像几百 MB 的镜像在服务器上拉来拉去纯属浪费。5.3 Compose 在生产环境中的进阶注意事项用 Docker Compose 部署生产环境有几个点我强烈建议注意。一是网络模式。默认 Compose 会创建一个 bridge 网络服务之间通过服务名互相访问这是很合理的设计。但生产环境建议显式声明网络避免和宿主机上的其他容器网络冲突networks: app-network: driver: bridge二是环境变量管理。不要把数据库密码硬编码在 Compose 文件里推荐使用.env文件MYSQL_ROOT_PASSWORDRoot123456 REDIS_PASSWORDRedis123456然后在 Compose 文件里引用MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}。这样 Compose 文件本身可以提交到 Git敏感信息留在.env里并加入.gitignore。三是健康检查。关键服务建议加上healthcheck让 Compose 能感知服务的真实状态healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5这样编排系统就知道 MySQL 是否真的就绪了而不是只看容器进程是否存活。6. 常见问题排查我踩过的那些坑Docker 用多了总会遇到各种稀奇古怪的问题。这里把出镜率最高的几个整理成速查表希望能帮大家少走弯路。现象可能原因解决办法Docker 服务启动失败containerd 版本不匹配、磁盘空间不足journalctl -u docker查日志清理磁盘容器启动后立刻退出入口命令执行失败、端口被占用docker logs 容器名看日志docker ps -a查退出码镜像拉取超时网络问题、镜像源失效配置加速源后重启 Docker或尝试更换源容器内无法访问外网宿主机有防火墙或代理检查 iptables、修改 Docker DNS 配置权限拒绝Permission denied当前用户不在 docker 用户组sudo usermod -aG docker $USER后重新登录端口映射不生效宿主机端口被占用netstat -tlnp6.1 Daemon 无法启动先看日志再看状态遇到 Docker Daemon 起不来的问题我的第一反应永远是执行下面三条命令systemctl status docker journalctl -u docker --no-pager -n 100 dockerd --debug日志里最常出现的几个关键词和处理思路failed to start daemon: Error initializing network controller多半是 iptables 配置有冲突清空 iptables 规则或者重启网络服务。mkdir /var/lib/docker: no space left on device磁盘满了清掉没用的镜像docker image prune -a。overlayfs: unsupported内核版本太低或者文件系统不支持考虑升级内核或者换用vfs存储驱动不推荐生产用。6.2 镜像下载慢换源和镜像瘦身除了前面说的配置加速源镜像下载慢还有两个隐藏原因。一个是镜像本身就很大。一个包含完整操作系统的镜像动辄几个 GB再快的网速也快不到哪去。解决办法是尽量用轻量基础镜像比如alpine、slim版本把镜像体积控制在几百 MB 以内。另一个是构建时没有利用缓存。Docker 构建镜像时会按层缓存如果COPY的文件频繁变动后面所有层都会失效。所以 Dockerfile 里尽量把不常变的操作安装依赖、下载依赖包写在前面把经常变化的操作COPY源码写在后面。6.3 容器内时间与宿主机不一致这种东西只有在部署定时任务相关应用时才会暴露出来。容器默认使用 UTC 时区和宿主机差了 8 小时。解决方法很暴力也很简单docker run -v /etc/localtime:/etc/localtime:ro ...或者在 Dockerfile 里RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ENV TZAsia/Shanghai6.4 权限问题别再动不动用 root 了容器默认是以 root 身份运行的这是个安全隐患。如果黑客通过容器逃逸拿到了宿主机的权限后果不堪设想。一个比较规范的做法是在 Dockerfile 里创建业务用户并切换到该用户RUN groupadd -r app useradd -r -g app app USER app在宿主机上也强烈建议把普通用户加入docker组而不是直接用 root 操作所有命令。docker run有一个--user参数可以指定容器内使用的 UID/GID比如挂载宿主机目录做读写时容器内进程需要和宿主机用户的权限匹配否则会报 Permission denied。6.5 使用 Docker 快速搭建 DVWA 靶场顺手提一个有意思的场景如果你是做安全测试学习的用 Docker 搭 DVWA 靶场是我见过最快的方案。docker run -d --name dvwa -p 8080:80 vulnerables/web-dvwa启动后浏览器访问http://localhost:8080默认账号密码admin/password点页面底部的Create / Reset Database初始化数据库就能开始练习了。这种一键起靶场的体验就是 Docker 环境隔离和快速部署能力的缩影。7. 我的几点实操心得文章最后按照惯例分享几条我这几年用 Docker 的个人经验都是要踩过坑才能总结出来的东西。第一条是关于镜像版本。尽量使用明确的版本号而不是latest。有一次我手抖拉了一个latest的 MySQL结果和项目中用的客户端版本不兼容排查了半天才发现是镜像自动升级了。生产环境尤其要用固定版本号配合镜像仓库的 tag 管理才能做到可控可回滚。第二条是数据卷备份的习惯。即便用了 Volume我也建议定期把关键容器里的数据做一次导出。比如 MySQL 可以用docker exec mysql8 sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD backup.sql把数据库逻辑备份到宿主机。容器可以被摧毁但备份一定要在宿主机或者远程存储上留一份。第三条是养成清理的习惯。docker system prune这个命令能一次性清理所有停止的容器、未使用的网络和悬空镜像。但注意别在生产环境随手执行因为无主数据卷dangling volume默认不会被清掉如果带着-v参数清除可能会连数据卷一起干掉。我一般会先执行docker system df看一下空间占用情况再决定清理策略。还有最后一个小技巧如果你经常遇到容器编排问题日志又看不出明显头绪试试把docker compose换成docker compose --verbose再跑一次。这个命令会把每个步骤的详细输出打出来很多隐藏的问题比如网络冲突、卷绑定失败都能在这里面找到线索。Docker 这条路越往里走越有意思。从最初的服务器虚拟化到现在的 Kubernetes 编排、Serverless 容器底层逻辑一脉相承。希望这篇整理能帮你少踩一些坑更快地把容器化这套东西真正用起来。