Appearance
课 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=passwordtypescript
// 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 # 服务健康检查课堂实作
- 复制
infra/.env.example为infra/.env - 运行
pnpm dev:infra,确认 Postgres、Redis、Elasticsearch 启动 - 验证 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'"'"';' - 访问
http://localhost:9200验证 ES 正常响应
面试追问
为什么 Neo4j 用 profile 延迟启动?
不是所有开发阶段都需要 Neo4j,强制启动会多占 1GB+ 内存。Docker Compose 的 profiles 功能让服务按需启动,默认不会启动带 profile 标记的服务,只有明确指定 --profile graph 才会包含它。
开发环境和生产环境的 Compose 配置有什么区别?
开发环境侧重方便:单节点、不开安全认证、挂载本地 volume 方便查看数据。生产环境需要集群、TLS、持久化和监控。进阶课聚焦开发阶段,生产部署在模块 8 再讨论。