【Docker Compose 快速入门】

Docker Compose 快速入门

  • [Docker Compose 快速入门](#Docker Compose 快速入门)
    • 前提条件
    • [第 1 步:搭建项目](#第 1 步:搭建项目)
    • [第 2 步:定义并启动你的服务](#第 2 步:定义并启动你的服务)
    • [第 3 步:通过健康检查修复启动竞态](#第 3 步:通过健康检查修复启动竞态)
    • [第 4 步:启用 Compose Watch 实现实时更新](#第 4 步:启用 Compose Watch 实现实时更新)
    • [第 5 步:使用命名卷持久化数据](#第 5 步:使用命名卷持久化数据)
    • [第 6 步:使用多个 Compose 文件组织项目](#第 6 步:使用多个 Compose 文件组织项目)
    • [第 7 步:检查和调试正在运行的栈](#第 7 步:检查和调试正在运行的栈)

Docker Compose 快速入门

本教程旨在通过引导你开发一个基础的 Python Web 应用,来介绍 Docker Compose 的基本概念。

该应用使用 Flask 框架,并借助 Redis 实现了一个访问计数器,为 Docker Compose 在 Web 开发场景中的应用提供了一个实用的示例。即使你不熟悉 Python,这里展示的概念也应该容易理解。

前提条件

请确保你已经:

  • 安装了最新版本的 Docker Compose
  • 对 Docker 概念以及 Docker 的工作原理有基本了解

第 1 步:搭建项目

  1. 为项目创建一个目录:

    console 复制代码
    $ mkdir compose-demo
    $ cd compose-demo
  2. 在项目目录中创建 app.py,并添加以下内容:

    python 复制代码
    import os
    import redis
    from flask import Flask
    
    app = Flask(__name__)
    cache = redis.Redis(
        host=os.getenv("REDIS_HOST", "redis"),
        port=int(os.getenv("REDIS_PORT", "6379")),
    )
    
    @app.route("/")
    def hello():
        count = cache.incr("hits")
        return f"Hello from Docker! I have been seen {count} time(s).\n"

    该应用从环境变量中读取 Redis 连接信息,并提供了合理的默认值,因此开箱即用。

  3. 在项目目录中创建 requirements.txt,并添加以下内容:

    text 复制代码
    flask
    redis
  4. 创建一个 Dockerfile:

    dockerfile 复制代码
    # syntax=docker/dockerfile:1
    
    # 基于 Python 3.12 镜像构建
    FROM python:3.12-alpine
    
    # 将工作目录设置为 `/code`
    WORKDIR /code
    
    # 设置 `flask` 命令使用的环境变量
    ENV FLASK_APP=app.py
    ENV FLASK_RUN_HOST=0.0.0.0
    
    # 安装 `gcc` 及其他依赖
    RUN apk add --no-cache gcc musl-dev linux-headers
    
    # 复制 `requirements.txt`
    COPY requirements.txt .
    
    # 安装 Python 依赖
    RUN pip install -r requirements.txt
    
    # 将项目中的当前目录 `.` 复制到镜像中的工作目录 `.`
    COPY . .
    
    EXPOSE 5000
    
    # 将容器的默认命令设置为 `flask run --debug`
    CMD ["flask", "run", "--debug"]

    !IMPORTANT

    请确保文件名为 Dockerfile,没有扩展名。有些编辑器会自动添加 .txt,

    这会导致构建失败。

    有关如何编写 Dockerfile 的更多信息,请参阅 Dockerfile 参考。

  5. 创建一个 .env 文件来保存配置值:

    text 复制代码
    APP_PORT=8000
    REDIS_HOST=redis
    REDIS_PORT=6379

    Compose 会自动读取 .env,并让这些值可用于 compose.yaml 中的变量插值。

    对于这个示例来说,好处不算大,但在实践中,将配置排除在 Compose 文件之外可以更容易地:

    • 在不同环境之间更改值而无需编辑 YAML
    • 避免将机密提交到版本控制
    • 在多个服务之间复用值
  6. 创建一个 .dockerignore 文件,将不必要的文件排除在构建上下文之外:

    text 复制代码
    .env
    *.pyc
    __pycache__
    redis-data

    Docker 在构建镜像时会把你项目目录中的所有内容发送给守护进程。

    如果没有 .dockerignore,其中就会包含你的 .env 文件(可能含有机密)以及

    任何缓存的 Python 字节码。排除它们可以保持构建速度,并避免意外

    将敏感值固化到镜像层中。

第 2 步:定义并启动你的服务

Compose 简化了整个应用栈的控制,让你可以在单个 YAML 配置文件中轻松管理服务、网络和卷。

  1. 在项目目录中创建 compose.yaml,并粘贴以下内容:

    yaml 复制代码
    services:
      web:
        build: .
        ports:
          - "${APP_PORT}:5000"
        environment:
          - REDIS_HOST=${REDIS_HOST}
          - REDIS_PORT=${REDIS_PORT}
    
      redis:
        image: redis:alpine

    这个 Compose 文件定义了两个服务:

    • web 服务使用基于当前目录中的 Dockerfile 构建的镜像。它将主机上的端口 8000 映射到容器中 Flask 默认监听的端口 5000。

    • redis 服务使用从 Docker Hub 注册表拉取的公共 Redis 镜像。

    有关 compose.yaml 文件的更多信息,请参阅 Compose 如何工作。

  2. 启动你的应用:

    console 复制代码
    $ docker compose up

    只需一条命令,你就可以根据配置文件创建并启动所有服务。Compose 会构建你的 web 镜像、拉取 Redis 镜像,并启动两个容器。

  3. 打开 http://localhost:8000。你应该会看到:

    text 复制代码
    Hello from Docker! I have been seen 1 time(s).

    刷新页面------每次访问计数器都会递增。

    这个最小化设置可以工作,但它有两个问题,你将在接下来的步骤中修复:

    • 启动竞态:web 与 redis 同时启动。如果 Redis 还没准备好,
      Flask 应用会连接失败并崩溃。
    • 没有持久化:如果你运行 docker compose down 然后再运行 docker compose up,
      计数器会重置为零。docker compose down 会删除容器,随之删除写入容器可写层的所有数据。
      docker compose stop 会保留容器,因此数据可以保留,但在生产环境中容器会定期被替换,
      你不能依赖这一点。
  4. 在继续之前停止整个栈:

    console 复制代码
    $ docker compose down

第 3 步:通过健康检查修复启动竞态

要修复启动竞态,Compose 需要等待 redis 被确认健康后,再启动 web。

  1. 更新 compose.yaml:

    yaml 复制代码
    services:
      web:
        build: .
        ports:
          - "${APP_PORT}:5000"
        environment:
          - REDIS_HOST=${REDIS_HOST}
          - REDIS_PORT=${REDIS_PORT}
        depends_on:
          redis:
            condition: service_healthy
    
      redis:
        image: redis:alpine
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 5
          start_period: 10s

    healthcheck 块告诉 Compose 如何测试 Redis 是否已准备好:

    • test 是 Compose 在容器内运行以检查其健康状况的命令。
      redis-cli ping 会连接到 Redis 并期望返回 PONG 响应------如果收到该响应,
      容器就是健康的。
    • start_period 给 Redis 10 秒的初始化时间,之后才开始健康检查。
      在这个时间窗口内的任何失败都不会计入重试次数。
    • interval 在启动期结束后每 5 秒运行一次检查。
    • timeout 给每次检查 3 秒的响应时间,超时则视为失败。
    • retries 设置 Compose 将容器标记为不健康之前允许的连续失败次数。
      在 interval: 5s 和 retries: 5 的情况下,Compose 最多会等待 25 秒才放弃。
  2. 启动整个栈以确认顺序问题已修复:

    console 复制代码
    $ docker compose up

    你应该会看到类似以下内容:

    text 复制代码
    [+] Running 2/2
    ✔ Container compose-demo-redis-1  Healthy                       0.0s
  3. 打开 http://localhost:8000 确认应用仍在工作,然后在继续之前停止整个栈:

    console 复制代码
    $ docker compose down

第 4 步:启用 Compose Watch 实现实时更新

如果没有 Compose Watch,每次代码更改都需要你停止整个栈、重新构建镜像并重启容器。

Compose Watch 通过在你保存文件时自动将更改同步到正在运行的容器中,消除了这个循环。

  1. 更新 compose.yaml,为 web 服务添加 develop.watch 块:

    yaml 复制代码
    services:
      web:
        build: .
        ports:
          - "${APP_PORT}:5000"
        environment:
          - REDIS_HOST=${REDIS_HOST}
          - REDIS_PORT=${REDIS_PORT}
        depends_on:
          redis:
            condition: service_healthy
        develop:
          watch:
            - action: sync+restart
              path: .
              target: /code
            - action: rebuild
              path: requirements.txt
    
      redis:
        image: redis:alpine
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 5
          start_period: 10s

    watch 块定义了两条规则:

    • sync+restart 操作会监视主机上的项目目录(.)。当文件发生变化时,
      Compose 会将任何已更改的文件复制到正在运行的容器内的 /code 中,然后重启容器。
      由于容器重启时更新后的文件已经就位,Flask 启动时会直接读取新代码------无需手动重新构建或重启。
    • 针对 requirements.txt 的 rebuild 操作会在你添加新依赖时触发完整的镜像重新构建,
      因为安装软件包需要重新构建镜像,而不仅仅是同步文件。
  2. 启用 Watch 启动整个栈:

    console 复制代码
    $ docker compose up --watch
  3. 进行实时更改。打开 app.py 并更新问候语:

    python 复制代码
    return f"Hello from Compose Watch! I have been seen {count} time(s).\n"
  4. 保存文件。Compose Watch 会检测到更改并立即同步:

    text 复制代码
    Syncing service "web" after changes were detected
  5. 刷新 http://localhost:8000。更新后的问候语会出现,无需任何重启,

    计数器应该仍在递增。

  6. 在继续之前停止整个栈:

    console 复制代码
    $ docker compose down

    有关 Compose Watch 工作原理的更多信息,请参阅 使用 Compose Watch。

第 5 步:使用命名卷持久化数据

每次停止并重启整个栈时,访问计数器都会重置为零。Redis 数据

存放在容器内部,因此当容器被删除时数据也会消失。命名卷通过将数据存储在主机上、

容器生命周期之外来解决这个问题。

  1. 更新 compose.yaml:

    yaml 复制代码
    services:
      web:
        build: .
        ports:
          - "${APP_PORT}:5000"
        environment:
          - REDIS_HOST=${REDIS_HOST}
          - REDIS_PORT=${REDIS_PORT}
        depends_on:
          redis:
            condition: service_healthy
        develop:
          watch:
            - action: sync+restart
              path: .
              target: /code
            - action: rebuild
              path: requirements.txt
    
      redis:
        image: redis:alpine
        volumes:
          - redis-data:/data
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 5
          start_period: 10s
    
    volumes:
      redis-data:

    redis.volumes 下的 redis-data:/data 条目将命名卷挂载到 /data,

    这是 Redis 写入其数据文件的路径。顶层的 volumes 键将其注册到 Docker,

    以便它在 compose down 和 compose up 循环之间持久化。

  2. 使用 docker compose up --watch 启动整个栈,并多次刷新 http://localhost:8000 以累积计数。

  3. 使用 docker compose down 拆除整个栈,然后再次使用 docker compose up --watch 将其启动。

  4. 打开 http://localhost:8000------计数器会从上次中断的地方继续。

  5. 现在使用 docker compose down -v 重置计数器。

    -v 标志会连同容器一起删除命名卷。请有意使用它------它会永久删除存储的数据。

第 6 步:使用多个 Compose 文件组织项目

随着应用的增长,单个 compose.yaml 会变得越来越难以维护。include

顶层元素允许你将服务拆分到多个文件中,同时让它们仍属于同一个应用。

当不同团队负责栈的不同部分,或者你想在多个项目之间复用基础设施定义时,

这尤其有用。

  1. 在项目目录中创建一个名为 infra.yaml 的新文件,并将 Redis 服务和卷移入其中:

    yaml 复制代码
    services:
      redis:
        image: redis:alpine
        volumes:
          - redis-data:/data
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 5
          start_period: 10s
    
    volumes:
      redis-data:
  2. 更新 compose.yaml 以包含 infra.yaml:

    yaml 复制代码
    include:
       - path: ./infra.yaml
    services:
      web:
        build: .
        ports:
          - "${APP_PORT}:5000"
        environment:
          - REDIS_HOST=${REDIS_HOST}
          - REDIS_PORT=${REDIS_PORT}
        depends_on:
          redis:
            condition: service_healthy
        develop:
          watch:
            - action: sync+restart
              path: .
              target: /code
            - action: rebuild
              path: requirements.txt
  3. 运行应用以确认一切仍然正常:

    console 复制代码
    $ docker compose up --watch

    Compose 会在启动时合并两个文件。web 服务仍然可以通过名称引用 redis,

    因为所有被包含的服务共享同一个默认网络。

    这是一个简化的示例,但它展示了 include 的基本原理,以及它如何更容易地

    将复杂应用模块化为子 Compose 文件。有关 include 以及使用多个 Compose 文件的更多信息,

    请参阅 使用多个 Compose 文件。

  4. 在继续之前停止整个栈:

    console 复制代码
    $ docker compose down

第 7 步:检查和调试正在运行的栈

有了完整配置的栈,你可以在不停止任何东西的情况下观察容器内部发生的情况。

本步骤涵盖用于检查解析后的配置、流式查看日志以及在正在运行的容器内运行命令的核心命令。

在启动栈之前,验证 Compose 已解析你的 .env 变量并正确合并了所有文件:

console 复制代码
$ docker compose config

docker compose config 不要求栈正在运行------它完全基于你的文件工作。输出中有几点值得注意:

  • ${APP_PORT}、${REDIS_HOST} 和 ${REDIS_PORT} 都已替换为
    你的 .env 文件中的值。
  • 短格式端口表示法("8000:5000")被展开为规范字段
    (target、published、protocol)。
  • 默认网络和卷名称被明确显示,并以项目名称
    compose-demo 为前缀。
  • 输出是完全解析后的配置,通过 include 引入的任何文件
    都会合并到单个视图中。

当你想要确认 Compose 实际将应用什么时,尤其是在调试变量替换或使用多个 Compose 文件时,

可以随时使用 docker compose config。

现在以分离模式启动栈,以便终端可以腾出来执行后续命令:

console 复制代码
$ docker compose up -d

流式查看所有服务的日志

console 复制代码
$ docker compose logs -f

-f 标志会实时跟踪日志流,将两个容器的输出交错显示,

并带有按服务名称着色的前缀。多次刷新 http://localhost:8000,

观察 Flask 请求日志出现。要跟踪单个服务的日志,请传入其名称:

console 复制代码
$ docker compose logs -f web

按 Ctrl+C 停止跟踪日志。容器会继续运行。

在正在运行的容器内运行命令

docker compose exec 在已经运行的容器内运行命令,而不会启动新容器。

这是进行实时调试的主要工具。

验证环境变量设置正确
console 复制代码
$ docker compose exec web env | grep REDIS
text 复制代码
REDIS_HOST=redis
REDIS_PORT=6379
测试 web 容器能否使用服务名称作为主机名访问 Redis
console 复制代码
$ docker compose exec web python -c "import redis; r = redis.Redis(host='redis'); print(r.ping())"
text 复制代码
True

这里使用了与你的应用相同的 redis 库,因此返回 True 确认了

服务发现、网络和 Redis 连接都端到端正常工作。

检查 Redis 中访问计数器的实时值
console 复制代码
$ docker compose exec redis redis-cli GET hits
相关推荐
QYRdata2 小时前
2026-2032年图形处理云服务器CAGR预计达18.7%
运维·服务器·服务发现
分布式存储与RustFS2 小时前
图床搬到自己的对象存储:PicGo 加 S3 插件的完整配置
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
月落汀兰2 小时前
建池后类型不可修改!深度拆解 Ceph PG 放置组,副本 / 纠删码池选型,快照与命名空间避坑
运维·ceph
技术人生0013 小时前
VMware Workstation 安装 Ubuntu 24.04 LTS 完整教程
运维·服务器·ubuntu
ai小陈3 小时前
GPU服务器租用容器实战:Docker数据卷持久化与安全重建
服务器·人工智能·安全·docker·ai·gpu算力
智能运维指南3 小时前
从分散运维到统一运维:2026 统一运维管理体系建设路径与选型要点
运维·一体化运维·智能运维·嘉为蓝鲸
ITyunwei09873 小时前
ITIL 5 落地前,先补齐工单数据底座的 4 步
运维·企业微信
如果'\'真能转义说3 小时前
Docker Desktop | 本地化挂载 Postgresql
docker·postgresql·容器