Docker 入门到实战:掌握镜像、容器、数据卷与 Compose 编排 我第一次在生产服务器上部署 Java 应用的时候光环境就折腾了三天。JDK 版本不对、系统底层库版本太老、中间件配置路径记错——每换一台机器整套流程就得重来一遍。后来我把整套运行环境连同应用一起塞进 Docker 容器里整个部署时间从三天变成三分钟。这就是 Docker 最核心的价值把环境、依赖、配置和应用一起打包搬到哪台机器上都能原样跑起来。这篇文章我把 Doker 里最该先搞明白的知识点串一遍包括镜像、容器、仓库这几个基础概念的内在关系Windows 和 Linux 两条安装路线的完整走法日常用得最多的生命周期命令还有数据卷、网络、编排和排错。不打算把文档里所有参数都堆给你只讲你在真实项目里真正会用到的那部分。刚接触 Docker 的同学可以直接照着做已经用了一段时间但总在报错里打转的人也能从排查链路里找到对症的思路。1. 从打包一切说起镜像、容器、仓库到底是什么关系1.1 镜像把应用和环境一起烤进一张光盘很多人第一次接触 Docker 都被镜像这个词绕晕了其实它一点都不神秘。你可以把一个镜像理解成一张已经做好的系统安装光盘光盘里不仅有操作系统的基础文件还预装了某个软件以及它跑起来需要的全部依赖。MySQL 8.0 镜像里就包含了精简版 Linux 系统、MySQL 的可执行文件、默认配置和运行脚本你用这个镜像启动容器得到的就是一台开箱即用的 MySQL 服务器。这个设计解决了一个很现实的问题以前部署应用要先在服务器上装系统、装运行时、装数据库、调依赖版本任何一步出错都会影响后面所有步骤。用 Docker 之后这些全部固化在镜像里镜像是什么样容器启动后就是什么样。同一个镜像在开发机、测试机、生产机上跑出来的结果是完全一致的不存在在我电脑上是好的这种说法。镜像还有一个特性是分层。Dockerfile 里每一条指令都会生成一个只读层拉取镜像时如果本地已经有相同层就会直接复用不会重复下载。这也是为什么你拉了一个 Ubuntu 镜像之后再拉基于它的 MySQL 镜像速度会明显快很多——公共的底层系统层被缓存了。1.2 容器同一张光盘可以复制出多个运行实例镜像本身是静止的运行时需要实例化这个运行中的实例就是容器。容器和虚拟机最直观的区别在于容器不虚拟化硬件它直接共享宿主机的操作系统内核只是通过命名空间和各种隔离机制让里面的进程以为自己在独立系统里。用光盘来类比虚拟机像是把整台电脑搬进虚拟环境里再装系统每个虚拟机都有一份完整独立的操作系统占用资源大容器更像是在宿主机系统上开出来的一个个隔离环境每个容器只打包了应用和它需要的文件系统启动就是一个进程的事秒级起步。这个区别带来了两个直接好处。第一是资源效率高一台 8G 内存的云服务器跑三四个虚拟机可能就捉襟见肘了但跑几十个容器没有问题热搜里提到的一台 N100 小主机跑 20 个 Docker就是这种玩法。第二是可移植性强容器不依赖底层操作系统的具体版本只要宿主机能跑 Docker就能跑任意镜像的容器。1.3 仓库与 Dockerfile从哪里拿镜像怎么自制镜像镜像不是凭空产生的。公开镜像都存放在镜像仓库里最常用的就是官方维护的 Docker Hub里面有 MySQL、Redis、Nginx、Hadoop 等海量现成镜像。你可以把仓库当成应用商店docker pull就是下载安装包。公司内部一般会自建私有仓库存放自制镜像避免把内部代码和配置暴露到公共平台上。自制镜像靠 Dockerfile它是一份描述如何构建镜像的清单。一个最简单的 Dockerfile 大概长这样FROM ubuntu:22.04 RUN apt-get update apt-get install -y nginx COPY index.html /usr/share/nginx/html/ EXPOSE 80 CMD [nginx, -g, daemon off;]这段内容的意思是以 Ubuntu 22.04 为基础安装 Nginx把本地文件复制进镜像暴露 80 端口启动时执行 Nginx。你可能会问这和写一份部署文档有什么区别区别在于这份清单是可执行的docker build能严格按步骤生成一个标准镜像任何人拿到这个镜像得到的环境都一样不存在漏装依赖或版本漂移的问题。2. 环境搭建的两条路线Windows 桌面版与 Linux 命令行安装2.1 Windows 路线Docker Desktop 的安装与虚拟化检查在 Windows 上装 Docker多数人走的是 Docker Desktop 这条路。但装之前必须先确认两件事CPU 虚拟化有没有开启以及系统是否支持 WSL2。virtualization support not detected 这个报错基本是 Docker Desktop 启动失败的标配九成原因是 BIOS 里的虚拟化开关没打开。开机时进 BIOS 设置不同品牌按键不同常见 F2、Del、F10找到 Intel Virtualization Technology 或者 AMD SVM Mode改成 Enabled 保存退出。进系统后打开任务管理器在性能标签页可以看到虚拟化是否显示已启用。确认虚拟化开启后还需要确保 WSL2 可用。Docker Desktop 在 Windows 上依赖 WSL2 作为运行后端没装的话桌面版会提示你安装。打开管理员权限的 PowerShell 执行wsl --install装完后重启系统。之后再下载 Docker Desktop 安装包一路默认安装。装完打开如果右下角的小鲸鱼图标稳定不动说明运行正常。这里踩过的坑是有些机器明明 BIOS 里开了虚拟化Docker Desktop 还是提示找不到虚拟化。这种情况多半是 Windows 自带的 Hyper-V 组件没启用。去控制面板 - 程序 - 启用或关闭 Windows 功能勾选Hyper-V和适用于 Linux 的 Windows 子系统两项重启后再启动 Docker Desktop 就正常了。2.2 Linux 路线CentOS 与 Ubuntu 的安装操作Linux 服务器上安装 Docker 没有图形界面步骤更直白。Ubuntu 系和 CentOS 系略有区别但大思路一样先卸载可能存在的旧版本尤其 CentOS 自带的 docker再配置官方软件源然后安装并启动服务。Ubuntu 22.04 的完整流程sudo apt-get update sudo apt-get 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 \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.ioCentOS 7 因为系统较老官方源里默认没有 Docker CE需要先设置软件源路径再执行 yum 安装。装完之后执行sudo systemctl start docker和sudo systemctl enable docker把服务启动并设为开机自启。验证是否装好跑一下docker version能同时看到 Client 和 Server 两段信息就说明服务正常。2.3 镜像加速源配置拉取慢的缓解办法镜像下载慢是搜索里出现频率特别高的问题。默认 Docker Hub 在国外国内网络访问自然慢还容易超时中断。解决办法是配置镜像加速源相当于给docker pull设置一个代理仓库它会缓存公共镜像再分发给你。配置方法很简单。修改/etc/docker/daemon.jsonWindows 版 Docker Desktop 在设置里找到 Docker Engine 选项编辑同样文件填入{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn ] }改完执行sudo systemctl daemon-reload和sudo systemctl restart docker让配置生效。加速源虽然不能保证所有镜像都飞快但能把最常用的操作系统镜像和中间件镜像的拉取时间从几十分钟压到几分钟。需要提醒的是加速源提供的镜像版本可能与官方实时同步存在轻微延迟生产环境建议锁定具体版本号不要用 latest 标签。3. 每天都要用的容器生命周期操作3.1 镜像拉取、查看与清理镜像拉取是 Docker 使用频次最高的操作。docker pull支持指定标签拉取特定版本比如docker pull mysql:8.0而不是docker pull mysql后者拿到的 latest 标签未来会随官方更新变动可能导致环境不一致。查看本地已有镜像docker images这个命令会列出仓库名、标签、镜像 ID、创建时间和大小。镜像用多了占用空间很大清理无用镜像用docker image prune加-a参数会连同没有被容器使用的镜像一起清理谨慎使用因为重建镜像可能要重新拉取基础层。3.2 docker run 的核心参数逐个拆解docker run是一条把镜像变成容器的命令参数较多但核心就这么几个我拆开解释docker run -d \ --name my-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0-d表示后台运行不加的话终端会被前台进程占住CtrlC 容器就停了。--name给容器起名字后续操作直接引名字不用记一长串容器 ID。-p 3306:3306是端口映射宿主机 3306 端口映射到容器内 3306 端口这样外部程序访问宿主机 IP 的 3306 就能连上容器里的数据库冒号左边是宿主机端口右边是容器内端口。-e设置环境变量MySQL 镜像要求必须设置 root 密码这个参数就是干这个的。-v是数据卷挂载把宿主机的/data/mysql目录挂载到容器内的数据目录容器删了数据还在宿主机上。一个很容易忽略但很实用的参数是--restart它控制容器在异常退出或者宿主机重启时的行为。线上服务建议加上--restartalways意思是只要 Docker 服务起来了就自动启动这个容器不用手动去拉。排查问题时用--restartno避免容器反复重启方便看日志。3.3 进入容器、看日志、查状态排障基本功容器里出了问题第一步是看日志而不是进容器重建。查看最近日志docker logs my-mysql指定日志条数和时间范围docker logs --tail 50 my-mysql docker logs --since 10m my-mysql--tail 50只看末尾 50 行--since 10m看最近 10 分钟的日志。想看实时输出加-f。要真正进入容器操作用docker execdocker exec -it my-mysql bash-it是交互式终端的组合参数容器里没有 bash 时可以换sh。进入容器后你就是容器里的 root 用户可以查看目录结构、运行命令、检查配置所有操作在容器重启后会失效。查看容器状态docker ps docker ps -a第一条可能让你吓一跳跑着的容器好像没几个。别急docker ps默认只显示运行中的容器-a才会显示包括已退出在内的所有容器。安装 MySQL 之后发现容器反复启动失败先docker ps -a看状态那一列再docker logs看具体报错这是排查的基本顺序。docker inspect能查看容器的完整配置和状态信息包括挂载路径、网络配置、环境变量等排障时很常用。你会看到 JSON 格式的输出内容冗长配合docker inspect my-mysql | grep -i ip之类的小技巧能快速定位容器 IP 之类的信息。3.4 容器端口、名字、重启策略的规划经验规划容器时有一个铁律容器创建后端口映射和挂载路径基本改不了。想改就得删容器重建而之前产生的数据如果没有挂载出来就全没了。所以第一次docker run之前一定要把端口、目录、环境变量都想清楚。给容器命名也讲究规范。我习惯按环境-项目-角色的方式命名比如prod-order-mysql、dev-user-redis看起来直观也好维护。容器多了以后docker ps的输出本来就长名字起得好一眼就能分辨哪个是哪个。重启策略和端口规划容易被人忽略的是防火墙问题。容器端口映射出去了宿主机防火墙没放行外部还是连不上。很多人排查半天容器本身没问题最后发现是防火墙把端口挡了。遇到外部访问不上、容器里一切正常的情况先查宿主机防火墙规则。4. 数据卷、端口与网络容器内外沟通的三件套4.1 数据卷和绑定挂载数据为什么必须挂出来容器是临时性的删掉之后写入的文件系统内容全没。但数据库的数据、应用的日志、上传的文件都是不能丢的所以 Docker 提供了数据卷机制。数据卷分两种。一种叫 bind mount就是前面提到的-v /data/mysql:/var/lib/mysql直接把宿主机目录挂进容器路径你指定备份和迁移都直观。另一种叫 named volume写法是不带宿主机路径只给卷起名字docker run -d --name my-redis -v redis-data:/data redisDocker 会在自己的数据目录下创建一个卷你用docker volume ls能看到它数据实际存储位置由 Docker 管理。我的经验是单机场景用 bind mount 更直观生产环境或需要跨主机迁移的场景用 named volume 更方便因为不用关心具体物理路径交给 Docker 编排体系处理。速度上 bind mount 在 Docker Desktop尤其是文件较多较复杂时会有明显的 IO 损耗因为 Windows 和 macOS 的文件系统与 Linux 容器文件系统之间有一层转换。大数据量读写的应用如果追求性能优先考虑 named volume 或者直接容器内存储再定期备份出来。4.2 三个网络模式bridge、host、自定义网络Docker 默认的网络模式是 bridge容器会通过一个虚拟网桥 NAT 出去访问外部网络宿主机以外的设备不能直接访问容器 IP。这相当于每个容器住在内网里对外得靠端口映射开门。另一种是 host 模式容器直接使用宿主机的网络栈没有独立 IP也没有 NAT 转换。好处是网络性能几乎零损耗坏处是端口直接占用宿主机端口每个容器都需要独立端口。生产环境如果用 host 模式要注意端口冲突和安全性。还有一种 none 模式容器只有回环接口没有外部网络适合对隔离要求极高的任务。不过实际操作中用得最少更多是用自定义网络来精细控制容器间通信。自定义网络是实际项目中最应该掌握的。创建一个自定义网络docker network create app-network然后把容器加入这个网络docker run -d --name user-api --network app-network user-service:1.0 docker run -d --name user-mysql --network app-network mysql:8.0同一个自定义网络里的容器可以通过容器名直接互相访问比如 user-api 里连接数据库主机名直接填user-mysql就行不需要查 IP。这个特性省掉了维护容器 IP 的麻烦容器重启 IP 变了也不会影响服务间通信。4.3 容器间通信推荐的自定义网络方案单机多容器部署我推荐一律走自定义网络原因有三个。第一是服务发现友好。默认 bridge 网络里容器之间只能通过 IP 通信而容器重建 IP 会变这就得动代码或配置。自定义网络里直接用容器名当主机名稳定可靠。第二是隔离性可控。自定义网络可以做更细粒度的访问控制比如把数据库放在一个内部网络不暴露端口到宿主机只有应用容器能通过自定义网络访问它。这是安全上很实用的做法。第三是方便编排。如果你后面用 docker compose它自动创建的网络就是你容器间通信的桥梁配置方式是一致的。提前熟悉自定义网络的理念迁移到 compose 是无缝的。试过这个方案之后我基本没用过默认 bridge 网络的默认模式来跑多服务应用。讲个实际场景应用容器和数据库容器都在 app-network 里应用连接数据库的地址写app-mysql:3306安全上数据库不用映射宿主端口外部除了应用谁也连不上。5. 用 docker compose 串起一个真实项目MySQL 8.0 Redis 主从5.1 为什么单条 docker run 不够用服务一多每条docker run手动敲的问题就暴露了。一是命令太长容易写错参数二是服务之间的网络关联要手动维护三是环境迁移时你得保证每台机器上都敲一模一样的命令这本身就是一个巨大的坑。docker compose 就是来解决这个问题的。它通过一个 YAML 文件声明所有容器的镜像、端口、挂载、网络和依赖关系一条docker compose up -d全部搞定。我把 docker compose 理解成容器项目的施工图纸所有服务怎么搭、怎么连都在图纸里写清楚。对开发环境、测试环境、小规模生产环境来说docker compose 足够好用。网上各种教程里最常见的场景——安装 MySQL、搭建 Redis 主从、部署微服务项目——基本都能用 compose 一套文件完成这也是这个命令搜索量大的原因。5.2 docker-compose.yml 的写法与参数含义拿部署 MySQL 8.0 加 Redis 主从举例目录结构大概这样redis-master/ redis.conf redis-slave/ redis.conf docker-compose.ymldocker-compose.yml 文件内容services: mysql: image: mysql:8.0 container_name: app-mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql networks: - app-net redis-master: image: redis:7 container_name: redis-master restart: always ports: - 6379:6379 command: [redis-server, --appendonly, yes] volumes: - redis-master-data:/data networks: - app-net redis-slave: image: redis:7 container_name: redis-slave restart: always ports: - 6380:6379 command: [redis-server, --slaveof, redis-master, 6379] volumes: - redis-slave-data:/data depends_on: - redis-master networks: - app-net volumes: mysql-data: redis-master-data: redis-slave-data: networks: app-net:逐个解释关键配置。services是顶层的关键字下面每个一级项是一个服务容器。image指定镜像和版本别省略版本号。container_name给容器固定名字这个容器名会被写入 Docker 的 DNS 解析服务之间用这个名字互相访问。restart: always让容器伴随 Docker 自动拉起这比在docker run里加--restart更清晰。ports是端口映射格式和docker run的-p一致。environment对应-e。volumes里的mysql-data:/var/lib/mysql是 named volume 的写法映射关系是卷名:容器内路径。networks把当前服务放进app-net网络。最后在文件底部声明卷和网络的名称Docker 会创建并管理它们。Redis 主从的关键在第 20 行处的command参数。它覆盖镜像默认的启动命令直接用redis-server --slaveof redis-master 6379让从节点指向主节点。因为主从两个容器都部署在 app-net 自定义网络里redis-master这个名字可以直接被解析成主节点的容器 IP。depends_on只是控制启动顺序确保从节点在主节点起来之后再启动减少首次同步的偶发连接失败。5.3 启动、验证与后续维护命令在 docker-compose.yml 所在目录执行docker compose up -d-d后台运行。Docker 会先拉取缺失镜像创建网络、卷和容器然后启动所有服务。检查状态docker compose ps看到所有服务状态为 Up 就基本成功。验证 MySQL 连接docker compose exec mysql mysql -uroot -proot123 -e SELECT 1如果提示找不到 mysql 客户端直接进容器验证docker compose exec mysql bash验证 Redis 主从关系进主节点写一个键docker compose exec redis-master redis-cli set foo bar再进从节点查看docker compose exec redis-slave redis-cli get foo如果同步正常从节点能读到这个键的值说明主从链路通了。日常维护命令里改动配置后需要重新创建容器才能生效执行docker compose down docker compose up -ddown会停止并删除容器但不会删除声明过的数据卷所以数据不会丢。这是 compose 比手动 docker run 更高效的核心原因配置即代码环境可重建且数据可延续。6. 高频报错排查实录从启动失败到容器网络不通6.1 Docker Desktop 启动失败与 virtualization 报错排查链路Docker Desktop 在 Windows 上启动失败最容易碰到的就是前面提到过的 virtualization support wasnt detected 报错。直接的排查链路是这样第一步检查 BIOS 虚拟化开关。重启进 BIOS把 Intel VT-x 或 AMD SVM 开启保存退出。这是最容易被漏掉的步骤很多新电脑默认并不开启。第二步确认 Windows 功能里有没有启用 Hyper-V 和 WSL 子系统。控制面板 - 程序 - 启用或关闭 Windows 功能勾选这两项重启。注意这一步在 Windows 11 家庭版上可能看不到 Hyper-V 选项但 WSL2 方式本身不依赖 Hyper-V所以你更需要关注 WSL 功能是否正常。第三步在 PowerShell 执行wsl --status确认默认版本是 2 而不是 1。如果默认是 1执行wsl --set-default-version 2。第四步检查 Docker Desktop 设置里的 Use the WSL 2 based engine 是否勾选。勾选后它才能借助 WSL2 内核运行 Linux 容器。按这个顺序排查基本能把 Docker Desktop 启动失败的 90% 原因覆盖掉。如果都不行卸载重装 Docker Desktop并且卸载时选择清理 WSL 数据再重装一次十有八九能解决。6.2 permission denied while trying to connect to the Docker APIpermission denied while trying to connect to the Docker API at unix:///var/run/docker.sock 这个报错在 Linux 上特别常见。原因是当前用户没有访问 Docker 服务端 socket 的权限。Docker 的客户端和服务端通过一个 Unix socket 通信这个 socket 默认只允许 root 用户访问。你不在 root 下执行docker ps就会报这个错。很多人第一反应是用 sudo但每次都 sudo 很麻烦。正规做法是把当前用户加入 docker 用户组sudo usermod -aG docker $USER改完需要重新登录或执行newgrp docker让组权限生效。之后就能直接执行docker ps了不用 sudo。需要注意一个安全细节加入 docker 组的用户等价于拥有 root 权限。因为 docker 组用户可以随意挂载宿主机目录也就意味着能读取宿主机文件系统。所以在服务器上给用户授予 docker 组权限时要想清楚不要给不信任的账号随便加。6.3 服务启动失败与旧版本残留Linux 上docker命令找不到或者systemctl start docker失败很大概率是旧版本残留或者内核模块没加载。CentOS 7 上如果执行 docker 命令提示命令不存在先确认有没有装 docker-ce。CentOS 自带的 docker 是旧版本与新版命令兼容性不好。建议先彻底移除旧版本sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine再按官方源装新的 docker-ce。安装后如果systemctl start docker还是失败执行journalctl -u docker看详细日志。比较常见的原因是系统内核版本太老Docker 需要的内核特性不支持。CentOS 7 的内核版本可能不够新升级内核或换一台较新的系统是治本方案。还有一类问题是/etc/docker/daemon.json配置写错比如 JSON 语法错误、镜像加速源地址拼错。Docker 服务启动时会加载这个文件语法错误会导致整个服务起不来。排查方式是查看服务状态systemctl status docker如果是 daemon.json 导致的日志里会有明确的语法错误提示把文件修正重启即可。6.4 容器网络不通的快速定位容器网络不通是个宽泛的问题要分场景排查。最常见的是外部访问不了容器内服务其次是容器之间互相访问不了。外部访问不了容器内服务先确认端口映射是否生效。docker ps的 PORTS 列会显示宿主机端口到容器端口的映射如果显示0.0.0.0:3306-3306/tcp说明映射成功。没看到映射就要检查创建容器时有没有写-p参数。端口没映射容器外部就完全访问不到。映射没问题但访问还是不通就在宿主机上用curl 127.0.0.1:3306测试能通说明容器没问题问题在宿主机防火墙或者云厂商安全组。防火墙放行对应端口安全组添加入方向规则。容器之间访问不了先确认两个容器是否在同一网络里。docker inspect 容器名看 Networks 部分如果不在同一个网络就用docker network connect app-net 容器名把它加进去。在同一个网络还连不上确定用的是容器名而不是 IP 去访问并且确认目标端口在容器内确实监听中。还有一个隐蔽因素容器内的服务绑定的地址。有些镜像默认绑定 127.0.0.1比如某些配置默认的 Redis只监听本机回环地址外部请求都进不来。这种情况要在容器配置里把监听地址改成 0.0.0.0再重启容器。我自己在刚学 Docker 的那段时间老觉得报错就是自己操作不对后来发现很多问题其实用docker logs加docker inspect就能定位。排查问题的时候别急着删容器重建先按日志和状态信息一步步推断多数时候比盲目重建要省时间。Docker 的知识点多而杂但如果先把镜像、容器、数据卷、网络和编排这几块搞透日常使用和问题排查基本都能覆盖到。上手阶段也不用刻意背命令装一个 MySQL、部署一个 Redis 主从、用 compose 把服务编排起来这些场景走一遍比看十遍文档都管用。