Docker实战指南:从安装到部署,容器化环境与运维全解析
2026/9/15 12:14:01 网站建设 项目流程

从第一次在测试环境里跑起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.io

CentOS 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" ] }

然后重启 Docker:

sudo 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_PASSWORD=123456 mysql:8.0 # 查看运行中的容器 docker ps # 查看所有容器(包括已停止) docker ps -a # 停止容器 docker stop mysql8 # 启动已停止的容器 docker start mysql8 # 删除容器 docker rm mysql8

docker run的参数比较多,我挑几个常用的解释一下:

  • -d:后台运行,不加的话容器挂在前台,Ctrl+C 就停了。
  • --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 \ --restart=always \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=Root123456 \ -v /data/mysql8/conf:/etc/mysql/conf.d \ -v /data/mysql8/data:/var/lib/mysql \ mysql:8.0

注意--restart=always这个参数,它让容器在 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-id=1 log-bin=mysql-bin binlog_format=ROW

从库配置文件/data/mysql8-slave/conf/my.cnf

[mysqld] server-id=2 log-bin=mysql-bin binlog_format=ROW relay-log=relay-log

注意server-id必须不同,否则主从会报冲突。

分别启动主库和从库:

docker run -d --name mysql-master \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=Root123456 \ -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_PASSWORD=Root123456 \ -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;

查看FilePosition两个值,它们是从库建立连接的关键信息。

然后在从库上执行:

CHANGE MASTER TO MASTER_HOST='宿主机IP', MASTER_PORT=3307, MASTER_USER='repl', MASTER_PASSWORD='Repl123456', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=xxx; START SLAVE;

最后查看状态:

SHOW SLAVE STATUS\G

看到Slave_IO_Running: YesSlave_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 --from=builder /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_PASSWORD=Root123456 REDIS_PASSWORD=Redis123456

然后在 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 -tlnp

6.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,再快的网速也快不到哪去。解决办法是尽量用轻量基础镜像,比如alpineslim版本,把镜像体积控制在几百 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 TZ=Asia/Shanghai

6.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 容器,底层逻辑一脉相承。希望这篇整理能帮你少踩一些坑,更快地把容器化这套东西真正用起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询