上一篇 下一篇 分享链接 返回 返回顶部

Docker 生产部署实战:从镜像构建到上线回滚的完整模板

发布人:慈云数据-客服中心 发布时间:5小时前 阅读量:2

Docker 生产环境部署指南|附源码

在很多团队中,Docker 早已不只是“开发环境一致性”的工具,而是生产环境交付、扩容、回滚和运维自动化的重要基础设施。相比传统部署方式,Docker 能把应用、运行时、依赖库、环境变量、启动命令等封装成统一镜像,从而减少“我本地能跑、线上跑不了”的问题。但要把 Docker 真正用于生产环境,不能只会写一个简单的 Dockerfile,还需要考虑镜像体积、安全权限、日志、网络、数据持久化、健康检查、资源限制、自动重启、灰度发布和故障回滚等一系列工程实践。

本文将从生产环境视角,系统讲解 Docker 部署的完整流程,并提供一套可直接参考的源码示例,包括 Dockerfiledocker-compose.ymlNginx 配置、环境变量文件以及常用部署脚本。你可以根据自己的项目类型,将其中的示例改造成 Node.js、Java、Go、Python 或 PHP 项目的生产部署模板。


一、为什么生产环境要使用 Docker?

传统部署通常依赖服务器上提前安装好的运行环境。例如部署一个 Node.js 项目,需要服务器安装指定版本的 Node.js、npm、PM2、Nginx,还要配置环境变量、日志路径和系统服务。如果服务器迁移、扩容或重装,运维人员需要重新搭建一遍环境,过程繁琐且容易出错。

Docker 的核心价值在于:将应用运行所需的一切依赖打包成镜像,通过容器统一运行。生产环境使用 Docker 主要有以下优势:

  1. 环境一致性高
    开发、测试、预发、生产环境可以使用同一个镜像,减少环境差异导致的问题。

  2. 部署速度快
    构建完成镜像后,服务器只需要拉取镜像并启动容器即可,不需要重复安装复杂依赖。

  3. 回滚简单
    每次发布都可以对应一个镜像版本,例如 app:v1.0.0app:v1.0.1。如果新版本异常,直接切回旧镜像即可。

  4. 便于扩容
    容器天然适合横向扩展,可以快速启动多个实例,再通过反向代理或负载均衡分发流量。

  5. 隔离性更好
    不同应用可以运行在不同容器中,依赖、端口、文件系统相对隔离,降低互相影响的风险。

不过,Docker 并不等于自动安全、自动高可用。生产环境仍然需要遵循规范,避免把开发环境的“随手写法”直接搬到线上。


二、生产环境部署的基本架构

一个常见的 Docker 生产部署架构如下:

用户请求
   ↓
Nginx / SLB / CDN
   ↓
Docker 容器中的应用服务
   ↓
数据库 / Redis / 对象存储 / 消息队列

在单机部署场景中,通常可以使用 docker compose 管理多个容器,例如:

  • app:业务应用容器
  • nginx:反向代理容器
  • redis:缓存容器,可选
  • mysql:数据库容器,可选,但生产环境更推荐使用独立数据库服务
  • prometheus / grafana:监控组件,可选

如果业务规模较大,可以升级到 Kubernetes、Docker Swarm 或云厂商容器服务。但对于中小型项目、企业后台、官网、接口服务而言,Docker + Docker Compose + Nginx 已经足够稳定可靠。


三、项目目录结构示例

下面是一套适合生产环境的基础目录结构:

docker-production-demo/
├── app/
│   ├── package.json
│   ├── package-lock.json
│   └── server.js
├── deploy/
│   ├── nginx.conf
│   ├── deploy.sh
│   └── rollback.sh
├── .env
├── Dockerfile
├── docker-compose.yml
└── README.md

这里以一个简单的 Node.js HTTP 服务为例。即使你的项目不是 Node.js,也可以参考它的部署思想:应用只负责提供服务,Nginx 负责对外暴露入口,配置通过环境变量注入,日志输出到标准输出,由 Docker 或宿主机统一采集。


四、示例应用源码

app/server.js

const http = require('http');

const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';

const server = http.createServer((req, res) => {
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok', env }));
    return;
  }

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    message: 'Docker production demo is running',
    env,
    time: new Date().toISOString()
  }));
});

server.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

app/package.json

{
  "name": "docker-production-demo",
  "version": "1.0.0",
  "description": "Docker production deployment demo",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {}
}

这个服务提供两个接口:

  • /:返回服务状态
  • /health:用于 Docker 健康检查或负载均衡探活

生产环境一定建议提供健康检查接口。它可以帮助我们判断容器是否真正可用,而不仅仅是“进程还活着”。


五、编写生产可用的 Dockerfile

Dockerfile

FROM node:20-alpine AS base

WORKDIR /app

COPY app/package*.json ./

RUN npm ci --omit=dev

COPY app/ .

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

USER node

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget -qO- http://127.0.0.1:3000/health || exit 1

CMD ["npm", "start"]

这个 Dockerfile 有几个关键点:

1. 使用轻量基础镜像

node:20-alpine 体积较小,适合大多数 Node.js 服务。镜像越小,传输越快,攻击面也相对更小。

2. 使用 npm ci

生产环境推荐使用 npm ci,它会严格按照 package-lock.json 安装依赖,保证依赖版本可复现。

3. 只安装生产依赖

--omit=dev 可以避免安装测试、构建工具等开发依赖,减少镜像体积。

4. 不使用 root 用户运行应用

USER node 能降低容器被攻击后的风险。很多初学者会忽略这一点,但在生产环境中,这是非常重要的安全实践。

5. 添加健康检查

HEALTHCHECK 可以让 Docker 自动判断容器健康状态。当应用无法响应 /health 时,容器会被标记为 unhealthy,便于运维排查和自动化处理。


六、编写 docker-compose.yml

docker-compose.yml

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: docker-production-demo:${APP_VERSION:-latest}
    container_name: docker-production-app
    restart: unless-stopped
    env_file:
      - .env
    expose:
      - "3000"
    networks:
      - app-network
    deploy:
      resources:
        limits:
          cpus: "1.00"
          memory: 512M
        reservations:
          memory: 128M
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

  nginx:
    image: nginx:1.27-alpine
    container_name: docker-production-nginx
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - "80:80"
    volumes:
      - ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

networks:
  app-network:
    driver: bridge

这里使用了两个容器:appnginx。应用容器不直接暴露宿主机端口,只通过内部网络提供 3000 端口;Nginx 作为统一入口,对外暴露 80 端口。

需要注意的是,deploy.resources 在普通 docker compose 场景下支持情况取决于 Docker 版本。如果资源限制没有生效,也可以改用:

mem_limit: 512m
cpus: 1.0

七、Nginx 反向代理配置

deploy/nginx.conf

upstream app_backend {
    server app:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name _;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    location /health {
        proxy_pass http://app_backend/health;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

Nginx 配置中,server app:3000app 是 Docker Compose 中定义的服务名。Docker 内部 DNS 会自动解析服务名,因此不需要写容器 IP。生产环境中不要依赖容器 IP,因为容器重建后 IP 可能变化。

如果需要 HTTPS,可以在 Nginx 前面接入云厂商负载均衡、CDN,也可以使用 Certbot 或 acme.sh 自动申请证书。


八、环境变量配置

.env

NODE_ENV=production
PORT=3000
APP_VERSION=1.0.0

环境变量不要硬编码到代码中,更不要把数据库密码、密钥、Token 写进镜像。推荐做法是:

  • 普通配置放 .env
  • 敏感配置使用云厂商密钥管理服务
  • 或通过 CI/CD 平台注入环境变量
  • .env 文件不要提交到公开仓库

在生产环境中,配置和镜像应当分离。镜像只描述应用如何运行,具体连接哪个数据库、使用什么密钥、开启哪些功能开关,应由部署环境决定。


九、部署脚本

deploy/deploy.sh

#!/usr/bin/env bash
set -e

APP_VERSION=${1:-latest}

echo "Deploying version: ${APP_VERSION}"

export APP_VERSION=${APP_VERSION}

docker compose build app
docker compose up -d

docker compose ps

echo "Deployment completed."

使用方式:

chmod +x deploy/deploy.sh
./deploy/deploy.sh 1.0.0

如果你使用远程镜像仓库,例如 Docker Hub、阿里云 ACR、腾讯云 TCR 或 Harbor,部署流程通常是:

docker build -t registry.example.com/demo/app:1.0.0 .
docker push registry.example.com/demo/app:1.0.0
docker pull registry.example.com/demo/app:1.0.0
docker compose up -d

生产环境更推荐由 CI/CD 系统完成镜像构建和推送,服务器只负责拉取指定版本镜像并启动。


十、回滚脚本

deploy/rollback.sh

#!/usr/bin/env bash
set -e

ROLLBACK_VERSION=$1

if [ -z "$ROLLBACK_VERSION" ]; then
  echo "Usage: ./deploy/rollback.sh "
  exit 1
fi

echo "Rolling back to version: ${ROLLBACK_VERSION}"

export APP_VERSION=${ROLLBACK_VERSION}

docker compose up -d

docker compose ps

echo "Rollback completed."

使用方式:

chmod +x deploy/rollback.sh
./deploy/rollback.sh 0.9.0

真正可靠的回滚前提是:每次发布都必须有明确版本号。不要长期使用 latest 作为生产版本,否则你很难确认当前线上运行的到底是哪一次构建产物。


十一、生产环境关键配置建议

1. 容器必须设置重启策略

推荐使用:

restart: unless-stopped

这样当 Docker 服务重启或容器异常退出时,容器可以自动恢复。相比 alwaysunless-stopped 更适合人工停止后不希望自动启动的场景。

2. 日志必须限制大小

如果不限制 Docker 日志,容器长期运行后可能撑满磁盘。建议配置:

logging:
  driver: json-file
  options:
    max-size: "20m"
    max-file: "5"

对于更复杂的生产环境,可以将日志统一采集到 ELK、Loki、云日志服务或 OpenTelemetry 平台。

3. 数据必须持久化

如果应用使用 MySQL、PostgreSQL、Redis 等有状态服务,必须配置 volume。否则容器删除后数据也会丢失。例如:

volumes:
  mysql-data:

不过,生产环境数据库更建议使用独立云数据库或专门的数据库服务器,而不是和应用放在同一台机器的 Compose 文件里。这样更便于备份、扩容、容灾和权限管理。

4. 不要把密钥写进镜像

错误示例:

ENV DB_PASSWORD=123456

正确做法是运行时注入:

env_file:
  - .env

或者使用 CI/CD、Kubernetes Secret、Docker Secret、云密钥管理系统等方式管理敏感信息。

5. 镜像要定期更新

基础镜像可能存在安全漏洞,因此不能多年不更新。建议定期执行镜像扫描,例如使用 Trivy:

trivy image docker-production-demo:1.0.0

发现高危漏洞后,应及时升级基础镜像或依赖包。


十二、上线前检查清单

在正式上线前,建议逐项确认以下内容:

  • 应用是否提供 /health 健康检查接口
  • 容器是否配置 restart 策略
  • 日志是否配置大小限制
  • 镜像是否使用明确版本号
  • 是否避免使用 root 用户运行应用
  • .env 是否未提交到公开仓库
  • 数据库、Redis 等数据是否持久化或使用独立服务
  • Nginx 超时时间是否符合业务场景
  • 端口是否只暴露必要服务
  • 是否具备回滚方案
  • 是否配置监控和告警
  • 是否有定期备份策略

这份清单看似基础,但很多生产事故正是由这些细节遗漏引起的。例如磁盘被日志写满、容器异常退出后无人发现、误删容器导致数据丢失、使用 latest 镜像无法回滚等。


十三、常用运维命令

查看容器状态:

docker compose ps

查看应用日志:

docker compose logs -f app

查看 Nginx 日志:

docker compose logs -f nginx

重新构建并启动:

docker compose up -d --build

停止服务:

docker compose down

进入容器:

docker exec -it docker-production-app sh

查看镜像:

docker images

清理无用资源:

docker system prune

清理命令要谨慎使用,尤其是带 -a 参数时,可能删除未被容器使用的镜像。生产环境执行清理前,一定要确认不会影响回滚。


十四、CI/CD 发布流程建议

成熟的 Docker 生产部署一般不会在服务器上手动构建镜像,而是使用 CI/CD。一个推荐流程如下:

  1. 开发者提交代码到 Git 仓库
  2. CI 系统运行测试和代码检查
  3. 测试通过后构建 Docker 镜像
  4. 镜像使用 Git Tag 或 Commit SHA 作为版本号
  5. 推送镜像到私有镜像仓库
  6. CD 系统通知服务器拉取新镜像
  7. 执行 docker compose up -d
  8. 检查健康状态
  9. 如果健康检查失败,自动回滚旧版本

版本号可以使用:

app:1.0.0
app:20250101-120000
app:git-a1b2c3d

其中 git-a1b2c3d 这类版本号非常适合排查问题,因为它能直接对应到某一次代码提交。


十五、总结

Docker 让生产环境部署变得更加标准化、自动化和可回滚,但它并不是简单地“把应用放进容器”就万事大吉。真正可靠的 Docker 生产部署,需要同时关注镜像构建、运行权限、配置管理、日志治理、健康检查、资源限制、网络隔离、数据持久化、安全扫描和发布回滚。

本文提供的示例虽然是一个简化版项目,但已经覆盖了生产部署中最核心的实践:使用非 root 用户运行容器、通过 Nginx 反向代理服务、使用 .env 管理配置、配置健康检查、限制日志大小、设置自动重启策略,并提供部署与回滚脚本。对于大多数中小型项目来说,这套方案可以作为上线模板;对于更复杂的系统,也可以在此基础上演进到 Kubernetes、服务网格、集中日志、链路追踪和自动伸缩架构。

如果你正在准备将项目容器化上线,建议不要一开始就追求复杂平台,而是先把基础规范做好:镜像可复现、配置可管理、日志可追踪、服务可探活、故障可回滚。只要这些关键能力具备,Docker 就能成为生产环境中非常可靠的部署底座。

目录结构
全文