Docker容器化部署实战:从Dockerfile到生产环境

容器化解决了什么问题

“在我机器上能跑”是开发协作中最经典的甩锅台词。Docker 通过把应用及其依赖(运行时、系统库、配置)打包成一个不可变镜像,从根本上消除了环境差异。到了 2026 年,容器化早已不是可选方案,而是云原生时代的基础设施默认形态。

本文以一个 Node.js + Python 混合服务为例,完整走一遍从 Dockerfile 到生产部署的流程。

编写第一个 Dockerfile

假设有一个 Express 应用,目录结构如下:

text
app/
├── package.json
├── package-lock.json
└── src/
    └── index.js

最朴素的 Dockerfile 是这样:

dockerfile
FROM node:20

WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "src/index.js"]

它能跑,但问题很多:镜像体积大(约 1GB)、每次构建都重装依赖、把源码和 node_modules 混在一起、以 root 运行不安全。下面逐步优化。

利用缓存:先拷贝依赖文件

Docker 构建是分层的,每条指令产生一层。只要某层输入没变,缓存就会命中。源码频繁变化,而 package.json 变化较少,所以应该先拷依赖文件、安装依赖,再拷源码。

dockerfile
FROM node:20-alpine

WORKDIR /app
# 先拷依赖描述文件
COPY package*.json ./
RUN npm ci --omit=dev
# 再拷源码
COPY src ./src

EXPOSE 3000
CMD ["node", "src/index.js"]

npm cinpm install 更适合 CI/生产:它严格按照 lockfile 安装、速度更快、可复现。-alpine 基础镜像基于 musl,体积只有完整镜像的几分之一。

多阶段构建:镜像瘦身利器

很多构建工具只在构建时需要,运行时完全用不到。多阶段构建让你在一个 Dockerfile 里用多个 FROM,最后只把产物拷进最终镜像。

以 Python 应用为例,编译依赖需要 gcc,但运行时不需要:

dockerfile
# ---- 构建阶段 ----
FROM python:3.12-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# ---- 运行阶段 ----
FROM python:3.12-slim

WORKDIR /app
# 只拷贝构建阶段装好的依赖
COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]

这样最终镜像里没有 gcc、没有 pip 缓存,体积从 900MB 降到约 150MB。

安全加固

生产镜像的安全不容忽视,至少做到以下几点:

  • 使用非 root 用户运行

  • 固定基础镜像版本,避免 latest 带来不可控变化。

  • 只装必要包,减少攻击面。
  • dockerfile
    FROM node:20-alpine
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --omit=dev && npm cache clean --force
    COPY src ./src
    
    # 创建非 root 用户
    RUN addgroup -S app && adduser -S app -G app
    USER app
    
    EXPOSE 3000
    HEALTHCHECK --interval=30s --timeout=3s \
      CMD wget -qO- http://localhost:3000/health || exit 1
    CMD ["node", "src/index.js"]

    HEALTHCHECK 让 Docker 知道容器是否健康,编排器(如 K8s)可以据此重启不健康的容器。

    .dockerignore:别把垃圾打进镜像

    .gitignore 类似,.dockerignore 决定哪些文件不进入构建上下文,既加速构建又避免泄密。

    text
    node_modules
    npm-debug.log
    .git
    .gitignore
    .env
    .env.local
    Dockerfile
    docker-compose.yml
    coverage
    *.md

    尤其要注意 .env,曾经有无数团队把数据库密码打进公开镜像。

    docker-compose 编排多服务

    真实项目通常不止一个服务。用 compose 描述多容器协作:

    yaml
    services:
      web:
        build: ./web
        ports:
          - "3000:3000"
        environment:
          - DATABASE_URL=postgres://app:secret@db:5432/app
        depends_on:
          db:
            condition: service_healthy
        restart: unless-stopped
    
      api:
        build: ./api
        environment:
          - DATABASE_URL=postgres://app:secret@db:5432/app
        restart: unless-stopped
    
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_USER: app
          POSTGRES_PASSWORD: secret
          POSTGRES_DB: app
        volumes:
          - pgdata:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U app"]
          interval: 5s
          timeout: 3s
          retries: 5
    
    volumes:
      pgdata:

    depends_oncondition: service_healthy 确保数据库就绪后 web 才启动,避免了“连接被拒绝”的启动竞态。

    镜像构建与推送

    构建并打标签,然后推送到镜像仓库(如 GHCR、Docker Hub):

    bash
    # 构建多架构镜像
    docker buildx build   --platform linux/amd64,linux/arm64   -t ghcr.io/myorg/web:1.2.3   -t ghcr.io/myorg/web:latest   --push ./web

    buildx 支持一次构建多架构镜像,在 ARM 服务器普及的今天几乎是必备技能。

    集成 CI/CD

    把构建流程放进 GitHub Actions,实现提交即构建、打 tag 即发布:

    yaml
    name: build-and-push
    on:
      push:
        tags: ["v*"]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          packages: write
        steps:
          - uses: actions/checkout@v4
          - uses: docker/setup-buildx-action@v3
          - uses: docker/login-action@v3
            with:
              registry: ghcr.io
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}
          - uses: docker/build-push-action@v6
            with:
              context: ./web
              platforms: linux/amd64,linux/arm64
              push: true
              tags: |
                ghcr.io/myorg/web:${{ github.ref_name }}
                ghcr.io/myorg/web:latest
              cache-from: type=gha
              cache-to: type=gha,mode=max

    cache-from/cache-to: type=gha 利用 GitHub Actions 缓存大幅加速重复构建。

    生产部署清单

    上线前对照这份清单逐项确认:

  • [ ] 镜像使用多阶段构建,体积合理(<300MB 为佳)。

  • [ ] 以非 root 用户运行,已配置 HEALTHCHECK。

  • [ ] 敏感配置通过环境变量或 secrets 注入,不在镜像中硬编码。

  • [ ] 基础镜像已固定版本,已配置 .dockerignore

  • [ ] 日志输出到 stdout/stderr,交给日志收集器处理。

  • [ ] 配置了资源限制(CPU/内存)与重启策略。

  • [ ] 已扫描镜像漏洞(如 docker scout 或 Trivy)。
  • 小结

    容器化的核心收益是“环境一致性”与“不可变交付”。一份高质量的 Dockerfile 应当:分层缓存友好、多阶段瘦身、非 root 运行、带健康检查。配合 compose 做本地编排、CI/CD 做自动构建推送、镜像扫描做安全兜底,你就能把应用稳妥地推向生产。记住:镜像越小、越少、越固定,生产越省心

    💬 评论区 (0)

    暂无评论,快来抢沙发吧!