fix(devops): 修复容器化部署面缺口,docker compose 可干净构建
消除 DEPLOYMENT.md §1.5 列出的 6 个「照抄即部署」缺口(仅动部署面文件,不改应用源码):
1. apps/api/Dockerfile 补 `COPY alembic.ini ./`,容器内可直接 alembic upgrade head。
2. base compose api 注入 CREDENTIAL_ENC_KEY(${VAR:-dev 占位},自动读根 .env 覆盖)+
APP_ENV/LOG_JSON/CORS_ORIGINS,无 .env 也能起,不再因缺 Fernet key 启动失败。
3. base compose web 设 API_BASE_INTERNAL=http://api:8000,RSC 直连 api 容器。
4. web Dockerfile 加 ARG/ENV NEXT_PUBLIC_API_BASE(默认 localhost:8000),compose
web.build.args 传入,构建期可注入公网 API 基址。
5. api/web Dockerfile 内置 HEALTHCHECK(api 用 urllib 打 /health、web 用 node 打 /)。
6. 新增 docker-compose.prod.yml 覆盖层做生产加固:pg 强口令 ${POSTGRES_PASSWORD}、
pg 仅绑 127.0.0.1、APP_ENV=prod、LOG_JSON=true、密钥/连接串/CORS/公网 API 全从
env 且必填(:? 缺失拒起)、restart: unless-stopped。base 保持 dev 便利不破坏。
附带修复:web Dockerfile 在 install 前补 COPY pnpm-workspace.yaml,使容器内 install
看到 minimumReleaseAgeExclude/onlyBuiltDependencies,避免误拒 @xyflow 等近期发布依赖;
新增 .dockerignore(根 + apps/web)排除宿主 node_modules/.next/.venv 等,避免平台原生
二进制经 COPY . . 覆盖容器版导致 next build 失败,并加速构建上下文、保证可复现。
验证:docker compose config(base)与 prod 合并 config 均通过;docker compose build
两镜像均成功(api 378MB / web 296MB);自定义 NEXT_PUBLIC_API_BASE 已确认内联进前端
bundle。DEPLOYMENT.md §1.5 回写为「已修复」并补 §2.6 生产 env 清单与正确部署命令。
This commit is contained in:
19
.dockerignore
Normal file
19
.dockerignore
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# api 镜像构建上下文(context=仓库根)。api Dockerfile 仅选择性 COPY
|
||||||
|
# pyproject.toml/uv.lock/packages/apps/api/alembic.ini,但整个仓库会作为构建上下文发给
|
||||||
|
# daemon——排除大体积/无关目录,加速上传并避免宿主产物泄漏进镜像。
|
||||||
|
.git
|
||||||
|
.venv
|
||||||
|
**/node_modules
|
||||||
|
**/.next
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
**/*.tsbuildinfo
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.DS_Store
|
||||||
|
example_skills
|
||||||
171
DEPLOYMENT.md
171
DEPLOYMENT.md
@@ -20,8 +20,8 @@
|
|||||||
| 服务名 | 角色 | 镜像来源 | 容器端口 | compose 发布端口 | 健康检查 |
|
| 服务名 | 角色 | 镜像来源 | 容器端口 | compose 发布端口 | 健康检查 |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `pg` | Postgres 16 数据库(唯一真源) | `postgres:16-alpine`(拉取) | 5432 | `5432:5432` | ✅ `pg_isready -U writer`(interval 5s / timeout 3s / retries 10) |
|
| `pg` | Postgres 16 数据库(唯一真源) | `postgres:16-alpine`(拉取) | 5432 | `5432:5432` | ✅ `pg_isready -U writer`(interval 5s / timeout 3s / retries 10) |
|
||||||
| `api` | FastAPI 后端(async + SSE + LangGraph 编排) | 由 `apps/api/Dockerfile` 构建,构建上下文 = **仓库根 `.`** | 8000 | `8000:8000` | ❌ 无(见 §1.5 缺口) |
|
| `api` | FastAPI 后端(async + SSE + LangGraph 编排) | 由 `apps/api/Dockerfile` 构建,构建上下文 = **仓库根 `.`** | 8000 | `8000:8000` | ✅ Dockerfile `HEALTHCHECK`:`python urllib` 打 `/health`(interval 30s / timeout 5s / start-period 20s / retries 3) |
|
||||||
| `web` | Next.js 前端(standalone 产物) | 由 `apps/web/Dockerfile` 构建,构建上下文 = **`./apps/web`** | 3000 | `3000:3000` | ❌ 无(见 §1.5 缺口) |
|
| `web` | Next.js 前端(standalone 产物) | 由 `apps/web/Dockerfile` 构建,构建上下文 = **`./apps/web`** | 3000 | `3000:3000` | ✅ Dockerfile `HEALTHCHECK`:`node http` 打 `/`(interval 30s / timeout 5s / start-period 20s / retries 3) |
|
||||||
|
|
||||||
依赖关系(compose `depends_on`):
|
依赖关系(compose `depends_on`):
|
||||||
- `api` 依赖 `pg`,且**等 pg 健康**后才起(`condition: service_healthy`)。
|
- `api` 依赖 `pg`,且**等 pg 健康**后才起(`condition: service_healthy`)。
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
| 变量 | 是否必填 | 默认值(`config.ts` 回退) | 说明 |
|
| 变量 | 是否必填 | 默认值(`config.ts` 回退) | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `NEXT_PUBLIC_API_BASE` | 生产建议 | `http://localhost:8000` | **浏览器端**访问 API 的基址。⚠️ Next.js 的 `NEXT_PUBLIC_*` 在**构建期内联**进客户端 bundle——只在运行期(compose environment)设它**不会改**已构建产物(见 §1.5 缺口)。生产需在**构建期**注入前端可达的公网 API 地址(通常是反代后的 `https://writer.example.com/api` 之类)。 |
|
| `NEXT_PUBLIC_API_BASE` | 生产建议 | `http://localhost:8000` | **浏览器端**访问 API 的基址。⚠️ Next.js 的 `NEXT_PUBLIC_*` 在**构建期内联**进客户端 bundle——只在运行期设它**不会改**已构建产物。web `Dockerfile` 现声明 `ARG NEXT_PUBLIC_API_BASE`(默认 `http://localhost:8000`),base/prod compose 的 `web.build.args` 均传入它;生产在**构建期**用 `docker compose build` 注入前端可达的公网 API 地址(通常反代后的 `https://writer.example.com` 之类)。 |
|
||||||
| `API_BASE_INTERNAL` | 容器编排建议 | 回退到 `NEXT_PUBLIC_API_BASE` | **服务端组件(RSC)**取数用的内网基址。容器内应设为 `http://api:8000`(compose 服务名)。**当前 compose 未设它**(见 §1.5 缺口),RSC 会回退到 `localhost:8000` 而打不到 api 容器。 |
|
| `API_BASE_INTERNAL` | 容器编排建议 | 回退到 `NEXT_PUBLIC_API_BASE` | **服务端组件(RSC)**取数用的内网基址。base/prod compose 的 `web.environment` 已设为 `http://api:8000`(compose 服务名),RSC 直连 api 容器。 |
|
||||||
|
|
||||||
### 1.4 LLM provider key 不进环境变量
|
### 1.4 LLM provider key 不进环境变量
|
||||||
|
|
||||||
@@ -69,17 +69,20 @@ Kimi Code 走 OAuth 订阅授权(`kimi_oauth` 路由),同样落库。
|
|||||||
|
|
||||||
推论:`CREDENTIAL_ENC_KEY` 是解开所有已存凭据的唯一钥匙——**丢了它 = 库里所有 provider 凭据不可解密**,只能重新录入。务必备份保管(见 §2.3 / §6)。
|
推论:`CREDENTIAL_ENC_KEY` 是解开所有已存凭据的唯一钥匙——**丢了它 = 库里所有 provider 凭据不可解密**,只能重新录入。务必备份保管(见 §2.3 / §6)。
|
||||||
|
|
||||||
### 1.5 已发现的部署面缺口(部署前需知)
|
### 1.5 部署面缺口——已修复(现可照抄部署)
|
||||||
|
|
||||||
以下是审阅 Dockerfile/compose 时发现的、影响「照抄即部署」的点,本文各节给出规避办法:
|
此前审阅 Dockerfile/compose 发现的 6 个「照抄即部署」缺口**均已在部署面文件消除**(不改应用源码)。现状如下:
|
||||||
|
|
||||||
1. **api 镜像未拷 `alembic.ini`**:`apps/api/Dockerfile` 只 `COPY pyproject.toml / packages / apps/api`,**没有** `COPY alembic.ini`(它在仓库根)。因此 `docker compose run --rm api uv run alembic upgrade head` 会因找不到 `alembic.ini` 而失败。规避:从**宿主机 checkout** 跑迁移(§2.4 方式一,当前即可用),或给 Dockerfile 补一行 `COPY alembic.ini ./`(§2.4 方式二)。
|
1. **api 镜像已拷 `alembic.ini`** ✅:`apps/api/Dockerfile` 在 `COPY apps/api` 之后新增 `COPY alembic.ini ./`(迁移目录 `packages/db/migrations` 随 `COPY packages` 进镜像)。因此可直接在 api 容器内跑 `docker compose run --rm api uv run alembic upgrade head`(§2.4)。
|
||||||
2. **compose 的 `api` 服务未提供 `CREDENTIAL_ENC_KEY`**(也没 `env_file`):base compose 里 `api.environment` 只有 `DATABASE_URL/DATABASE_URL_SYNC/APP_ENV=dev`。由于该密钥启动即校验,**base compose 直接 `up` 会让 api 启动失败**。生产必须补齐(§2.2 覆盖文件)。
|
2. **compose `api` 已提供 `CREDENTIAL_ENC_KEY`** ✅:base `docker-compose.yml` 的 `api.environment` 现注入 `CREDENTIAL_ENC_KEY`(`${CREDENTIAL_ENC_KEY:-<dev 占位>}`,compose 自动读仓库根 `.env` 覆盖该默认)、`APP_ENV`/`LOG_JSON`/`CORS_ORIGINS`。**base compose 现可直接 `up` 起 api**(无 `.env` 也走 dev 占位 key 起来;生产用 §2.2 覆盖文件强制真值)。
|
||||||
3. **compose 的 `web` 服务未设 `API_BASE_INTERNAL`**:RSC 服务端取数会回退到 `localhost:8000`,在容器里打不到 api。需在覆盖文件设 `API_BASE_INTERNAL=http://api:8000`(§2.2)。
|
3. **compose `web` 已设 `API_BASE_INTERNAL`** ✅:base 的 `web.environment` 设 `API_BASE_INTERNAL=http://api:8000`,RSC 服务端取数直连 api 容器。
|
||||||
4. **`NEXT_PUBLIC_API_BASE` 是构建期内联**:web Dockerfile 未声明该 build ARG,浏览器 bundle 里的 API 基址会固定为 `http://localhost:8000`。生产要改,需在**构建阶段**注入(§3 反代方案里说明取舍)。
|
4. **`NEXT_PUBLIC_API_BASE` 支持构建期注入** ✅:web `Dockerfile` 在 `pnpm build` 前声明 `ARG NEXT_PUBLIC_API_BASE=http://localhost:8000` + `ENV`;base/prod compose 的 `web.build.args` 传入它。生产 `docker compose -f ... -f docker-compose.prod.yml build` 即把公网 API 基址内联进 bundle。
|
||||||
5. **api/web 无 HEALTHCHECK**:两个 `Dockerfile` 和 compose 的 `api`/`web` 都没有健康检查(只有 `pg` 有)。编排/反代的存活探测需自行加(health 端点已就绪,见 §7.1)。
|
5. **api/web 已内置 HEALTHCHECK** ✅:`apps/api/Dockerfile` 用 stdlib `urllib` 打 `/health`、`apps/web/Dockerfile` 用 `node http` 打 `/`(均 interval 30s / timeout 5s / start-period 20s / retries 3,镜像无 curl 故用运行时自带解释器)。编排/反代可直接读容器健康态。
|
||||||
|
6. **base 弱口令/裸暴露——生产用覆盖层加固** ✅:base `docker-compose.yml` 保持 dev 便利(pg `writer/writer`、`5432:5432`、`APP_ENV=dev`);新增 **`docker-compose.prod.yml`** 覆盖层做生产加固——pg 强口令 `${POSTGRES_PASSWORD}`(`:?` 缺失即拒起)、pg 仅绑 `127.0.0.1:5432`、`APP_ENV=prod`、`LOG_JSON=true`、`CREDENTIAL_ENC_KEY`/`DATABASE_URL*`/`CORS_ORIGINS`/`NEXT_PUBLIC_API_BASE` 全从 env 且必填、`restart: unless-stopped`。
|
||||||
|
|
||||||
> 以上缺口不阻断部署,但决定了「不能照抄 base compose 一把梭」。生产用 §2.2 的覆盖文件补齐即可。
|
> 构建面同时补了两个 `.dockerignore`(仓库根 + `apps/web`):排除宿主 `node_modules`/`.next`/`.venv`/`.git`/缓存,避免宿主平台原生二进制(如 macOS 的 next-swc)经 `COPY . .` 覆盖容器内 Linux 版导致 `pnpm build` 失败,并加速构建上下文传输、保证可复现。
|
||||||
|
>
|
||||||
|
> 校验:`docker compose config`(base)与 `docker compose -f docker-compose.yml -f docker-compose.prod.yml config`(prod 合并)均通过;`docker compose build` 两镜像均成功构建。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -102,46 +105,27 @@ git checkout main # 或你的发布分支
|
|||||||
|
|
||||||
### 2.2 准备生产配置(覆盖 base compose 的 dev 缺省)
|
### 2.2 准备生产配置(覆盖 base compose 的 dev 缺省)
|
||||||
|
|
||||||
base `docker-compose.yml` 是**开发导向**(api 用 `APP_ENV=dev`、缺 `CREDENTIAL_ENC_KEY`、
|
base `docker-compose.yml` 是**开发导向**(pg 弱口令 `writer/writer`、`5432` 对外发布、`APP_ENV=dev`、
|
||||||
web 缺 `API_BASE_INTERNAL`、pg 端口对外发布)。生产用一个**覆盖文件** `docker-compose.prod.yml`
|
`CREDENTIAL_ENC_KEY` 用 dev 占位默认)。生产加固的**覆盖文件 `docker-compose.prod.yml` 已随仓库提供**(无需手写),
|
||||||
补齐,不改 base 文件:
|
与 base 叠加使用即可。它把安全项全部改为**从宿主 env 强制注入**(缺失即拒绝启动):
|
||||||
|
|
||||||
```yaml
|
- `pg`:`POSTGRES_PASSWORD=${POSTGRES_PASSWORD}` 强口令、端口仅绑 `127.0.0.1:5432`、`restart: unless-stopped`。
|
||||||
# docker-compose.prod.yml —— 与 base 叠加:docker compose -f docker-compose.yml -f docker-compose.prod.yml ...
|
- `api`:`APP_ENV=prod`、`LOG_JSON=true`、`CREDENTIAL_ENC_KEY`/`CORS_ORIGINS`/`DATABASE_URL*`(用同一强口令)全从 env。
|
||||||
services:
|
- `web`:`NEXT_PUBLIC_API_BASE`(构建期 build arg + 运行期 env)指向公网 API、`API_BASE_INTERNAL=http://api:8000`。
|
||||||
pg:
|
|
||||||
# 不把 5432 暴露到公网:仅绑本机回环(供宿主机跑迁移用),或整段删掉只走 compose 内网
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:5432:5432"
|
|
||||||
|
|
||||||
api:
|
你只需在**仓库根**准备一个 `.env`(compose 自动读取做 `${...}` 插值;**chmod 600、勿入库**,已被 `.gitignore` 忽略):
|
||||||
environment:
|
|
||||||
DATABASE_URL: postgresql+asyncpg://writer:${PG_PASSWORD}@pg:5432/writer
|
|
||||||
DATABASE_URL_SYNC: postgresql+psycopg://writer:${PG_PASSWORD}@pg:5432/writer
|
|
||||||
APP_ENV: prod
|
|
||||||
LOG_JSON: "true"
|
|
||||||
CREDENTIAL_ENC_KEY: ${CREDENTIAL_ENC_KEY} # 见 §2.3 生成
|
|
||||||
CORS_ORIGINS: "https://writer.example.com" # 前端真实来源,逗号分隔;不得含 '*'
|
|
||||||
# 可选:补健康检查(镜像里已装 uv/python,可用 python 探 /health)
|
|
||||||
# 若加了反代做存活探测,这里可省
|
|
||||||
|
|
||||||
web:
|
|
||||||
environment:
|
|
||||||
NEXT_PUBLIC_API_BASE: https://writer.example.com # 浏览器可达的公网 API(经反代)
|
|
||||||
API_BASE_INTERNAL: http://api:8000 # RSC 服务端内网直连 api 容器
|
|
||||||
```
|
|
||||||
|
|
||||||
同时准备 pg 的强口令与密钥(**不要提交进仓库**),可放在同目录 `.env`(compose 会自动读取用于 `${...}` 插值):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env(compose 变量插值用;chmod 600,勿入库)
|
# .env(compose 变量插值用;chmod 600,勿入库)
|
||||||
PG_PASSWORD=<强随机口令>
|
POSTGRES_PASSWORD=<强随机口令>
|
||||||
CREDENTIAL_ENC_KEY=<§2.3 生成的 Fernet key>
|
CREDENTIAL_ENC_KEY=<§2.3 生成的 Fernet key>
|
||||||
|
CORS_ORIGINS=https://writer.example.com # 前端真实来源,逗号分隔;禁通配 *
|
||||||
|
NEXT_PUBLIC_API_BASE=https://writer.example.com # 浏览器可达的公网 API(经反代)
|
||||||
```
|
```
|
||||||
|
|
||||||
> ⚠️ base compose 里 pg 的 `POSTGRES_PASSWORD: writer` 是硬编码弱口令。生产请在覆盖文件里
|
> prod 覆盖层对 `POSTGRES_PASSWORD`/`CREDENTIAL_ENC_KEY`/`CORS_ORIGINS`/`NEXT_PUBLIC_API_BASE` 用
|
||||||
> 用 `POSTGRES_PASSWORD: ${PG_PASSWORD}` 覆盖 pg,并让 `DATABASE_URL*` 用同一口令
|
> `${VAR:?...}`(必填)语义——**任一缺失,`docker compose ... config`/`up` 直接报错拒起**,从设计上杜绝
|
||||||
> (上例已用 `${PG_PASSWORD}`)。若你保持 base 的 `writer` 口令,**务必**确保 pg 不可从公网访问(§6)。
|
> 「带 base 弱口令/占位 key 上生产」。完整清单见 [§2.6 生产 env 清单](#26-生产-env-清单)。
|
||||||
|
|
||||||
### 2.3 生成并妥善保管 CREDENTIAL_ENC_KEY
|
### 2.3 生成并妥善保管 CREDENTIAL_ENC_KEY
|
||||||
|
|
||||||
@@ -182,31 +166,27 @@ $COMPOSE ps # 看 pg 是否 healthy
|
|||||||
四张检查点表。**checkpointer 的建表只在迁移里发生,绝不在应用运行时**(符合 CLAUDE.md「LangGraph」纪律)。
|
四张检查点表。**checkpointer 的建表只在迁移里发生,绝不在应用运行时**(符合 CLAUDE.md「LangGraph」纪律)。
|
||||||
所以**部署时必须先把迁移升到 head,应用才能正常写章/审稿/续跑**。
|
所以**部署时必须先把迁移升到 head,应用才能正常写章/审稿/续跑**。
|
||||||
|
|
||||||
因 api 镜像未随包 `alembic.ini`(§1.5 缺口 1),有两种可用方式:
|
api 镜像现已随包 `alembic.ini`(§1.5 已修复 1),**推荐直接在 api 容器里跑迁移**(无需宿主装 uv):
|
||||||
|
|
||||||
**方式一(当前即可用,无需改 Dockerfile)——从宿主机 checkout 跑迁移:**
|
**方式一(推荐)——在 api 容器里跑迁移:**
|
||||||
|
|
||||||
```bash
|
|
||||||
# 宿主机需装 uv(curl -LsSf https://astral.sh/uv/install.sh | sh; export PATH="$HOME/.local/bin:$PATH")
|
|
||||||
cd writer-work-flow
|
|
||||||
uv sync
|
|
||||||
# 指向 compose 里 pg 暴露到 127.0.0.1:5432 的端口(§2.2 覆盖文件已绑本机)
|
|
||||||
DATABASE_URL="postgresql+asyncpg://writer:${PG_PASSWORD}@127.0.0.1:5432/writer" \
|
|
||||||
DATABASE_URL_SYNC="postgresql+psycopg://writer:${PG_PASSWORD}@127.0.0.1:5432/writer" \
|
|
||||||
CREDENTIAL_ENC_KEY="${CREDENTIAL_ENC_KEY}" \
|
|
||||||
uv run alembic upgrade head
|
|
||||||
uv run alembic check # 可选:确认无漂移
|
|
||||||
```
|
|
||||||
|
|
||||||
**方式二(更干净,需给 Dockerfile 补一行)——在 api 容器里跑迁移:**
|
|
||||||
先在 `apps/api/Dockerfile` 加 `COPY alembic.ini ./`(放在 `COPY apps/api ./apps/api` 之后),
|
|
||||||
`$COMPOSE build api` 重建后:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$COMPOSE run --rm api uv run alembic upgrade head
|
$COMPOSE run --rm api uv run alembic upgrade head
|
||||||
|
$COMPOSE run --rm api uv run alembic check # 可选:确认无漂移
|
||||||
```
|
```
|
||||||
|
|
||||||
> 这一步需要 parent/维护者决定是否接受改 Dockerfile。改前方式二不可用。
|
> 该临时容器与 api 服务共享同一 env(含 `DATABASE_URL*`/`CREDENTIAL_ENC_KEY`),直连 compose 内网的 `pg`,无需暴露端口。
|
||||||
|
|
||||||
|
**方式二(备选)——从宿主机 checkout 跑迁移**(宿主已装 uv 时可用;prod 覆盖层把 pg 绑到 `127.0.0.1:5432`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 宿主机装 uv:curl -LsSf https://astral.sh/uv/install.sh | sh; export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
cd writer-work-flow && uv sync
|
||||||
|
DATABASE_URL="postgresql+asyncpg://writer:${POSTGRES_PASSWORD}@127.0.0.1:5432/writer" \
|
||||||
|
DATABASE_URL_SYNC="postgresql+psycopg://writer:${POSTGRES_PASSWORD}@127.0.0.1:5432/writer" \
|
||||||
|
CREDENTIAL_ENC_KEY="${CREDENTIAL_ENC_KEY}" \
|
||||||
|
uv run alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
**4) 起全部服务:**
|
**4) 起全部服务:**
|
||||||
|
|
||||||
@@ -229,11 +209,40 @@ curl -fI http://127.0.0.1:3000/
|
|||||||
$COMPOSE logs -f api
|
$COMPOSE logs -f api
|
||||||
```
|
```
|
||||||
|
|
||||||
若 api 启动即退出,最常见原因是 `CREDENTIAL_ENC_KEY` 缺失/非法(§1.5 缺口 2 / §2.3)——
|
若 api 启动即退出,最常见原因是 `CREDENTIAL_ENC_KEY` 非法(base compose 已注入 dev 默认;
|
||||||
日志会显示 CredentialKeyError 相关堆栈。
|
生产覆盖层要求经 env 提供合法 Fernet key,见 §2.3)——日志会显示 CredentialKeyError 相关堆栈。
|
||||||
|
|
||||||
部署完成后,**打开前端「设置」页录入 LLM provider 凭据**(§1.4),产品才能真正调用模型。
|
部署完成后,**打开前端「设置」页录入 LLM provider 凭据**(§1.4),产品才能真正调用模型。
|
||||||
|
|
||||||
|
### 2.6 生产 env 清单
|
||||||
|
|
||||||
|
生产用 `docker-compose.prod.yml` 覆盖层,以下变量必须在**仓库根 `.env`**(compose 自动读取做 `${...}` 插值;
|
||||||
|
`chmod 600`、勿入库)或宿主环境变量提供。带 `:?` 者缺失即 `config`/`up` **报错拒起**。
|
||||||
|
|
||||||
|
| 变量 | 必填 | 消费方 | 说明 |
|
||||||
|
|---|:---:|---|---|
|
||||||
|
| `POSTGRES_PASSWORD` | ✅ | pg + api(`DATABASE_URL*`) | pg 强随机口令,替换 base 弱口令 `writer/writer`。api 连接串复用它。 |
|
||||||
|
| `CREDENTIAL_ENC_KEY` | ✅ | api | Fernet key(§2.3 新生成,**勿用 `.env.example` 占位值**)。api 启动即校验;解开全部已存 provider 凭据的唯一钥匙,须离线备份。 |
|
||||||
|
| `CORS_ORIGINS` | ✅ | api | 浏览器跨源白名单,逗号分隔,**禁通配 `*`**(如 `https://writer.example.com`)。 |
|
||||||
|
| `NEXT_PUBLIC_API_BASE` | ✅ | web(**构建期**内联 + 运行期) | 浏览器可达的公网 API 基址(经反代)。改它必须**重新 `docker compose ... build web`**(构建期内联进 bundle)。 |
|
||||||
|
| `APP_ENV` | 覆盖层已固定 `prod` | api | 无需手设(prod 层写死)。 |
|
||||||
|
| `LOG_JSON` | 覆盖层已固定 `true` | api | 无需手设(prod 层写死)。 |
|
||||||
|
| `API_BASE_INTERNAL` | 覆盖层已固定 `http://api:8000` | web(RSC) | 无需手设(prod 层写死)。 |
|
||||||
|
|
||||||
|
> LLM provider key(DeepSeek/Kimi/OpenAI…)**永不进 env**——部署后在应用「设置」页录入、加密落库(§1.4)。
|
||||||
|
|
||||||
|
生成 `.env` 骨架(示例):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > .env <<'EOF'
|
||||||
|
POSTGRES_PASSWORD=<强随机口令>
|
||||||
|
CREDENTIAL_ENC_KEY=<uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" 的输出>
|
||||||
|
CORS_ORIGINS=https://writer.example.com
|
||||||
|
NEXT_PUBLIC_API_BASE=https://writer.example.com
|
||||||
|
EOF
|
||||||
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 反向代理 + HTTPS
|
## 3. 反向代理 + HTTPS
|
||||||
@@ -241,9 +250,9 @@ $COMPOSE logs -f api
|
|||||||
生产应在应用前放一层反代做 TLS 终止、单一入口、并(配合 §6)做访问控制。
|
生产应在应用前放一层反代做 TLS 终止、单一入口、并(配合 §6)做访问控制。
|
||||||
拓扑:浏览器 → 反代(443) → `web:3000`(页面/RSC)与 `api:8000`(`/api` 或独立子域)。
|
拓扑:浏览器 → 反代(443) → `web:3000`(页面/RSC)与 `api:8000`(`/api` 或独立子域)。
|
||||||
|
|
||||||
> 注意 `NEXT_PUBLIC_API_BASE` 的构建期内联(§1.5 缺口 4):浏览器直接打 API 的地址在**构建 web 镜像时**就定死了。
|
> 注意 `NEXT_PUBLIC_API_BASE` 的构建期内联:浏览器直接打 API 的地址在**构建 web 镜像时**就定死了。
|
||||||
> 若走「同源 `/api` 前缀反代」,需让前端构建时 `NEXT_PUBLIC_API_BASE` 指向该前缀(当前 web Dockerfile 未暴露该 build ARG,需按缺口 4 处理);
|
> web `Dockerfile` 已暴露 `ARG NEXT_PUBLIC_API_BASE`、prod compose 的 `web.build.args` 会传入它——改这个基址后**必须重新 `docker compose ... build web`** 才生效(重启不改已构建 bundle)。
|
||||||
> 若走「api 独立子域」(如 `api.example.com`),同理需在构建期注入。
|
> 若走「同源 `/api` 前缀反代」,构建时把 `NEXT_PUBLIC_API_BASE` 设为该前缀;若走「api 独立子域」(如 `api.example.com`),同理构建期注入。
|
||||||
|
|
||||||
### 3.1 Caddy(推荐,自动证书)
|
### 3.1 Caddy(推荐,自动证书)
|
||||||
|
|
||||||
@@ -310,9 +319,9 @@ server {
|
|||||||
|
|
||||||
- 两个 app:`fly launch` 分别为 api(`apps/api/Dockerfile`,构建上下文=仓库根)与 web(`apps/web/Dockerfile`,上下文=`apps/web`)。
|
- 两个 app:`fly launch` 分别为 api(`apps/api/Dockerfile`,构建上下文=仓库根)与 web(`apps/web/Dockerfile`,上下文=`apps/web`)。
|
||||||
- Postgres 用 `fly postgres create`(或外部托管 PG),把 `DATABASE_URL`/`DATABASE_URL_SYNC` 设成其连接串(注意驱动前缀 `+asyncpg`/`+psycopg`,Fly 给的是原生 `postgres://`,需自行加前缀)。
|
- Postgres 用 `fly postgres create`(或外部托管 PG),把 `DATABASE_URL`/`DATABASE_URL_SYNC` 设成其连接串(注意驱动前缀 `+asyncpg`/`+psycopg`,Fly 给的是原生 `postgres://`,需自行加前缀)。
|
||||||
- `fly secrets set CREDENTIAL_ENC_KEY=... CORS_ORIGINS=... PG_PASSWORD=...`。
|
- `fly secrets set CREDENTIAL_ENC_KEY=... CORS_ORIGINS=... POSTGRES_PASSWORD=...`。
|
||||||
- 迁移:`fly ssh console` 进 api 机器跑 `alembic upgrade head`(同样受 §1.5 缺口 1 影响——需镜像含 `alembic.ini`,或用 `fly ssh` + release_command)。可用 Fly `[deploy] release_command` 在每次发布前自动跑迁移(需 alembic.ini 在镜像里)。
|
- 迁移:镜像已含 `alembic.ini`(§1.5 已修复 1),可用 Fly `[deploy] release_command = "uv run alembic upgrade head"` 在每次发布前自动跑迁移,或 `fly ssh console` 手动跑。
|
||||||
- web 的 `NEXT_PUBLIC_API_BASE` 在**构建期**注入(Fly build args / Dockerfile ARG,缺口 4)。
|
- web 的 `NEXT_PUBLIC_API_BASE` 在**构建期**注入(Fly build args → Dockerfile `ARG NEXT_PUBLIC_API_BASE`,已暴露)。
|
||||||
- api 内部端口 8000、web 内部端口 3000,在 `fly.toml` 的 `internal_port` 对应。
|
- api 内部端口 8000、web 内部端口 3000,在 `fly.toml` 的 `internal_port` 对应。
|
||||||
|
|
||||||
### 4.2 Render
|
### 4.2 Render
|
||||||
@@ -320,11 +329,11 @@ server {
|
|||||||
- 两个 Web Service(Docker 运行时),分别指向两个 Dockerfile;web 的 Docker build context 设 `apps/web`,api 设仓库根。
|
- 两个 Web Service(Docker 运行时),分别指向两个 Dockerfile;web 的 Docker build context 设 `apps/web`,api 设仓库根。
|
||||||
- Postgres 用 Render Managed Postgres,把内部连接串填进 api 的 `DATABASE_URL*`(加 `+asyncpg`/`+psycopg` 前缀)。
|
- Postgres 用 Render Managed Postgres,把内部连接串填进 api 的 `DATABASE_URL*`(加 `+asyncpg`/`+psycopg` 前缀)。
|
||||||
- 在 api service 的 Environment 填 `CREDENTIAL_ENC_KEY`/`CORS_ORIGINS`/`APP_ENV=prod`/`LOG_JSON=true`。
|
- 在 api service 的 Environment 填 `CREDENTIAL_ENC_KEY`/`CORS_ORIGINS`/`APP_ENV=prod`/`LOG_JSON=true`。
|
||||||
- 迁移:用 Render 的 **Pre-Deploy Command** 跑 `uv run alembic upgrade head`(需镜像含 `alembic.ini`,缺口 1)。
|
- 迁移:用 Render 的 **Pre-Deploy Command** 跑 `uv run alembic upgrade head`(镜像已含 `alembic.ini`,§1.5 已修复 1)。
|
||||||
- web 与 api 的互通用 Render 的内部地址;`API_BASE_INTERNAL` 指向 api 内部 URL,`NEXT_PUBLIC_API_BASE`(构建期)指向 api 公网 URL。
|
- web 与 api 的互通用 Render 的内部地址;`API_BASE_INTERNAL` 指向 api 内部 URL,`NEXT_PUBLIC_API_BASE`(构建期 build arg)指向 api 公网 URL。
|
||||||
|
|
||||||
> 两个平台的共同注意点:**全部继承 §1.5 的缺口**(尤其 alembic.ini 未随镜像、NEXT_PUBLIC 构建期内联)。
|
> 两个平台的共同注意点:`NEXT_PUBLIC_API_BASE` 仍是**构建期内联**(改它须重构 web 镜像);`alembic.ini` 已随镜像,迁移可在容器/release 命令里跑。
|
||||||
> 上线前按缺口处理,或接受「迁移只能从含源码的环境跑」。
|
> HEALTHCHECK 已内建于镜像,平台存活探测可直接复用或按平台惯例另配 `/health`(api)/`/`(web)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -337,9 +346,8 @@ export COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
|
|||||||
|
|
||||||
git pull # 拉新代码(含新迁移 / schema.d.ts)
|
git pull # 拉新代码(含新迁移 / schema.d.ts)
|
||||||
$COMPOSE build # 重建 api/web 镜像
|
$COMPOSE build # 重建 api/web 镜像
|
||||||
# 先升迁移到 head(含新增业务表 + 可能的 checkpointer 变更),再滚动重启
|
# 先升迁移到 head(含新增业务表 + 可能的 checkpointer 变更),再滚动重启(见 §2.4)
|
||||||
# —— 用 §2.4 的方式一或方式二
|
$COMPOSE run --rm api uv run alembic upgrade head # 在 api 容器内跑迁移(镜像已含 alembic.ini)
|
||||||
uv run alembic upgrade head # 方式一(宿主机 checkout,指向 pg)
|
|
||||||
$COMPOSE up -d # 用新镜像重启 api/web(pg 不动)
|
$COMPOSE up -d # 用新镜像重启 api/web(pg 不动)
|
||||||
|
|
||||||
# 冒烟
|
# 冒烟
|
||||||
@@ -358,7 +366,7 @@ git checkout <上一个可用 tag>
|
|||||||
$COMPOSE build && $COMPOSE up -d
|
$COMPOSE build && $COMPOSE up -d
|
||||||
|
|
||||||
# 数据库回滚(谨慎):按需降级一版
|
# 数据库回滚(谨慎):按需降级一版
|
||||||
uv run alembic downgrade -1
|
$COMPOSE run --rm api uv run alembic downgrade -1
|
||||||
```
|
```
|
||||||
|
|
||||||
> ⚠️ DB 降级可能丢列/丢表数据,且 `d3e4f5a6b7c8` 的 `downgrade()` 会 `DROP` 四张 checkpoint 表
|
> ⚠️ DB 降级可能丢列/丢表数据,且 `d3e4f5a6b7c8` 的 `downgrade()` 会 `DROP` 四张 checkpoint 表
|
||||||
@@ -399,7 +407,7 @@ uv run alembic downgrade -1
|
|||||||
- `GET /health` → `{"status":"ok"}`
|
- `GET /health` → `{"status":"ok"}`
|
||||||
|
|
||||||
两者均无鉴权、返回 200,可作反代/编排的存活探测目标。
|
两者均无鉴权、返回 200,可作反代/编排的存活探测目标。
|
||||||
(提醒:api/web 的 Dockerfile 与 compose **未内置** HEALTHCHECK,§1.5 缺口 5——存活探测靠反代/平台配。)
|
api/web 的 Dockerfile **已内置** `HEALTHCHECK`(§1.5 已修复 5):api 用 stdlib `urllib` 打 `/health`、web 用 `node http` 打 `/`——`docker compose ps` 直接显示 `healthy`,反代/平台亦可复用。
|
||||||
|
|
||||||
### 7.2 结构化日志
|
### 7.2 结构化日志
|
||||||
|
|
||||||
@@ -444,5 +452,6 @@ $COMPOSE exec -T pg pg_restore -U writer -d writer --clean < backup_YYYY-MM-DD.d
|
|||||||
- **必填 env**:`CREDENTIAL_ENC_KEY`(启动即校验,缺失/非法则 api 起不来)。
|
- **必填 env**:`CREDENTIAL_ENC_KEY`(启动即校验,缺失/非法则 api 起不来)。
|
||||||
- **迁移**:`alembic upgrade head` 同时建业务表 + LangGraph checkpointer 表;checkpointer 只在迁移建表,不在运行时。
|
- **迁移**:`alembic upgrade head` 同时建业务表 + LangGraph checkpointer 表;checkpointer 只在迁移建表,不在运行时。
|
||||||
- **provider key**:不进 env,部署后在设置页录入、加密落库。
|
- **provider key**:不进 env,部署后在设置页录入、加密落库。
|
||||||
- **已知缺口**(§1.5):api 镜像缺 `alembic.ini`;compose `api` 缺 `CREDENTIAL_ENC_KEY`、`web` 缺 `API_BASE_INTERNAL`;`NEXT_PUBLIC_API_BASE` 构建期内联;api/web 无 HEALTHCHECK。
|
- **部署面缺口已全部修复**(§1.5):api 镜像含 `alembic.ini`;base compose `api` 注入 `CREDENTIAL_ENC_KEY`/`APP_ENV`/`LOG_JSON`/`CORS_ORIGINS`、`web` 设 `API_BASE_INTERNAL` + `NEXT_PUBLIC_API_BASE` build arg;api/web 内置 HEALTHCHECK;生产加固走 `docker-compose.prod.yml` 覆盖层(强口令/回环 pg/prod env/`restart`)。
|
||||||
|
- **部署命令**:`docker compose build`(dev)或 `docker compose -f docker-compose.yml -f docker-compose.prod.yml build`(prod)→ `... run --rm api uv run alembic upgrade head` → `... up -d`。
|
||||||
- **红线**:单用户无认证 → 不裸暴露公网;pg 不对外;CORS 禁 `*`;`CREDENTIAL_ENC_KEY` 备份+轮换。
|
- **红线**:单用户无认证 → 不裸暴露公网;pg 不对外;CORS 禁 `*`;`CREDENTIAL_ENC_KEY` 备份+轮换。
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ WORKDIR /app
|
|||||||
COPY pyproject.toml uv.lock* ./
|
COPY pyproject.toml uv.lock* ./
|
||||||
COPY packages ./packages
|
COPY packages ./packages
|
||||||
COPY apps/api ./apps/api
|
COPY apps/api ./apps/api
|
||||||
|
# alembic.ini 在仓库根,随 build context 拷入镜像,供容器内 `alembic upgrade head`(迁移目录
|
||||||
|
# packages/db/migrations 已随 `COPY packages` 进镜像;缺 alembic.ini 迁移会找不到配置而失败)。
|
||||||
|
COPY alembic.ini ./
|
||||||
RUN uv sync --no-dev --frozen || uv sync --no-dev
|
RUN uv sync --no-dev --frozen || uv sync --no-dev
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
# 存活探测:镜像无 curl,用 stdlib urllib 打 /health(非 200/连接失败会抛异常,退出非零)。
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||||
|
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health').read()"]
|
||||||
CMD ["uv", "run", "uvicorn", "ww_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uv", "run", "uvicorn", "ww_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
13
apps/web/.dockerignore
Normal file
13
apps/web/.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# web 镜像构建上下文(context=./apps/web)。排除宿主产物,避免 `COPY . .` 把宿主
|
||||||
|
# node_modules(含平台原生 next-swc/esbuild 二进制)覆盖容器内 `pnpm install` 的 Linux 版,
|
||||||
|
# 导致 `pnpm build` 加载原生绑定失败。同时排除构建产物/日志加速上下文传输、保证可复现。
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.turbo
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.DS_Store
|
||||||
|
coverage
|
||||||
|
*.tsbuildinfo
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
FROM node:22-slim AS build
|
FROM node:22-slim AS build
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json pnpm-lock.yaml* ./
|
# pnpm-workspace.yaml 必须在 install 前就位:它携带 minimumReleaseAgeExclude(豁免新发布包的
|
||||||
|
# 供应链策略)、onlyBuiltDependencies/allowBuilds(构建脚本白名单)。缺它则容器内 install 看不到
|
||||||
|
# 这些策略,会误拒近期发布的依赖(如 @xyflow/*)而失败——与宿主行为不一致。
|
||||||
|
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
|
||||||
RUN pnpm install --frozen-lockfile || pnpm install
|
RUN pnpm install --frozen-lockfile || pnpm install
|
||||||
COPY . .
|
COPY . .
|
||||||
|
# NEXT_PUBLIC_* 在 Next 构建期内联进浏览器 bundle——必须在 `pnpm build` 前用 build ARG 注入。
|
||||||
|
# 默认 http://localhost:8000 供 dev;生产经 compose build args 传公网 API 基址。
|
||||||
|
ARG NEXT_PUBLIC_API_BASE=http://localhost:8000
|
||||||
|
ENV NEXT_PUBLIC_API_BASE=$NEXT_PUBLIC_API_BASE
|
||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
FROM node:22-slim AS run
|
FROM node:22-slim AS run
|
||||||
@@ -12,4 +19,7 @@ COPY --from=build /app/.next/standalone ./
|
|||||||
COPY --from=build /app/.next/static ./.next/static
|
COPY --from=build /app/.next/static ./.next/static
|
||||||
COPY --from=build /app/public ./public
|
COPY --from=build /app/public ./public
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
# 存活探测:node 打首页(run 阶段无 curl);5xx/连接失败退出非零。
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||||
|
CMD ["node", "-e", "require('http').get('http://127.0.0.1:3000/',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"]
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
40
docker-compose.prod.yml
Normal file
40
docker-compose.prod.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 生产加固覆盖层 —— 与 base 叠加使用(base 保持 dev 便利,本文件收紧安全):
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml build
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||||
|
#
|
||||||
|
# 需在宿主机 .env(chmod 600,勿入库)或环境变量提供以下必填项(缺失即拒绝 up / config 报错):
|
||||||
|
# POSTGRES_PASSWORD —— pg 强随机口令(替换 base 的弱口令 writer/writer)
|
||||||
|
# CREDENTIAL_ENC_KEY —— Fernet key(生产新生成,勿用 .env.example 占位值)
|
||||||
|
# CORS_ORIGINS —— 前端真实来源,逗号分隔,禁通配 *
|
||||||
|
# NEXT_PUBLIC_API_BASE—— 浏览器可达的公网 API 基址(构建期内联进 web bundle)
|
||||||
|
services:
|
||||||
|
pg:
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: writer
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD 必填(生产强口令,勿用 base 弱口令)}
|
||||||
|
POSTGRES_DB: writer
|
||||||
|
# 不对公网暴露:仅绑本机回环(供宿主机跑迁移)。彻底隔离可整段删掉只走 compose 内网。
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:5432:5432"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
api:
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+asyncpg://writer:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD 必填}@pg:5432/writer
|
||||||
|
DATABASE_URL_SYNC: postgresql+psycopg://writer:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD 必填}@pg:5432/writer
|
||||||
|
APP_ENV: prod
|
||||||
|
LOG_JSON: "true"
|
||||||
|
CREDENTIAL_ENC_KEY: ${CREDENTIAL_ENC_KEY:?CREDENTIAL_ENC_KEY 必填(生产 Fernet key,勿用占位值)}
|
||||||
|
CORS_ORIGINS: ${CORS_ORIGINS:?CORS_ORIGINS 必填(前端真实来源,逗号分隔,禁通配)}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: ./apps/web
|
||||||
|
args:
|
||||||
|
# 构建期内联:生产须指向浏览器可达的公网 API(通常反代后的 https 域名)。
|
||||||
|
NEXT_PUBLIC_API_BASE: ${NEXT_PUBLIC_API_BASE:?NEXT_PUBLIC_API_BASE 必填(浏览器可达的公网 API 基址)}
|
||||||
|
environment:
|
||||||
|
NEXT_PUBLIC_API_BASE: ${NEXT_PUBLIC_API_BASE:?NEXT_PUBLIC_API_BASE 必填}
|
||||||
|
API_BASE_INTERNAL: http://api:8000
|
||||||
|
restart: unless-stopped
|
||||||
@@ -22,7 +22,12 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+asyncpg://writer:writer@pg:5432/writer
|
DATABASE_URL: postgresql+asyncpg://writer:writer@pg:5432/writer
|
||||||
DATABASE_URL_SYNC: postgresql+psycopg://writer:writer@pg:5432/writer
|
DATABASE_URL_SYNC: postgresql+psycopg://writer:writer@pg:5432/writer
|
||||||
APP_ENV: dev
|
APP_ENV: ${APP_ENV:-dev}
|
||||||
|
LOG_JSON: ${LOG_JSON:-false}
|
||||||
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
# Fernet 加密 key:api 启动即校验,缺失/非法则拒绝启动。compose 自动读根 .env 覆盖此默认;
|
||||||
|
# 默认值 = .env.example 的 dev 占位 key,保证 `docker compose up` 无 .env 也能起(勿用于生产)。
|
||||||
|
CREDENTIAL_ENC_KEY: ${CREDENTIAL_ENC_KEY:-cnMpG6QQxJejuDLHTe_S-nq2snoKgXCqWFfsctEHB-4=}
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -32,8 +37,13 @@ services:
|
|||||||
web:
|
web:
|
||||||
build:
|
build:
|
||||||
context: ./apps/web
|
context: ./apps/web
|
||||||
|
# NEXT_PUBLIC_API_BASE 为构建期内联,须经 build arg 传入(见 apps/web/Dockerfile)。
|
||||||
|
args:
|
||||||
|
NEXT_PUBLIC_API_BASE: ${NEXT_PUBLIC_API_BASE:-http://localhost:8000}
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_API_BASE: http://localhost:8000
|
NEXT_PUBLIC_API_BASE: ${NEXT_PUBLIC_API_BASE:-http://localhost:8000}
|
||||||
|
# RSC 服务端取数走容器内网直连 api(否则回退 localhost:8000 打不到 api 容器)。
|
||||||
|
API_BASE_INTERNAL: http://api:8000
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
Reference in New Issue
Block a user