一条命令跑通整个后端:Docker Compose 入门

22 阅读5分钟

「从零到 AI 应用工程师」专栏 · 第 7 篇


到上一篇,你的对话服务已经有了:接口、分层、统一错误、PostgreSQL 历史、Redis 缓存。

但「在我电脑上能跑」和「别人(或未来的你)十分钟能跑」之间,还差一口气。本机装数据库、改端口、环境不一致……这些摩擦会吃掉大量热情。

今天把它们收成一句:

docker compose up -d --build

应用、Postgres、Redis 一起起来;再用 /healthz 证明依赖真的可用——不只是容器显示 Up。


一、先把三个词分清

概念一句话
镜像 Image只读的构建产物,像安装包
容器 Container镜像跑起来的进程实例
Compose用一份 YAML 声明「多容器怎么一起跑」

Compose 里,服务名就是容器网络里的主机名。所以应用连数据库应写 db,连 Redis 应写 redis——不要写 127.0.0.1

127.0.0.1 在容器里指的是这个容器自己,不是你的笔记本电脑,也不是旁边的数据库容器。这是新手踩得最多的坑。


二、最小 Dockerfile

FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app

# 先装依赖,再拷代码 —— 利用层缓存,改代码不必每次重装包
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

两个细节:

  • 监听 0.0.0.0,否则容器外访问不到;
  • requirements.txt 与源码分开 COPY,改一行业务代码不会让依赖层全部失效。

三、docker-compose.yml(应用 + 库 + 缓存)

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: chat_db
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./sql/init.sql:/docker-entrypoint-initdb.d/01_init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d chat_db"]
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10

  web:
    build: .
    env_file: .env.docker
    ports:
      - "127.0.0.1:8000:8000"   # 只绑本机,降低误暴露风险
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz')\"",
        ]
      interval: 10s
      timeout: 5s
      retries: 10

volumes:
  pg_data:
  redis_data:

.env.docker 示例(不要提交真实密钥):

DB_HOST=db
DB_PORT=5432
DB_USER=app_user
DB_PASSWORD=请换成强密码
DB_NAME=chat_db

REDIS_HOST=redis
REDIS_PORT=6379

API_TOKEN=请换成你的令牌
MODEL_API_KEY=sk-你的密钥
MODEL_API_URL=https://api.example.com/v1/chat/completions
MODEL_NAME=your-model-name

应用代码里读库、读缓存,主机名要用环境变量里的 db / redis


四、健康检查:Up 不等于可用

depends_on: condition: service_healthy 比「只等容器启动」强一截,但应用自己仍应提供:

GET /ping     → 进程活着
GET /healthz  → 进程 + 数据库 + Redis(必要时再加模型配置检查)

示例逻辑:

@router.get("/healthz")
async def healthz():
    db_ok = check_db()       # SELECT 1
    redis_ok = check_redis() # PING
    ready = db_ok and redis_ok
    status_code = 200 if ready else 503
    return JSONResponse(
        status_code=status_code,
        content={
            "code": status_code,
            "message": "ok" if ready else "dependency not ready",
            "data": {"db": db_ok, "redis": redis_ok},
        },
    )

验收时:容器 Up 只是起点,/healthz 返回 200 才算栈就绪。


五、常用命令

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

# 看状态
docker compose ps

# 看日志(出问题先看这里)
docker compose logs --tail=100 web
docker compose logs --tail=100 db

# 停掉(默认保留数据卷)
docker compose down

# 停掉并删除数据卷 —— 危险,本地数据会没
# docker compose down -v

改了 .env 或依赖:通常需要 重建容器,不是简单 restart

docker compose up -d --build --force-recreate web

六、验收清单

docker compose up -d --build
docker compose ps

curl -i http://127.0.0.1:8000/ping
curl -i http://127.0.0.1:8000/healthz

curl -X POST http://127.0.0.1:8000/chat \
  -H "Authorization: Bearer 你的令牌" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "docker_demo",
    "session_id": "session_docker_01",
    "message": "栈是否健康"
  }'

全部通过,阶段 1 的「能交付最小后端」就齐了。


七、常见坑(建议贴显示器旁)

  1. 容器里把数据库地址写成 localhost → 改成服务名 db
  2. 密码、Token、模型 Key 写进镜像或打进 Git。 → 环境变量 / env_file,示例用 .env.example
  3. 以为 depends_on 等于数据库已可查询。 → 加 healthcheck;应用再做 /healthz
  4. 误用 docker compose down -v → 本地库被清空。
  5. 改了 init.sql 却不见生效。 → 初始化脚本只在数据卷首次创建时跑;已有卷需要迁移或删卷重建(删卷会丢数据)。
  6. 生产把 5432 / 6379 映射到公网。 → 数据库和缓存只走内部网络;本示例仅把 8000 绑在 127.0.0.1
  7. 改了依赖不 --build → 容器里仍是旧包。

八、阶段 1 小结:你现在手里有什么

从第 2 篇到第 7 篇,一条对话后端的骨架已经立住:

能力
02FastAPI + 大模型,打通 /chat
03路由 / 业务 / 客户端分层
04统一响应、统一异常、request_id
05PostgreSQL 历史 + 分页
06Redis 缓存重复问题(可降级)
07Docker Compose 一键拉起整栈

这不是玩具脚本了——这是后面做 RAG、做 Agent 时,可以继续往上长的底座。


九、带走这三条

  1. Compose 把应用、依赖、网络、卷、健康检查变成可重复执行的声明。
  2. 容器网络用服务名;持久数据用 Volume;密钥用环境注入。
  3. 验收看 /healthz 和核心接口,不只看容器 Up。

下一阶段进入专栏最硬核的一块:RAG——让大模型读懂你自己的资料,还不许瞎编。 第 8 篇先把概念和全貌讲清,再进入解析、分块、向量检索。

这是专栏第 7 篇,也是 chat-api 阶段的收官。两天一更,RAG 见。