build: local stack, container images and the end-to-end smoke script
docker-compose brings up PostgreSQL 16 with pgvector and Redis 7 on non-standard host ports so it never collides with an existing local database. scripts/smoke.sh is the proof: compose, migrate, boot the API, issue a token, call the real DeepSeek API and print the resulting ob_ai_call and bl_ledger rows. services/vision and services/matting are placeholders noting that the local RTX 5090 makes BiRefNet and RTMPose developable without cloud GPU.
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# API image (架构文档 §14.1 CVM-app / api). Multi-stage so the runtime layer
|
||||
# carries no build toolchain and no dev dependencies.
|
||||
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy \
|
||||
UV_PYTHON_DOWNLOADS=never
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock* README.md ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-install-project --no-dev || uv sync --no-install-project --no-dev
|
||||
|
||||
COPY apps ./apps
|
||||
COPY packages ./packages
|
||||
COPY services ./services
|
||||
COPY prompts ./prompts
|
||||
COPY migrations ./migrations
|
||||
COPY alembic.ini ./
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --no-dev --frozen || uv sync --no-dev
|
||||
|
||||
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
RUN groupadd --system meida && useradd --system --gid meida --home /app meida
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder --chown=meida:meida /app /app
|
||||
|
||||
USER meida
|
||||
EXPOSE 8000
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD python -c "import urllib.request;urllib.request.urlopen('http://localhost:8000/healthz')"
|
||||
|
||||
# Gunicorn supervises uvicorn workers (架构文档 §5: FastAPI + Uvicorn/Gunicorn).
|
||||
CMD ["gunicorn", "apps.api.main:app", \
|
||||
"--worker-class", "uvicorn.workers.UvicornWorker", \
|
||||
"--workers", "4", \
|
||||
"--bind", "0.0.0.0:8000", \
|
||||
"--timeout", "120", \
|
||||
"--graceful-timeout", "30", \
|
||||
"--access-logfile", "-"]
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Celery worker image. Same build as the API; different entrypoint and queue set.
|
||||
# 架构文档 §14.1 runs worker-cpu / worker-io on CVM-app and worker-gpu on CVM-gpu,
|
||||
# so the queue list is injected rather than baked in.
|
||||
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy \
|
||||
UV_PYTHON_DOWNLOADS=never
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock* README.md ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-install-project --no-dev || uv sync --no-install-project --no-dev
|
||||
|
||||
COPY apps ./apps
|
||||
COPY packages ./packages
|
||||
COPY services ./services
|
||||
COPY prompts ./prompts
|
||||
COPY migrations ./migrations
|
||||
COPY alembic.ini ./
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --no-dev --frozen || uv sync --no-dev
|
||||
|
||||
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
TZ=Asia/Shanghai \
|
||||
CELERY_QUEUES=cpu,io \
|
||||
CELERY_CONCURRENCY=4
|
||||
|
||||
RUN groupadd --system meida && useradd --system --gid meida --home /app meida
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder --chown=meida:meida /app /app
|
||||
|
||||
USER meida
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||
CMD celery -A apps.worker.celery_app.celery_app inspect ping -d "celery@$(hostname)" || exit 1
|
||||
|
||||
CMD ["sh", "-c", "celery -A apps.worker.celery_app.celery_app worker -Q ${CELERY_QUEUES} -c ${CELERY_CONCURRENCY} -l info"]
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
# `deploy/`
|
||||
|
||||
| 文件 | 用途 |
|
||||
| --- | --- |
|
||||
| `docker-compose.dev.yml` | 本地开发栈:PostgreSQL 16 + pgvector、Redis 7(均带 healthcheck) |
|
||||
| `Dockerfile.api` | API 镜像(gunicorn + uvicorn worker) |
|
||||
| `Dockerfile.worker` | Celery worker 镜像(队列由 `CELERY_QUEUES` 注入) |
|
||||
|
||||
```bash
|
||||
make up # 只起 postgres + redis
|
||||
make down
|
||||
docker compose -f deploy/docker-compose.dev.yml --profile app up -d # 连 api/worker 一起
|
||||
```
|
||||
|
||||
本地端口刻意用 **55432 / 56379**,避免和开发机上已有的 PG/Redis 撞车。
|
||||
|
||||
## V1 生产形态(架构 §14.1)
|
||||
|
||||
单可用区 + Docker Compose,不上 K8s(TD-03、团队规模):
|
||||
|
||||
```
|
||||
CLB → CVM-app: nginx / api / admin / worker-cpu / worker-io / beat / vision-svc
|
||||
CVM-gpu(按需启停): tryon-svc / worker-gpu / matting
|
||||
云数据库 PostgreSQL(pgvector) · 云数据库 Redis · COS×4 + CDN · CLS
|
||||
```
|
||||
|
||||
**GPU 实例按需启停是 V1 最有效的成本手段**:队列有积压时拉起,空闲一段时间后
|
||||
释放(NFR-COST-04)。
|
||||
|
||||
## 规矩
|
||||
|
||||
- **镜像里不许有密钥**(架构 §11.2)。配置一律从环境变量注入,生产走 SSM/KMS。
|
||||
`env_file: ../.env` 只在本地 compose 用。
|
||||
- 数据库迁移是发布流程里的**独立步骤**,先迁移后发布(§14.3)。
|
||||
- 镜像打标签化版本,不要用 `latest` 发布。
|
||||
- 容器以非 root 用户运行,两个 Dockerfile 都已如此。
|
||||
|
||||
## 还没有的东西
|
||||
|
||||
nginx 配置、TLS 终止、CLB/CDN 配置、COS 四桶的生命周期策略(临时桶 ≤ 2 小时,
|
||||
CR-02)、Grafana 三张看板、告警规则、备份与恢复演练脚本(FR-SYS-09)。
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
# Local development stack (T-P1-03: 新成员一条命令起环境).
|
||||
# Host ports are deliberately non-standard so this never collides with a
|
||||
# PostgreSQL/Redis already running on the developer's machine.
|
||||
name: meida-dev
|
||||
|
||||
services:
|
||||
postgres:
|
||||
# pgvector is required from day one (架构文档 §5, TD-08) — proving the
|
||||
# extension exists here is what stops "works on my laptop" later.
|
||||
image: pgvector/pgvector:pg16
|
||||
container_name: meida-postgres
|
||||
environment:
|
||||
POSTGRES_USER: meida
|
||||
POSTGRES_PASSWORD: meida
|
||||
POSTGRES_DB: meida
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "55432:5432"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U meida -d meida"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: meida-redis
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
ports:
|
||||
- "56379:6379"
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
# --- optional: run the app itself in containers (`--profile app`) ---------
|
||||
api:
|
||||
profiles: ["app"]
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.api
|
||||
container_name: meida-api
|
||||
env_file: ../.env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://meida:meida@postgres:5432/meida
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
CELERY_BROKER_URL: redis://redis:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://redis:6379/2
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://localhost:8000/healthz')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
worker-cpu:
|
||||
profiles: ["app"]
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.worker
|
||||
container_name: meida-worker-cpu
|
||||
env_file: ../.env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://meida:meida@postgres:5432/meida
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
CELERY_BROKER_URL: redis://redis:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://redis:6379/2
|
||||
CELERY_QUEUES: cpu,io
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
redisdata:
|
||||
Reference in New Issue
Block a user