Skip to content

课 3 · Docker Compose 开发环境

本课目标

用 Docker Compose 把进阶项目的中间件依赖一键启动,不再手动安装。Neo4j 通过 profile 延迟启动,需要时再开。

关键理解:开发环境编排不是"运维工作",而是让团队成员 pnpm dev:infra 就能跑起来所有依赖。

进阶项目会用到四个中间件:

中间件用途模块
Postgres + pgvector关系数据 + 向量存储,替代 SQLite模块 1-2、模块 4
Redis缓存、限流、任务队列模块 3
Elasticsearch全文检索、混合检索模块 4
Neo4j图谱存储(Graph RAG)模块 7

为什么要从 SQLite 换到 Postgres?

基础课用 SQLite 是合理的:单进程、零配置、够用。进阶课引入 API + Worker 双进程后,问题出现了——SQLite 同一时刻只允许一个写入者,API 进程和 Worker 进程并发写任务状态会锁冲突甚至数据损坏。Postgres 是生产级关系数据库,支持多进程并发写,同时 pgvector 扩展可以直接在 Postgres 里存向量,不再需要独立的 sqlite-vec 文件。

这节课先把 Redis 和 Elasticsearch 跑起来,Neo4j 延迟到模块 7 再启。

Docker Compose 配置

yaml
# infra/docker-compose.dev.yml
version: '3.9'

services:
  redis:
    image: redis:7-alpine
    ports:
      - '6379:6379'
    volumes:
      - redis-data:/data
    command: redis-server --save 60 1 --loglevel warning
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      timeout: 3s
      retries: 5

  postgres:
    image: pgvector/pgvector:pg16      # Postgres 16 + pgvector 预装
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=knowledgeops
    ports:
      - '5432:5432'
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U app -d knowledgeops']
      interval: 5s
      timeout: 3s
      retries: 10

  elasticsearch:
    image: elasticsearch:8.13.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - '9200:9200'
    volumes:
      - es-data:/usr/share/elasticsearch/data
    healthcheck:
      test: ['CMD-SHELL', 'curl -sf http://localhost:9200/_cluster/health | grep -q "\"status\":\"green\"\|\"status\":\"yellow\""']
      interval: 10s
      timeout: 5s
      retries: 10

  neo4j:
    image: neo4j:5-community
    profiles: ['graph']        # 需要时用 --profile graph 启动
    environment:
      - NEO4J_AUTH=neo4j/password
    ports:
      - '7474:7474'
      - '7687:7687'
    volumes:
      - neo4j-data:/data

volumes:
  postgres-data:
  redis-data:
  es-data:
  neo4j-data:

启动命令封装

在根目录 package.json 添加便捷命令:

json
{
  "scripts": {
    "dev:infra": "docker compose -f infra/docker-compose.dev.yml up -d",
    "dev:infra:down": "docker compose -f infra/docker-compose.dev.yml down",
    "dev:infra:logs": "docker compose -f infra/docker-compose.dev.yml logs -f",
    "dev:infra:graph": "docker compose -f infra/docker-compose.dev.yml --profile graph up -d"
  }
}

连接配置

.env 管理连接信息,不硬编码在代码里:

bash
# infra/.env.example
DATABASE_URL=postgres://app:secret@localhost:5432/knowledgeops
REDIS_URL=redis://localhost:6379
ELASTICSEARCH_URL=http://localhost:9200
NEO4J_URL=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password
typescript
// apps/api/src/config.ts
export const config = {
  database: {
    url: process.env.DATABASE_URL ?? 'postgres://app:secret@localhost:5432/knowledgeops',
  },
  redis: {
    url: process.env.REDIS_URL ?? 'redis://localhost:6379',
  },
  elasticsearch: {
    url: process.env.ELASTICSEARCH_URL ?? 'http://localhost:9200',
  },
  neo4j: {
    url: process.env.NEO4J_URL ?? 'bolt://localhost:7687',
    user: process.env.NEO4J_USER ?? 'neo4j',
    password: process.env.NEO4J_PASSWORD ?? 'password',
  },
}

健康检查脚本

本地开发常见问题是"服务还没准备好就启动了应用"。加一个简单的健康检查:

typescript
// apps/api/src/lib/health-check.ts
import { config } from '../config'

export async function waitForServices() {
  // 等待 Elasticsearch 就绪
  let retries = 20
  while (retries > 0) {
    try {
      const res = await fetch(`${config.elasticsearch.url}/_cluster/health`)
      const data = await res.json()
      if (data.status === 'green' || data.status === 'yellow') break
    } catch {
      // 还没准备好
    }
    retries--
    await new Promise((r) => setTimeout(r, 1000))
    console.log(`等待 Elasticsearch... (${retries})`)
  }
  if (retries === 0) throw new Error('Elasticsearch 启动超时')
}

本节产物

infra/
  docker-compose.dev.yml      # 开发环境中间件编排
  .env.example                # 连接配置示例
apps/api/src/
  config.ts                   # 统一配置读取
  lib/
    health-check.ts           # 服务健康检查

课堂实作

  1. 复制 infra/.env.exampleinfra/.env
  2. 运行 pnpm dev:infra,确认 Postgres、Redis、Elasticsearch 启动
  3. 验证 Postgres 正常并启用 pgvector:
    bash
    psql postgres://app:secret@localhost:5432/knowledgeops \
      -c 'CREATE EXTENSION IF NOT EXISTS vector; SELECT extversion FROM pg_extension WHERE extname = '"'"'vector'"'"';'
  4. 访问 http://localhost:9200 验证 ES 正常响应

面试追问

为什么 Neo4j 用 profile 延迟启动?

不是所有开发阶段都需要 Neo4j,强制启动会多占 1GB+ 内存。Docker Compose 的 profiles 功能让服务按需启动,默认不会启动带 profile 标记的服务,只有明确指定 --profile graph 才会包含它。

开发环境和生产环境的 Compose 配置有什么区别?

开发环境侧重方便:单节点、不开安全认证、挂载本地 volume 方便查看数据。生产环境需要集群、TLS、持久化和监控。进阶课聚焦开发阶段,生产部署在模块 8 再讨论。

面向前端工程师和独立开发者的 AI 应用工程课程