
先交代个背景这套部署流程我在不同机器上前前后后踩了四五轮坑才稳定下来从本机开发环境到服务器上线都用它在跑。如果你正准备用 Docker 和 Docker Compose 搭建一套可复现、可迁移的部署方案这篇内容应该能帮你少走不少弯路。文章里的每一条命令、每一个配置项都是实际跑过验证过的不是那种照着官方文档念一遍的“秒懂教程”而是把容易翻车的细节和背后的逻辑都捋清楚。1. 部署前先想清楚的几个问题1.1 为什么选 Docker Compose 而不是裸命令很多新手一上来就习惯docker run一串长参数把容器拉起来比如这样docker run -d \ --name nginx \ -p 80:80 \ -v /data/nginx/html:/usr/share/nginx/html \ -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ --restartalways \ nginx:1.26-alpine单容器这么写勉强能接受但一旦你开始部署带数据库、后端、前端的完整应用情况就完全失控了。三个服务需要三条docker run每条命令七八个参数服务之间有依赖关系还得控制启动顺序想改个端口要翻半天历史命令新同事接手你的环境根本不知道从哪下手。Docker Compose 的核心价值就是把这些分散的命令固化成一个声明式配置文件docker-compose.yml。你用 YAML 描述“我要哪些服务、各服务用什么镜像、端口怎么映射、依赖什么环境变量、数据和网络怎么打通”然后一条docker compose up -d全部搞定。这个文件放到 Git 仓库里整个团队任何一个人拉下来都能跑出一模一样的环境。从运维角度看Compose 还有一个隐藏优势它强制你把基础设施当代码管。配置文件能 diff、能 review、能回滚这比某个人脑子里记着的“当年我是这么启动的”靠谱太多。1.2 方案选型背后的几个考量先说说版本选择。Compose 命令目前存在两种形态旧版的docker-compose独立二进制和新版的docker composeDocker CLI 插件。新版本是官方主推的方向不仅参数更规范还跟 Docker Engine 的版本绑定省去了单独维护 Compose 版本的麻烦。我这篇内容全部基于新版docker compose语法操作如果你的机器上恰好只有旧版命令格式也兼容只要把docker compose换成docker-compose即可但建议你尽快升级。镜像源也是个关键决策点。当前 Docker Hub 在国内的拉取速度不太稳定官方镜像经常出现超时中断所以配置一个可用的镜像加速器几乎是必须做的一步。需要注意的是加速器的配置因环境网络而异没有“一个配置走天下”的方案实测下来用你所在云服务商提供的专属加速地址最稳妥后面我会给出具体的配置方法。数据持久化方案是另一个我花了不少心思琢磨的点。容器本身是无状态的容器一删写在容器层的数据就全没了。所以配置文件、数据库文件、日志文件、上传资源这些必须通过volumes或bind mounts挂载到宿主机这是所有部署方案里不可妥协的底线。2. Docker 安装实操2.1 Linux 环境安装 Docker Engine我平时主要在 Ubuntu 22.04 和 Debian 12 上操作以这两种系统为例。先卸载可能存在的旧版本避免冲突sudo apt-get remove docker docker-engine docker.io containerd runc然后安装依赖包让 apt 支持通过 HTTPS 访问软件源sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release添加 Docker 官方 GPG 密钥这一步是为了确保你安装的软件包来自官方、没有被篡改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 sudo chmod ar /etc/apt/keyrings/docker.gpg把 Docker 的 apt 仓库写入系统源列表注意 Ubuntu 和 Debian 的 codename 要对应你自己的系统版本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 /dev/null接着更新索引并安装 Docker Engine、containerd 和 Compose 插件sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin安装完成后先别急着用确认守护进程已经跑起来sudo systemctl status docker sudo docker run hello-world看到 “Hello from Docker!” 的输出就说明整套环境正常了。如果systemctl status显示服务没起来多半是内核模块没加载执行一下sudo modprobe overlay sudo modprobe br_netfilter再试。CentOS 系系统的安装套路类似只是包的名称和源配置略有差异用yum install -y yum-utils添加源后安装docker-ce docker-ce-cli containerd.io docker-compose-plugin就行。2.2 Windows 与 macOS 安装 Docker DesktopWindows 用户目前最省心的方案仍然是 Docker Desktop但这里有一个高频翻车点Docker Desktop 底层依赖虚拟化技术安装后启动经常会报 “Docker Desktop failed to start because virtualisation support wasn’t detected” 或者弹窗提示虚拟化未开启。这个问题的根源通常在三个层面第一BIOS/UEFI 里没开启 Intel VT-x 或 AMD-V需要重启进 BIOS 找 “Virtualization Technology” 选项打开。第二Windows 自带的 Hyper-V 或虚拟机监控程序没启用在 PowerShell 管理员模式下执行下面的命令然后重启Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All第三Windows 10/11 家庭版用户还需要额外启用 Windows Hypervisor Platform。新版本 Docker Desktop 默认走 WSL 2 后端所以你需要确认 WSL 2 内核已经安装在管理员 PowerShell 里执行wsl --update。macOS 用户相对省事直接在官网下载对应 Apple Silicon 或 Intel 芯片的 Docker Desktop 安装包就行。装完之后注意在 Settings 的 Resources 里给 Docker 分配合理的内存默认 2GB 跑多容器项目明显不够建议至少 4GB否则容器运行到一半会被 OOM 杀死日志里全是Killed字样。2.3 安装完成后的三个必做优化环境装好后不要急着拉镜像先把下面三件事做完能省掉后面一大半麻烦。第一件事把当前用户加入docker组这样不用每次敲sudo dockersudo usermod -aG docker $USER newgrp docker第二件事修改 Docker 数据根目录。默认情况下所有镜像、容器和卷数据都存在/var/lib/docker如果你的系统盘空间不大建议把 Docker 的数据目录挪到大容量磁盘上。在/etc/docker/daemon.json里这样配置{ data-root: /data/docker }改完执行sudo systemctl restart docker。这一步对后面跑数据库、模型文件这种大体积容器尤其重要我遇到过系统盘被镜像塞满导致容器无法启动的事故提前迁移能救命。第三件事配置镜像加速器。/etc/docker/daemon.json改成下面这样注意替换成你实际使用的加速地址{ registry-mirrors: [https://你的专属加速地址] }重启 Docker 后用docker info查看 Registry Mirrors 一栏是否生效。这里多提一句加速器只对 Docker Hub 官方镜像的拉取生效第三方仓库的镜像加速效果有限遇到拉不动的镜像先考虑换 tag 或换源。3. Docker Compose 安装与配置细节3.1 三种安装方式按需选择如果你按照上一节的方法安装了docker-compose-plugin那docker compose命令已经在系统里了直接跳到下一节验证。很多人会遇到的情况是Docker 装好了但docker compose提示 command not found这时候就是因为 Compose 插件没有单独安装。Linux 环境下最干净的安装方式是下载官方的 compose 插件二进制文件到 Docker CLI 插件目录mkdir -p ~/.docker/cli-plugins curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o ~/.docker/cli-plugins/docker-compose chmod x ~/.docker/cli-plugins/docker-compose注意目标文件名必须是docker-compose目录必须是~/.docker/cli-plugins否则 Docker CLI 识别不到。安装完执行docker compose version就能看到版本号。如果你用的是旧版独立二进制方案即docker-compose命令需要放到/usr/local/bin并加上可执行权限这里不再展开新项目不建议再走这条路。Windows 和 macOS 用户只要安装了 Docker Desktop新版都会自带 Compose 插件不需要额外处理。有时候docker compose命令执行不了大概率是 Docker Desktop 没启动或者版本太旧升级到最新版就解决了。3.2 验证安装与版本兼容性安装完成后先用版本命令确认两个核心组件都就位docker --version docker compose version我的实测环境输出是这样的Docker version 27.3.1, build ce122303 Docker Compose version v2.29.7-desktop.1这里要注意一个常见坑如果你看到的是docker-compose version 1.29.2这种旧版本号说明你调用的是独立二进制虽然大部分命令兼容但有些新语法比如docker compose config里的--format没法用。建议直接下载新版 plugin 覆盖掉旧命令或者干脆用docker compose调用新插件。验证文件语法是否合格不需要真的启动容器用config子命令做静态检查是最快的办法docker compose config --quiet如果配置文件有语法错误比如 YAML 缩进不对、端口号格式错误这里会直接报错不会等你up的时候才炸出来。这个命令应该养成习惯每次改动配置后先跑一遍。3.3 安装 Bash 自动补全还能省不少事如果你经常在终端里敲 Compose 命令强烈建议配置一下命令补全对提升效率有实打实的帮助。以 bash 为例先看有没有bash-completion包sudo apt-get install bash-completion然后把 Compose 的补全脚本放到系统补全目录sudo curl -L https://raw.githubusercontent.com/docker/compose/master/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose重新加载一下 bash 环境source ~/.bashrc之后输入docker compose再按两下 Tab 键就能看到提示。zsh 用户同理把补全脚本放到~/.zsh/completions/路径下就行。这个属于“装不装都能用但装了体验完全不同”的优化项。4. 用 Compose 部署一个真实服务完整示例4.1 项目结构怎么设计理论说太多容易虚直接拿一个实际项目来说。假设我们要部署一个用 Nginx 反向代理、后端 API、MySQL 数据库组成的小型应用。项目目录结构我建议这样组织myapp/ ├── docker-compose.yml ├── .env ├── nginx/ │ ├── Dockerfile │ └── conf.d/ │ └── default.conf ├── backend/ │ ├── Dockerfile │ └── app.py └── mysql/ └── init/ └── init.sql每个服务一个独立子目录把各自依赖的 Dockerfile 和配置放在一起。.env文件统一管理环境变量避免把密码、端口这类敏感信息硬编码到 compose 文件里。MySQL 的初始化 SQL 放到init目录这样第一次创建数据卷时会自动执行建表脚本。4.2 docker-compose.yml 核心字段拆解下面这份配置文件是我线上环境的精简版去掉了一些业务相关的敏感信息核心结构完整保留name: myapp services: mysql: image: mysql:8.0 container_name: myapp-mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: - mysql_data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 3s retries: 10 backend: build: ./backend container_name: myapp-backend restart: always depends_on: mysql: condition: service_healthy environment: DB_HOST: mysql DB_PORT: 3306 DB_USER: ${MYSQL_USER} DB_PASSWORD: ${MYSQL_PASSWORD} DB_NAME: ${MYSQL_DATABASE} ports: - 8080:8080 nginx: build: ./nginx container_name: myapp-nginx restart: always depends_on: - backend ports: - 80:80 volumes: - ./nginx/conf.d:/etc/nginx/conf.d - ./static:/usr/share/nginx/html volumes: mysql_data:逐个拆解几个容易踩坑的字段。name: myapp是新版 Compose 的项目名声明不写的话默认取当前目录名。项目名看着简单但会参与容器网络命名、卷命名后面排查问题全靠它区分建议每个项目都显式声明一个业务相关的名称。depends_on这块先划个重点不是简单写backend depends_on mysql就万事大吉了。旧版depends_on只控制启动顺序不关心服务内部是否真正就绪。MySQL 容器启动了但初始化还没完成后端就开始连库结果就是报Connection refused。新版 Compose 支持condition: service_healthy配合healthcheck探针等 MySQL 真正健康了才会启动后端。这是多容器编排里最容易被忽略的细节务必要用上。healthcheck的定义本身也有讲究。MySQL 官方镜像自带mysqladmin ping所以用它作为探针命令最直接。interval: 5s表示每隔 5 秒探测一次retries: 10表示连续失败 10 次才判定不健康。这个参数在数据库初始化时间较长时尤其重要设得太短会导致后端提前启动。环境变量DB_HOST: mysql这里的mysql不是乱写的Compose 会为项目内的服务自动创建一个默认网络服务间可以通过服务名互相访问。只要 Compose 文件里服务名是mysql网络里它的 DNS 解析名就是mysql端口就是容器内部的 3306不需要关心宿主机端口是多少。4.3 启动、验证与日常运维命令配置文件就绪后首次启动只需要一条命令docker compose up -d-d是 detached 模式让容器在后台运行日志会输出到 Docker 的日志系统。想实时看某个服务的日志docker compose logs -f backend查看所有服务的运行状态docker compose ps我实际运行时的输出大致长这样NAME IMAGE COMMAND SERVICE STATUS PORTS myapp-mysql mysql:8.0 docker-entrypoint.s… mysql healthy 3306/tcp myapp-backend myapp-backend python app.py backend running 0.0.0.0:8080-8080/tcp myapp-nginx myapp-nginx nginx -g daemon off; nginx running 0.0.0.0:80-80/tcpSTATUS列显示healthy说明健康检查已经通过了这个信息非常直观。日常更新代码后需要重新构建镜像并滚动重启docker compose up -d --build改动 compose 文件的端口或环境变量后重新应用配置docker compose up -d --force-recreate要停止并删除当前项目所有容器和网络但保留数据卷docker compose down如果想连数据卷一起清掉加上-v参数这个操作不可逆生产环境使用前要仔细确认docker compose down -v4.4 数据持久化与备份的一个真实案例我在部署另一个带 PostgreSQL 的服务时遇到过一次惨痛教训。当时图省事没挂载数据卷直接让数据库容器裸跑。某天服务器自动更新后 Docker 服务重启数据库容器虽然restart: always自动拉起来了但数据文件所在容器层发生了损坏库里的业务表全部丢失。从那以后我对持久化的态度就是任何有状态的服务数据目录必须挂载到宿主机同时加上定期备份机制。以 MySQL 为例备份用宿主机 crontab 定时执行mysqldump是最简单也最可靠的方式docker exec myapp-mysql mysqldump -uroot -p$MYSQL_ROOT_PASSWORD myapp /data/backup/myapp_$(date %F).sql恢复时把 SQL 文件重新导入cat /data/backup/myapp_2025-01-01.sql | docker exec -i myapp-mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD myapp关于数据卷有一个细节值得注意如果你用了./mysql/init:/docker-entrypoint-initdb.d这种 bind mount 方式初始化脚本只会在数据卷第一次创建时执行。之后想改初始化 SQL 重新跑必须先把旧数据卷删掉否则不会有任何效果。很多人在这一步反复 “改了配置但没生效”其实就是卷里已经有了数据被初始化机制跳过了。5. 常见问题与排查技巧实录5.1 问题速查表我把这几年部署过程中遇到的坑整理成了一张速查表覆盖了从安装到运行的绝大多数问题问题现象根本原因解决办法Docker Desktop 启动失败提示 virtualization support 未检测到BIOS 中虚拟化未开启或 Hyper-V/WSL 2 未启用BIOS 开启 VT-x/AMD-V启用 Windows 虚拟机监控程序wsl --update更新内核docker compose提示 command not foundCompose 插件未安装或不在插件目录下载插件到~/.docker/cli-plugins/docker-compose并加执行权限容器启动后一直处在 Restarting 状态进程启动即崩溃常见于数据库、后端应用docker compose logs查看日志定位具体报错不要光看状态拉取镜像超时或卡在 waiting网络问题或没有配置加速器配置registry-mirrors尝试更换 tag 或使用其他仓库docker compose up报端口已被占用宿主机端口被别的进程或容器占用ss -lntp或netstat -lntp查看占用端口的进程改映射或停掉冲突服务容器间无法互相访问服务不在同一个 Compose 网络确认都在同一个 compose 文件内检查自定义网络配置MySQL 初始化脚本不执行数据卷已有数据初始化机制跳过删除对应数据卷后重新up或手动执行 SQL容器内时间与宿主机不一致镜像默认 UTC 时区加TZAsia/Shanghai环境变量或挂载/etc/localtimedocker compose logs看不到中文内容全是乱码字符集未设置为 UTF-8设置LANGC.UTF-8环境变量数据库连接参数加characterEncodingutf8修改 compose 文件后不生效需要up -d重新应用配置执行docker compose up -d必要时加--force-recreate容器被 OOM Kill日志显示 KilledDocker Desktop 内存分配不足或应用内存泄漏给 Docker 分配更多内存优化应用内存使用这张表解决不了的场景直接执行docker compose up不带-d让容器在前台跑报错信息会非常直观地打印出来比看任何日志都好使。5.2 两条独家排查经验第一条是排查容器启动失败时最关键的命令组合。很多人一看到容器状态是Restarting就慌了直接docker rm -f删了重启结果下个循环继续崩。千万别这么干正确顺序是docker compose ps docker compose logs --tail200 服务名先看日志找到真正的报错信息。大多数情况下问题出在环境变量拼写错误、配置文件路径不对、数据库连接地址写错这几个点上。日志里会有明确提示。看完日志再决定怎么改省时省力。第二条是关于网络排障的思路。容器之间通信失败时不要急着怀疑代码先确认服务在同一个网络里docker network ls docker network inspect myapp_defaultinspect输出里能看到这个网络下挂了哪些容器、每个容器的 IP 地址还能直接进容器里用ping或curl验证连通性docker exec -it myapp-backend ping mysql如果ping不通查看 compose 文件里服务名拼写是否有误以及是否误用了自定义网络把所有服务都塞进去了。按照“先看网络、再看日志、最后查代码”的顺序排查几乎能解决九成以上的部署问题。5.3 一条关于调试的小建议可以在项目里放一个debug.sh脚本把常用的调试命令集中管理#!/bin/bash SERVICE${1:-backend} docker compose ps docker compose logs --tail100 $SERVICE docker compose exec $SERVICE sh每次出问题时先跑这个脚本日志和容器内 shell 一步到位。脚本本身很简单但在多人协作时非常实用能让所有人都按统一的流程排查问题而不是每个人凭习惯乱点。6. 后续扩展的几个方向部署环境稳定跑起来之后有几个可以继续深挖的方向每个都是独立主题这里简单提下思路。第一个方向是给 Compose 项目加 CI/CD。把docker compose up -d --build接到 Git 仓库的 Webhook 上代码 push 到主分支后自动重新构建部署。实现方式可以是一条简单的脚本配合 Jenkins 或 Gitea Actions 等工具重点是让部署行为可审计、可追溯。第二个方向是引入更细粒度的配置管理。.env 文件只是起点复杂项目可以把配置拆成多个文件用docker compose -f docker-compose.yml -f docker-compose.prod.yml的方式做环境区分。开发环境、测试环境、生产环境的差异全部收敛到 override 文件里其他配置完全复用。第三个方向是监控告警。容器能跑起来只是第一步能不能稳定跑、挂了能不能及时发现这是另一个层面的事。可以用 Prometheus 那一套去采集宿主机和容器的指标也可以简单地在 crontab 里定时执行docker compose ps检查容器状态异常时发通知。后者的成本低得多但效果直接。从一条docker run命令到一套完整的 Compose 编排中间隔着的就是这几个关键认知声明式配置、数据持久化、健康检查、依赖管理。把这些想清楚后面的路会顺很多。