← 编程返回顶部

Docker 与 Docker Compose

Docker 用于将应用及其运行环境封装为容器,实现环境隔离和一致部署。

Docker Compose 用于定义和管理由多个容器组成的应用。

1. 核心概念

Dockerfile
    ↓ docker build
Image(镜像)
    ↓ docker run
Container(容器)
概念作用
Dockerfile描述镜像如何构建
Image只读镜像模板,包含应用和依赖
Container镜像的运行实例,具有独立可写层
Volume持久化数据
Network容器间通信
Registry镜像存储与分发服务
Docker Compose管理多容器应用

一个镜像可以创建多个容器。容器之间默认不共享自己的可写文件层。

容器停止不等于数据消失;但删除容器时,其可写层通常会一起删除,因此数据库需要持久化存储。

2. 构建镜像

docker build -t sorivel-backend:1.0.0 .

参数:

参数作用
-t指定镜像名称及 Tag
.Build Context,当前目录
-f Dockerfile.prod指定 Dockerfile
--build-arg KEY=value传递构建参数
--no-cache不使用已有构建缓存
--pull尝试拉取较新的基础镜像
--platform linux/amd64指定目标平台
--target prod指定多阶段构建目标

例如:

docker build \
  -f Dockerfile.prod \
  -t sorivel-backend:1.2.0 \
  --build-arg APP_VERSION=1.2.0 \
  .

Build Context

docker build . 会把当前目录作为构建上下文。

Dockerfile 的 COPY 通常只能读取构建上下文中未被忽略的文件。

例如:

COPY ./src /app/src

./src 相对于 Build Context,而不是必然相对于 Dockerfile 所在目录。

.dockerignore

非常建议配置:

.git
node_modules
.venv
__pycache__
.env
.env.*
dist
*.log

作用:

  • 减少构建上下文。
  • 加快构建。
  • 减少不必要的缓存失效。
  • 降低敏感文件意外进入镜像的风险。

.dockerignore 不等于安全密钥管理工具,仍然需要正确管理 Secrets。

3. 创建和运行容器

docker run -d \
  --name myapp \
  -p 8000:8000 \
  --env-file .env \
  myapp:1.0
参数作用
--name容器名称
-p 8000:8000宿主机端口映射到容器端口
-d后台运行
--rm容器退出后自动删除
-e设置环境变量
--env-file加载环境变量文件
-v挂载 Volume 或 Bind Mount
--network指定容器网络
--restart重启策略

端口映射

-p 8000:8000

格式:

Host Port : Container Port

如果只允许宿主机本地访问:

-p 127.0.0.1:8000:8000

比直接绑定所有宿主机网络接口更适合不需要公开访问的开发服务。

覆盖启动命令

docker run --rm myapp:1.0 python manage.py shell

这里的命令会覆盖镜像的 CMD;如果镜像配置了 ENTRYPOINT,参数组合行为需要一并考虑。

4. Dockerfile 常见指令

指令作用
FROM指定基础镜像
WORKDIR指定工作目录
COPY复制文件
ADD增强型复制,可处理部分归档和远程资源
RUN构建镜像时执行命令
CMD容器默认命令或参数
ENTRYPOINT容器默认入口程序
ENV设置环境变量
ARG构建参数
EXPOSE声明容器监听端口
USER指定执行用户
VOLUME声明挂载目录
LABEL镜像元数据
HEALTHCHECK容器健康检查
SHELL修改 Shell 形式指令使用的默认 Shell
STOPSIGNAL设置容器停止信号

Dockerfile 示例

FROM python:3.12-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD [
  "gunicorn",
  "config.asgi:application",
  "-k",
  "uvicorn_worker.UvicornWorker",
  "--bind",
  "0.0.0.0:8000"
]

需要在依赖中安装 Gunicorn、Uvicorn 和对应 Worker 包。

CMD 与 ENTRYPOINT

ENTRYPOINT ["python"]
CMD ["manage.py", "runserver"]

组合后的默认命令:

python manage.py runserver
  • ENTRYPOINT:定义主程序。
  • CMD:提供默认命令或参数。

两者都可以被运行参数覆盖,但覆盖方式不同。

一般推荐使用 JSON 数组形式(Exec Form),以便正确处理进程信号。

ARG 与 ENV

ARG APP_VERSION=dev
ENV APP_ENV=production

区别:

  • ARG:构建阶段使用,默认不作为容器环境变量保留。
  • ENV:构建阶段和容器运行阶段都可以使用,并保留在镜像配置中。

不要使用 ARG 或 ENV 保存构建密钥。

5. Docker Image Layers 与 Build Cache

Docker 镜像由多个层组成,BuildKit 可以缓存构建步骤的结果。

例如:

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

如果仅修改 Python 业务代码,且依赖文件不变,Docker 通常可以复用之前安装依赖的构建缓存。

而:

COPY . .
RUN pip install -r requirements.txt

代码变化可能使依赖安装步骤的缓存更容易失效。

缓存优化原则

先复制变化频率低的依赖描述文件,再安装依赖,最后复制业务代码。

BuildKit Cache Mount

可以为包管理器提供构建缓存:

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

这种方式允许复用 Pip 下载缓存,不等于将缓存目录永久写入最终镜像。

6. 多阶段构建(Multi-stage Build)

多阶段构建用于将构建依赖与最终运行环境分开。

典型 React + Vite:

FROM node:24-alpine AS builder

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm build

FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html
Node Builder
    ↓
pnpm build
    ↓
dist/
    ↓ COPY --from
Nginx Image

最终镜像不需要包含 Node.js 项目的全部构建依赖。

COPY --chown

COPY --chown=appuser:appuser . /app

可在复制文件时设置所有者,适合非 Root 用户运行的容器。

7. Volume 与 Bind Mount

Named Volume

Docker 管理的数据卷:

docker volume create postgres_data

挂载:

docker run \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:17

这里的路径适用于 PostgreSQL 17 官方镜像。

Volume 不会因为容器正常删除而自动删除,适合数据库持久化。

Bind Mount

把宿主机目录挂载进容器:

docker run \
  -v ./src:/app/src \
  myapp:1.0
Host ./src
    ↕
Container /app/src

适合本地开发和配置文件挂载。

区别

类型管理者典型用途
Named VolumeDockerPostgreSQL 数据
Bind Mount宿主机文件系统源码、配置文件
tmpfs内存临时敏感或高频数据

注意:生产环境中不要随意将整个源码目录 Bind Mount 到 /app,否则可能覆盖镜像构建时写入的文件。

8. Docker Network

容器可以通过 Docker 网络通信。

Docker Compose 默认会为项目创建网络,同一网络内的容器可以通过服务名访问彼此。

例如:

backend
  ↓ db:5432
PostgreSQL

backend
  ↓ redis:6379
Redis

Django 数据库配置通常使用:

DB_HOST=db
DB_PORT=5432

而不是:

DB_HOST=localhost

因为在容器中,localhost 通常指容器本身。

ports 与 expose

ports:
  - "8000:8000"

发布到宿主机。

expose:
  - "8000"

声明容器端口,但不发布到宿主机。

对于同一 Compose 网络的服务通信,通常即使不写 expose,也可以直接访问容器实际监听的端口。

9. Docker Compose

Compose 将多个服务定义在一个 YAML 文件中。

例如:

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: prod
      args:
        APP_VERSION: "1.2.0"
    image: sorivel-backend:1.2.0
    command: >
      gunicorn config.asgi:application
      -k uvicorn_worker.UvicornWorker
      --bind 0.0.0.0:8000
    env_file:
      - ./backend/.env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - backend_network
      - frontend_network

  db:
    image: postgres:17
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Password required}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - backend_network

  redis:
    image: redis:7
    networks:
      - backend_network

volumes:
  postgres_data:

networks:
  backend_network:
  frontend_network:

这里是结构示例,实际项目还需要按需配置 Nginx、静态资源、Redis 持久化和密钥管理。

重要字段

字段作用
services定义服务
image指定镜像名称
build指定构建方式
command覆盖 Dockerfile CMD
entrypoint覆盖 Dockerfile ENTRYPOINT
environment注入容器环境变量
env_file从文件注入容器环境变量
ports发布宿主机端口
volumes配置挂载
depends_on定义依赖启动顺序及条件
healthcheck定义健康检查
restart容器重启策略
networks指定容器网络

depends_on

depends_on:
  db:
    condition: service_healthy

表示启动 Backend 前先等待 DB 的 Health Check 通过。

但不会自动保证数据库在整个应用运行期间始终可用。Django 仍然需要合理处理数据库连接失败和重连。

10. Environment Variables

这里需要区分两种机制。

Compose 插值变量

environment:
  APP_PORT: ${APP_PORT:-8000}

Compose 从 Shell、项目 .env 等来源解析变量。

其中:

  • ${APP_PORT:-8000}:没有设置或为空时使用 8000。
  • ${VAR:?Required}:未设置或为空时报错。

env_file

env_file:
  - ./backend/.env

将文件中的变量注入 Backend 容器。

重要区别

Compose .env
    ↓
Compose 配置变量插值

services.backend.env_file
    ↓
Backend 容器环境变量

两者不是同一回事。

同名变量同时在 environment 和 env_file 指定时,显式的 environment 具有更高优先级。

可以检查:

docker compose config

但这个命令可能输出解析后的敏感信息,不应随意把完整结果提交到日志或公开分享。

11. 容器生命周期

Created
   ↓
Running
   ↓
Stopped / Exited
   ↓
Removed

容器主进程退出,容器通常也会退出。

例如:

CMD ["gunicorn", "..."]

Gunicorn 是主进程;它退出后,容器随之结束。

Restart Policy

restart: unless-stopped

表示容器退出后由 Docker 按相应策略尝试重启,除非已被手动停止。

它不是 Health Check 失败后必然自动重启容器的保证。

PID 1 与信号

容器停止时通常向主进程发送终止信号,然后等待一段时间。

因此建议:

  • 使用 Exec Form 启动应用。
  • 确保应用支持优雅退出。
  • 不要让不必要的 Shell 阻挡终止信号。
  • 必要时使用 init: true 处理子进程回收。

12. Docker Compose 常用命令

状态与日志

docker compose ps

docker compose logs

docker compose logs -f backend

docker stats

启动与停止

# 启动
docker compose up -d

# 停止但保留容器
docker compose stop

# 停止并删除服务容器和网络
docker compose down

# 同时删除 Compose 管理的相关 Volume
docker compose down -v

生产数据库所在的 Named Volume 不应随意执行 down -v。

构建与更新

# 只构建 Backend 镜像
docker compose build backend

# 不使用构建缓存
docker compose build --no-cache backend

# 构建并启动
docker compose up -d --build

# 只更新 Backend 服务
docker compose up -d --build backend

# 强制重建容器
docker compose up -d --force-recreate backend

如果修改了 Dockerfile 或镜像内部的源码,应先构建新镜像再启动。

如果只修改了通过 env_file 注入的环境变量,可以重新创建容器:

docker compose up -d --force-recreate backend

如果修改的是 Compose 配置:

docker compose up -d

通常会识别配置变化并按需重新创建对应服务。

进入容器

docker compose exec backend sh

docker compose exec backend python manage.py shell

docker compose exec backend python manage.py migrate

并非所有容器镜像都包含 Bash,特别是 Alpine 或精简镜像,因此 sh 通常更通用。

exec 与 run

docker compose exec backend python manage.py shell

在已有的运行中容器执行命令。

docker compose run --rm backend python manage.py shell

创建一个新的临时服务容器执行命令。

后者适合一次性管理任务,但其网络、端口及生命周期行为与 exec 不完全相同。

13. 镜像仓库(Registry)

常见镜像仓库:

  • Docker Hub。
  • GitHub Container Registry(GHCR)。
  • AWS ECR。

镜像名称示例:

ghcr.io/username/sorivel-backend:1.0.0

构建与推送:

docker build \
  -t ghcr.io/username/sorivel-backend:1.0.0 \
  .

docker push ghcr.io/username/sorivel-backend:1.0.0

拉取:

docker pull ghcr.io/username/sorivel-backend:1.0.0

多架构构建

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t ghcr.io/username/sorivel-backend:1.0.0 \
  --push \
  .

可以构建并推送多个 CPU 架构对应的镜像。

需要使用支持目标平台构建的 Buildx Builder,必要时通过 QEMU 等机制执行跨架构构建。

14. Health Check

Dockerfile:

HEALTHCHECK --interval=30s --timeout=5s \
  CMD python -c \
  "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"

也可以在 Compose 中定义:

healthcheck:
  test:
    - CMD
    - python
    - -c
    - "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"
  interval: 30s
  timeout: 5s
  retries: 3

Health Check 用于判断服务是否能够正常响应。

常见状态:

starting
healthy
unhealthy

注意:容器处于 Running 状态,不代表应用一定 Healthy。

15. Secrets 与安全

不要把密码直接写入 Dockerfile 或提交到 Git 仓库。

尤其避免:

ARG DATABASE_PASSWORD=secret
ENV DATABASE_PASSWORD=secret

构建阶段如果需要访问私有资源,可以使用 BuildKit Secret Mount。

例如:

RUN --mount=type=secret,id=pip_token \
    ...

对于运行时密钥,可以使用部署平台的 Secret 管理能力,或通过适当权限保护的配置文件注入。

其他建议:

  • 尽量使用非 Root 用户运行应用。
  • 不向公网暴露 PostgreSQL、Redis 等内部服务端口。
  • 使用明确的镜像版本,而不是无条件依赖 latest。
  • 定期更新基础镜像。
  • 为 PostgreSQL Volume 建立独立备份机制。

16. 数据库操作与备份

执行 Migration:

docker compose exec backend python manage.py migrate

查看 Volume:

docker volume ls

PostgreSQL 导出

docker compose exec -T db \
  pg_dump -U app -d app > backup.sql

PostgreSQL 导入

docker compose exec -T db \
  psql -U app -d app < backup.sql

这里假设 Compose 服务名为 db,数据库和用户均为 app。

执行导入之前,需要确认目标数据库状态及备份格式。SQL 导入不等于安全、完整的数据库迁移流程。

Volume 不等于 Backup

Volume 负责持久化,但无法防止:

  • 错误删除数据。
  • 数据库逻辑损坏。
  • 磁盘故障。
  • 错误 Migration。
  • 宿主机丢失。

生产数据库仍然需要定期备份,并验证恢复流程。

17. 清理命令

# 查看镜像
docker images

# 查看容器
docker ps -a

# 查看 Volume
docker volume ls

# 删除指定镜像
docker rmi image_name

# 清理不再使用的构建缓存
docker builder prune

# 清理全部未使用的构建缓存
docker builder prune -a

开发环境完全重置:

docker compose down -v --rmi all --remove-orphans
docker compose up -d --build

该操作具有破坏性,尤其是 -v 会删除 Compose 管理的相关 Volume。不要在包含生产数据的环境中直接执行。

总结

Docker 核心内容可以分为五部分:

分类必须掌握的内容
基础模型Dockerfile、Image、Container
构建Build Context、Cache、Multi-stage、Buildx
运行环境ENV、ARG、CMD、ENTRYPOINT、Health Check
基础设施Volume、Bind Mount、Network、Ports
运维Docker Compose、Logs、Exec、Backup、Registry

日常部署的典型流程:

Write Dockerfile
        ↓
docker compose build
        ↓
Docker Images
        ↓
docker compose up -d
        ↓
Running Containers
        ↓
Health Check / Logs
        ↓
Application

最重要的实践原则:

  1. 镜像负责打包应用,容器负责运行。
  2. 数据库数据必须持久化,并且要有备份。
  3. 生产部署尽量避免直接挂载整个源码目录。
  4. 通过 Docker Network 和服务名进行容器间通信。
  5. 修改镜像内容需要重新构建并更新容器。
  6. 不要把敏感信息写入镜像或版本库。

参考文档