docker compose up -d 只能说明容器已经启动。生产部署还要固定镜像、保存数据、隔离端口、限制资源,并验证容器和主机重启后的恢复结果。

单机 Docker Compose 生产部署中反向代理、应用容器、持久化卷、密钥和日志的拓扑图
公网只开放反向代理的 80/443;应用端口留在回环地址或容器网络中。

Compose 适合什么样的生产服务

单台主机、服务数量不多、依赖关系清楚时,Compose 足以承担部署:

  • 应用和数据库主要运行在一台服务器;
  • 主机故障时允许人工或自动化脚本在另一台机器恢复;
  • 容量可以通过单机扩容解决;
  • 日志、监控和备份有明确去处。

已经需要多节点调度、跨可用区容错、自动扩缩容或大规模滚动更新时,应选择 Kubernetes、Nomad 或云平台的编排服务,而不是继续把所有能力塞进一个 Compose 文件。

Docker 官方生产环境说明(在新标签页打开)建议把开发环境中的源码挂载、调试端口和开发参数从生产配置中移除,并为生产环境设置独立的变量、重启策略和日志方式。

部署参数、应用配置和密钥分开保存

/opt/webapp/
├── compose.yaml
├── .env
├── app.env
└── secrets/
    └── app_secret

.env 保存 Compose 展开配置时使用的镜像、端口和目录参数;app.env 保存需要注入容器的日志级别、公开 URL 和功能参数。两者都不放密码。密码、私钥和访问令牌放进 secrets/,权限只给实际需要读取的账户。

secrets 只把宿主文件以只读方式挂进容器,不负责加密、轮换或审计。需要这些能力时,使用专用密钥管理服务。

sudo install -d -m 700 -o root -g root /opt/webapp/secrets
sudo install -m 600 -o root -g root /dev/null /opt/webapp/secrets/app_secret
sudoedit /opt/webapp/secrets/app_secret

一份单机 Web 应用的 Compose 配置

name: ${COMPOSE_PROJECT_NAME:-webapp}

services:
  app:
    image: ${APP_IMAGE:?APP_IMAGE 必须固定为版本加 digest}
    init: true
    restart: unless-stopped
    ports:
      - "127.0.0.1:${APP_HOST_PORT:-8080}:${APP_CONTAINER_PORT:-8080}"
    env_file:
      - ./app.env
    environment:
      APP_SECRET_FILE: /run/secrets/app_secret
    secrets:
      - app_secret
    volumes:
      - app_data:${APP_DATA_DIR:-/var/lib/webapp}
    networks:
      - edge
    healthcheck:
      test: ["CMD", "/usr/local/bin/healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    cpus: "1.00"
    mem_limit: 1g
    security_opt:
      - no-new-privileges:true
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"

secrets:
  app_secret:
    file: ./secrets/app_secret

volumes:
  app_data:

networks:
  edge:
    driver: bridge

镜像内的端口、数据目录和健康检查命令必须换成应用真实提供的值。如果镜像内没有 /usr/local/bin/healthcheck,健康检查会直接失败。

镜像不要只写 latest

可变 tag 可能在两次部署之间指向不同内容。生产配置应保存完整引用:

COMPOSE_PROJECT_NAME=webapp
APP_IMAGE=registry.example.com/webapp:2026.09.02@sha256:完整摘要
APP_HOST_PORT=8080
APP_CONTAINER_PORT=8080
APP_DATA_DIR=/var/lib/webapp

tag 方便阅读,digest 用来锁定实际制品。回滚配置还要保留上一版 digest,才能重新拉取同一制品。

docker compose --env-file .env config
docker compose --env-file .env pull
docker compose --env-file .env images

应用端口不要直接暴露到公网

反向代理和应用在同一主机时,把应用绑定到回环地址:

ports:
  - "127.0.0.1:8080:8080"

代理也在同一个 Compose 网络中时,可以完全不发布宿主端口:

expose:
  - "8080"

expose 不是防火墙。还要同时检查 Docker 端口映射、主机防火墙和云安全组:

docker compose ps
docker port webapp-app-1
sudo ss -lntp

重要数据必须离开容器可写层

容器删除后,它的可写层也会消失。数据库、上传文件和应用状态要放到 named volume 或明确的 bind mount。

Docker 容器重建后,可写层被替换而持久化卷和异机备份继续保存数据的示意图
容器应当可以随时重建;需要保留的数据必须独立存放。

named volume 由 Docker 管理实际路径;bind mount 的宿主路径更直观,但目录权限、UID 和 GID 需要自己维护。两者都不能代替备份。

确认数据不会随重建丢失,可以在隔离环境执行一次完整过程:

  1. 写入一条容易识别的数据;
  2. 备份数据和恢复所需配置;
  3. 执行 docker compose up -d --force-recreate
  4. 确认应用仍能读取原数据;
  5. 再把备份恢复到另一个空目录或空实例。

只看到备份文件存在,不足以证明它能够恢复。

healthcheck 不会触发 restart

healthcheck 负责告诉 Docker“应用现在是否健康”。它应该访问应用的就绪端点,或者执行镜像内确实存在的检查命令。

restart: unless-stopped 只处理容器进程退出和 Docker 守护进程重启。应用变成 unhealthy,但主进程仍在运行时,Docker 不会因为健康状态自动重启它。

持续 unhealthy 应由监控告警。应用本身则要为数据库和外部 API 设置超时、重连和有限重试,不能依靠无限重启掩盖故障。

depends_on 只负责启动顺序

应用依赖数据库时,可以等待数据库先进入健康状态:

services:
  app:
    depends_on:
      database:
        condition: service_healthy
        restart: true

Docker 官方启动顺序说明(在新标签页打开)区分了 service_startedservice_healthyservice_completed_successfully

这项配置只解决启动时机。运行过程中数据库仍可能重启或断网,应用自己的连接恢复逻辑不能省略。

设置资源上限和日志轮转

cpus: "1.00"
mem_limit: 1g
logging:
  driver: local
  options:
    max-size: "10m"
    max-file: "5"

CPU 和内存数值要根据实际负载调整,同时给操作系统、Docker、反向代理和突发流量留出余量。日志轮转只能防止本机文件无限增长;需要检索和告警时,仍要把结构化日志送到集中系统。

启动后检查配置、容器、健康端点和日志

# Compose 最终渲染出的内容
docker compose --env-file .env config --quiet

# 容器是否运行、端口是否符合预期
docker compose ps

# 应用是否可以响应
curl -fsS http://127.0.0.1:8080/healthz

# 启动阶段有没有报错
docker compose logs --tail 100 app

再查看镜像、重启策略、健康状态和挂载:

app_container_id="$(docker compose ps -q app)"
docker inspect "$app_container_id" --format 'ref={{.Config.Image}} id={{.Image}}'
docker inspect "$app_container_id" --format '{{json .State.Health}}'
docker inspect "$app_container_id" --format '{{.HostConfig.RestartPolicy.Name}}'
docker inspect "$app_container_id" --format '{{json .Mounts}}'

如果应用没有 /healthz,不要把示例路径硬抄进去。应先为应用提供一个轻量、无副作用的就绪端点,再让反向代理和监控使用它。

上线前验证容器重建、主机重启和备份恢复

在隔离环境分别验证:

  • 强制重建应用容器,确认镜像引用、挂载、secret 和健康状态不变;
  • 重启 Docker 和主机,确认服务能恢复,应用端口也没有意外暴露;
  • 从备份恢复到空卷或空目录,确认当前镜像可以读取恢复后的数据。

常见问题

Docker Compose 适合生产环境吗?

适合边界清楚的单机或小规模服务。镜像固定、数据持久化、日志轮转、备份恢复和外部监控都要成为部署的一部分。

restart 会重启 unhealthy 容器吗?

不会。restart 策略处理容器进程退出和 Docker 重启;healthcheck 只记录健康状态。

depends_on 能保证数据库一直可用吗?

不能。service_healthy 主要控制初始启动顺序。应用仍要处理运行中的断连、超时和重连。

生产镜像固定 tag 还是 digest?

用 digest 保证不可变,同时保留版本 tag 方便阅读。配置、部署记录和实际容器应指向同一个 digest。

密码放在 env_file 里安全吗?

普通 env 文件仍是明文,而且环境变量更容易出现在诊断信息中。支持 _FILE 或文件参数的应用,应从只读 secret file 获取密码,并限制宿主文件权限。

应用稳定运行后,再把 HTTPS 和域名入口接进来;三种常见选择见 Caddy、Nginx 和 Nginx Proxy Manager 怎么选