首页/网络与部署/Docker Compose 实战部署指南:从 0 到上线的标准流程
网络与部署

Docker Compose 实战部署指南:从 0 到上线的标准流程

本文系统讲解 Docker Compose 实战部署的完整流程,涵盖 Dockerfile、docker-compose.yml、环境变量、Nginx 反向代理、数据卷、健康检查、HTTPS、更新回滚与常见故障排查,适合用于生产环境上线参考。

发布时间:2026年4月9日 20:42阅读量:3

很多人学 Docker Compose,学到最后只会一句:

bash
docker compose up -d

看起来像会了,实际上离"能上线、能稳定、能维护、能回滚"的标准部署,还差很远。

真正的线上部署,不是把容器拉起来就完事。你要考虑的东西远比启动命令多:

  • 项目目录怎么组织
  • 环境变量怎么管理
  • 数据卷怎么挂载
  • 数据库和缓存怎么接
  • Nginx 怎么反向代理
  • 日志怎么控制
  • 服务重启怎么办
  • 更新版本怎么回滚
  • 机器重启后能不能自恢复
  • 哪一步出问题,应该先查哪里

说白了,很多"Docker Compose 部署失败",根本不是 Compose 不行,而是部署流程压根没标准化。

一、什么是 Docker Compose,为什么部署时必须学会它

Docker 解决的是"把应用装进容器里跑"的问题。 而 Docker Compose 解决的是"多个容器如何一起协作"的问题。

一个真实项目,往往不只一个服务:

  • Web 应用
  • 数据库
  • Redis
  • Nginx
  • 定时任务
  • 后台管理服务
  • 文件存储服务

如果你靠一个一个 docker run 去启动,先不说麻烦,光是网络、环境变量、挂载、端口、重启策略,就足够把你绕晕。

Compose 的意义就在这里:你把这些服务写进一个 docker-compose.yml 文件里,统一定义镜像、端口、环境变量、数据卷、网络、启动顺序、健康检查、日志策略,然后通过一条命令完成整套服务编排。

所以,Docker 是零件,Docker Compose 才是部署工程。

二、上线前先搞清楚:什么才叫"标准部署"

很多新手对"上线"的理解太粗糙了。

真正的标准部署,至少要满足下面几点:

  1. 重启后能自动恢复 - 服务器重启后,服务不能靠你手动一个个再拉起来
  2. 配置和代码分离 - 环境变量、数据库密码、域名配置,不能硬写死在镜像里
  3. 数据持久化 - 数据库、上传文件、日志目录,不能随着容器销毁一起消失
  4. 反向代理清晰 - Nginx、应用、数据库之间的边界要明确
  5. 便于维护和升级 - 新版本上线不应该靠瞎改容器,更不能一升级就全站瘫痪
  6. 出问题可排查 - 日志、健康检查、端口、网络、挂载,都要有标准查看路径

一句话概括:真正的部署,不是把服务跑起来,而是把服务跑稳。

三、标准的 Docker Compose 部署架构长什么样

最常见、最实用的生产部署结构:

用户请求 ↓ Nginx(反向代理、静态资源、HTTPS) ↓ App(你的 Web 应用) ↓ MySQL / PostgreSQL(数据库) ↓ Redis(缓存 / 会话 / 队列)

这一套架构,是绝大多数中小型项目最常见的部署形态。

四、服务器环境准备

正式开始之前,先准备一台 Linux 服务器。常见系统都可以:

  • Ubuntu 22.04 / 24.04
  • Debian 12
  • Rocky Linux / AlmaLinux

建议最低配置:

  • CPU:2 核
  • 内存:2GB 起步
  • 磁盘:40GB 起步
  • 系统:64 位 Linux

安装 Docker

Ubuntu / Debian 示例:

bash
sudo apt update
sudo apt install -y docker.io docker-compose-plugin

启动并设置开机自启:

bash
sudo systemctl enable docker
sudo systemctl start docker

检查版本:

bash
docker --version
docker compose version

五、项目目录怎么设计,决定你以后会不会乱

很多人的部署目录像垃圾堆。配置混着代码,日志混着数据,备份目录到处乱飞。出问题时连该去哪儿看都不知道。

推荐你从一开始就按标准结构来:

/opt/myapp/ ├── docker-compose.yml ├── .env ├── app/ │ ├── Dockerfile │ ├── package.json │ ├── src/ │ └── ... ├── nginx/ │ ├── default.conf │ └── certs/ ├── data/ │ ├── mysql/ │ ├── redis/ │ └── uploads/ ├── logs/ │ ├── nginx/ │ └── app/ └── backup/

这个结构的好处非常直接:

  • app/ 放业务代码
  • nginx/ 放反代配置
  • data/ 放持久化数据
  • logs/ 放日志
  • backup/ 放备份
  • .env 放环境变量
  • docker-compose.yml 作为总控文件

部署最怕乱。目录一乱,排查就开始变成赌博。

六、先写应用镜像:Dockerfile 才是镜像的根

Compose 负责编排,镜像本身还是靠 Dockerfile 来定义。

下面给一个 Node.js Web 项目的示例 Dockerfile:

dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install --production

COPY . .

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

CMD ["npm", "start"]

这个 Dockerfile 的逻辑很清晰:

  • 使用 Node 20 Alpine 作为基础镜像
  • 设置工作目录为 /app
  • 先复制依赖描述文件
  • 安装生产依赖
  • 再复制项目代码
  • 暴露 3000 端口
  • 启动应用

为什么先复制 package*.json

因为这样可以利用 Docker 构建缓存。如果代码改了,但依赖没改,安装依赖这一层就不用重新跑,构建速度快很多。

别忘了 .dockerignore

很多人 Dockerfile 写得不错,结果构建目录一锅粥,全被带进镜像。

推荐加一个 .dockerignore:

node_modules npm-debug.log .git .gitignore Dockerfile docker-compose.yml .env logs data backup

否则你的镜像会越来越胖,构建越来越慢,纯属给自己找事。

七、编写 .env 文件:配置不要写死在 Compose 里

部署项目时,最忌讳把密码、数据库地址、密钥直接写死在配置文件里。

应该使用 .env 文件统一管理环境变量。

示例:

APP_NAME=myapp APP_PORT=3000 NODE_ENV=production DB_HOST=db DB_PORT=3306 DB_NAME=myapp DB_USER=myapp_user DB_PASSWORD=your_strong_password REDIS_HOST=redis REDIS_PORT=6379 TZ=Asia/Shanghai

这样做有几个好处:

  • 配置和代码分离
  • 不同环境可复用同一套 Compose
  • 改配置不一定要改镜像
  • 更方便迁移和维护

八、核心来了:编写 docker-compose.yml

下面给你一套典型的生产级基础模板。

yaml
version: "3.9"

services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    container_name: myapp_app
    restart: unless-stopped
    env_file:
      - .env
    environment:
      TZ: Asia/Shanghai
      PORT: 3000
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: myapp
      DB_USER: myapp_user
      DB_PASSWORD: your_strong_password
      REDIS_HOST: redis
      REDIS_PORT: 6379
    expose:
      - "3000"
    depends_on:
      - db
      - redis
    volumes:
      - ./data/uploads:/app/uploads
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
      interval: 10s
      timeout: 3s
      retries: 5
    logging:
      options:
        max-size: "10m"
        max-file: "3"

  db:
    image: mysql:8.0
    container_name: myapp_db
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      MYSQL_DATABASE: myapp
      MYSQL_USER: myapp_user
      MYSQL_PASSWORD: your_strong_password
      MYSQL_ROOT_PASSWORD: your_root_password
    command:
      [
        "--default-authentication-plugin=mysql_native_password",
        "--character-set-server=utf8mb4",
        "--collation-server=utf8mb4_unicode_ci"
      ]
    volumes:
      - ./data/mysql:/var/lib/mysql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-pyour_root_password"]
      interval: 10s
      timeout: 5s
      retries: 10
    logging:
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:7-alpine
    container_name: myapp_redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - ./data/redis:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    logging:
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:1.27-alpine
    container_name: myapp_nginx
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - ./logs/nginx:/var/log/nginx
    logging:
      options:
        max-size: "10m"
        max-file: "3"

九、这份 Compose 配置,关键点到底在哪

很多人会抄配置,但不明白为什么这么写。不懂原理,出了问题就只会发呆。

下面把关键点拆开说。

1. restart: unless-stopped

yaml
restart: unless-stopped

作用:容器异常退出后自动重启;服务器重启后也会自动恢复。 这几乎是生产环境必备。

2. env_file

yaml
env_file:
  - .env

作用:从 .env 文件统一加载配置。这样你切换环境时,改 .env 就行,不必改 Compose 主文件。

3. expose 和 ports 的区别

yaml
expose:
  - "3000"

expose 只在容器网络内部可见,不对宿主机开放。

yaml
ports:
  - "80:80"

ports 才是映射到宿主机,对外暴露。

所以:

  • App 服务通常只给 Nginx 访问,用 expose
  • Nginx 要让外部访问,用 ports

4. depends_on 不是"服务一定可用"的保证

yaml
depends_on:
  - db
  - redis

它只能保证启动顺序,不保证数据库已经 ready。

这也是为什么还要加 healthcheck,并且应用自己最好也要带重试机制。

很多项目的问题就出在这里:应用比数据库起得快,然后启动瞬间连接失败,容器开始疯狂重启。

5. volumes 负责持久化

yaml
volumes:
  - ./data/mysql:/var/lib/mysql

如果没有这个挂载,MySQL 数据会放在容器层里。容器一删,数据跟着走,你就知道什么叫"秒变事故现场"。

6. logging 用来防止日志把磁盘打爆

yaml
logging:
  options:
    max-size: "10m"
    max-file: "3"

非常关键。Docker 默认日志文件如果不限制,写嗨了能把磁盘直接撑满。磁盘一满,最先发疯的不是你,是数据库和系统服务。

十、Nginx 反向代理怎么写才像回事

nginx/default.conf 示例:

nginx
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    client_max_body_size 50m;

    location / {
        proxy_pass http://app:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

这里最核心的一句是:

nginx
proxy_pass http://app:3000;

注意不是 127.0.0.1,不是服务器公网 IP,而是 Compose 服务名 app。

在 Compose 网络里,服务之间互访靠的就是服务名。

为什么很多人会配出 502

因为他们把 Nginx 写成了:

nginx
proxy_pass http://127.0.0.1:3000;

然后 Nginx 又跑在容器里。结果这个 127.0.0.1 指向的是 Nginx 容器自己,不是 App 容器。打不通,502,理所当然。

Docker 部署里,很多报错不是玄学,是你根本没搞懂"谁的 localhost"。

十一、正式启动:从构建到运行的标准命令

进入项目根目录:

bash
cd /opt/myapp

先检查 Compose 配置是否能正确渲染:

bash
docker compose config

这一条强烈建议养成习惯。很多 YAML 缩进、变量替换、格式错误,在这里就能提前发现。

构建镜像

bash
docker compose build

如果你不需要重新构建,只拉镜像:

bash
docker compose pull

启动服务

bash
docker compose up -d

查看运行状态

bash
docker compose ps

查看日志

bash
docker compose logs -f

如果只看某个服务:

bash
docker compose logs -f app
docker compose logs -f nginx
docker compose logs -f db

十二、第一次上线后,必须做的 8 个检查

很多人把服务拉起来就关窗口了。这不叫部署完成,这叫把事故预埋好了。

上线后至少做下面这些检查:

1. 检查容器状态

bash
docker ps -a

确认没有 Exited、Restarting 之类的异常状态。

2. 检查应用健康接口

bash
curl http://127.0.0.1:3000/health

如果应用没健康接口,建议补一个。生产环境没有健康检查,等于闭着眼开车。

3. 检查 Nginx 转发

bash
curl -I http://127.0.0.1

看是否正常返回 200、301、302 等预期状态码。

4. 检查数据库连接

进入应用容器里,看程序是否能正常连库:

bash
docker exec -it myapp_app sh

必要时测试环境变量:

bash
env | grep DB_

5. 检查数据卷是否落地

bash
ls -lah ./data/mysql
ls -lah ./data/uploads
ls -lah ./data/redis

看到文件真实写进宿主机,才算持久化成功。

6. 检查端口监听

bash
ss -ltnp

确认 80、443、3306、6379 等端口状态是否符合预期。

7. 检查磁盘空间

bash
df -h
docker system df

部署当天就看这两条,远比等磁盘爆了再看体面得多。

8. 检查重启恢复能力

执行一次:

bash
docker compose restart

或者重启服务器后验证服务是否自动恢复。

如果重启完站点就没了,那你这套部署就是纸糊的。

十三、HTTPS 怎么上

如果你是正式站点,HTTPS 基本是必需项。最常见做法有两种:

方案一:宿主机申请证书,挂载进 Nginx 容器

你可以用 certbot 在宿主机申请证书,然后把证书目录挂载进 Nginx。

Compose 里已经预留了:

yaml
- ./nginx/certs:/etc/nginx/certs:ro

然后在 Nginx 配置中启用 443:

nginx
server {
    listen 443 ssl http2;
    server_name your-domain.com www.your-domain.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass http://app:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

方案二:使用专门的自动证书方案

比如:

  • Nginx Proxy Manager
  • Traefik
  • Caddy

这类工具更自动化,但对新手来说复杂度也会提升。

十四、应用更新时,正确的上线流程是什么

别再 SSH 上去一通乱改了。标准上线应该是这样的:

1. 拉取最新代码

bash
git pull origin main

2. 重新构建镜像

bash
docker compose build app

3. 重启应用服务

bash
docker compose up -d app

或者整套更新:

bash
docker compose up -d --build

4. 观察日志

bash
docker compose logs -f app

5. 验证页面、接口、数据库连接

确认没问题再离开。

十五、回滚怎么做,才不至于上线翻车后满地找牙

上线永远不是"成功 or 结束",而是"成功 or 回滚"。

如果你没有回滚方案,说明你根本没准备好上线。

最基础的回滚策略

1. 镜像版本化

别永远使用:

yaml
image: myapp:latest

建议:

yaml
image: myapp:1.0.0
image: myapp:1.0.1
image: myapp:1.0.2

一旦新版本出事,直接改回旧 tag 再启动。

2. 数据和镜像分离

镜像升级,不应该动数据库数据目录。否则你回滚代码时,数据结构已经变了,照样翻车。

3. 上线前备份数据库

MySQL 示例:

bash
docker exec myapp_db mysqldump -uroot -pyour_root_password myapp > ./backup/myapp_$(date +%F_%H-%M-%S).sql

别嫌麻烦。真正的数据事故,都是嫌麻烦的人制造出来的。

十六、Compose 部署最常见的 10 个坑

下面这些坑,几乎是高频事故区。

1. 容器启动了,但服务没监听 0.0.0.0

如果你的应用只监听:

127.0.0.1:3000

那外部和其他容器都可能访问不到。

应该监听:

0.0.0.0:3000

2. 容器里乱用 localhost

容器里的 localhost 是当前容器自己。你要访问数据库服务,应该写:

db:3306

不是:

127.0.0.1:3306

3. 没做数据卷挂载

数据库数据、上传文件、缓存持久化没挂载,删个容器直接清零。这不叫线上系统,这叫一次性用品。

4. 没有限制日志大小

日志文件无限长大,磁盘被打满,整个系统跟着抽风。

5. 端口冲突没排查

80、443、3306 常年都是高危端口。

检查命令:

bash
ss -ltnp | grep :80
ss -ltnp | grep :443

6. 环境变量名字写错

程序要的是 DATABASE_URL,你写成 DB_URL。程序要的是 REDIS_HOST,你写成 REDIS_URL。Docker 没错,Compose 没错,纯属你自己瞎配。

7. 目录权限不对

容器里的非 root 用户写不了宿主机挂载目录,就会报权限错误。

必要时检查:

bash
ls -lah ./data
docker exec -it 容器名 id

8. depends_on 当成健康检查

depends_on 不是 ready 检查。数据库容器启动了,不代表数据库已经可以接连接。

9. 直接把数据库端口暴露公网

yaml
ports:
  - "3306:3306"

不是不能用,但生产环境里最好只在必要时开放,并配好安全策略。否则等于把数据库大门半开着。

10. 只有部署,没有监控

你至少要有:

  • 日志查看方式
  • 磁盘检查方式
  • 容器状态检查方式
  • 备份方案

没有这些,迟早出事。

十七、线上故障时,应该先打哪几条命令

真出问题时,别慌,也别一通瞎改。

先按顺序打:

bash
docker compose ps
docker ps -a
docker compose logs -f
docker inspect myapp_app
ss -ltnp
df -h
docker system df

如果是 Nginx 502,就进 Nginx 容器测后端:

bash
docker exec -it myapp_nginx sh
wget -qO- http://app:3000/health

如果 Nginx 容器里都打不通后端,那就别再怪域名、浏览器、CDN 了。问题就在你的容器网络或者应用本身。

十八、给新手的一套最稳上线流程

如果你刚开始做 Docker Compose 部署,我建议你按下面这个顺序来,不要乱跳步骤。

第一步:本地先跑通 Dockerfile 先确保应用镜像能独立启动。

第二步:再接入 Compose 先让 App + DB + Redis 在 Compose 里协同正常。

第三步:再加 Nginx 别一开始就上 HTTPS、负载均衡、复杂转发,先把最基础的反代跑通。

第四步:验证健康检查和日志 别等出事后才第一次看日志。

第五步:验证重启恢复 重启服务、重启机器,都要确认是否自动恢复。

第六步:上线前备份 尤其是数据库,有备份你才配谈上线。

第七步:更新流程标准化 以后每次更新都固定:拉代码 → 构建 → 启动 → 看日志 → 验证页面 → 结束。

十九、一份更适合多数项目的上线检查清单

你每次正式上线前,都可以照着过一遍。

基础环境

  • [ ] Docker 已安装
  • [ ] Docker Compose 插件可用
  • [ ] 服务器防火墙和安全组配置正确
  • [ ] 域名解析已指向服务器

应用层

  • [ ] Dockerfile 可构建成功
  • [ ] 应用监听 0.0.0.0
  • [ ] 健康检查接口可访问
  • [ ] 环境变量齐全

Compose 层

  • [ ] docker compose config 无报错
  • [ ] 服务依赖关系明确
  • [ ] 数据卷已挂载
  • [ ] 自动重启已配置
  • [ ] 日志轮转已配置

网络层

  • [ ] Nginx 反代目标正确
  • [ ] App 只暴露给内部网络
  • [ ] 外部只开放必要端口
  • [ ] HTTPS 证书就绪

数据层

  • [ ] 数据库已初始化
  • [ ] 上传目录权限正确
  • [ ] Redis 持久化策略明确
  • [ ] 备份脚本已准备

运维层

  • [ ] 日志查看方式已确认
  • [ ] 更新流程已标准化
  • [ ] 回滚方案已准备
  • [ ] 磁盘空间足够

二十、结语:Compose 不是高级玩法,它是上线的基本功

很多人把 Docker Compose 想得太简单,以为它就是个方便启动容器的小工具。其实不是。

对于绝大多数中小项目来说,Docker Compose 本身就是最实用的一套上线编排方案。

它不花哨,但够稳。它不神秘,但足够专业。

它最大的价值不是"把服务拉起来",而是让你的部署有结构、有秩序、有可维护性。

记住一句最重要的话:

能跑起来,只是开始;能稳定上线,才算部署。

别再把 Compose 当成一条命令。你真正要学会的,是它背后的整套部署逻辑:

  • 目录怎么分
  • 配置怎么管
  • 数据怎么存
  • 服务怎么连
  • 反代怎么写
  • 出问题怎么查
  • 升级怎么回滚

这些东西学明白了,Docker Compose 才真的开始为你干活。否则你只是把一堆问题,从宿主机搬进了容器里。

问题求助

没能解决你的问题?直接问我

如果你遇到任何技术问题无法解决,可以在这里提交求助。我会尽快查看并回复你。

支持作者

如果这篇文章帮到了你,可以支持我

扫码打赏,支持我持续更新原创排障文章。

打赏二维码