Chorus 提供了官方 Docker 镜像 **`chorusaidlc/chorus-app`**。本指南包括：两种 Compose 形态，
即独立（内置数据库）与生产（外部 PostgreSQL + Redis）；一个对接现有数据库的 `docker run`
示例；完整的环境变量参考；以及容器启动时的行为。

先拉取镜像：

```bash
docker pull chorusaidlc/chorus-app:latest
```

关于如何在独立与生产两种形态之间做选择，请参见[部署概览](/zh/guides/deployment-overview/)。

## 独立（内置数据库）

无需外部数据库。镜像内置了 [PGlite](https://pglite.dev)（嵌入式 PostgreSQL），并会自动启动
所需的一切。数据保存在 Docker 卷中，可在容器重启后持久保留。

创建 `docker-compose.local.yml`：

```yaml
# Standalone Chorus — embedded PGlite, no external PostgreSQL or Redis
services:
  app:
    image: chorusaidlc/chorus-app:latest
    ports:
      - "8637:8637"
    environment:
      # No DATABASE_URL — entrypoint auto-starts embedded PGlite
      - REDIS_URL=
      # Leave empty: the container generates a random session secret on first
      # start and keeps it in the chorus-local-data volume. Set it explicitly
      # (openssl rand -base64 32) if you prefer to manage it yourself.
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-}
      - COOKIE_SECURE=false
      - DEFAULT_USER=${DEFAULT_USER:-admin@example.com}
      - DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-changeme}
    volumes:
      - chorus-local-data:/app/data

volumes:
  chorus-local-data:
```

然后运行：

```bash
docker compose -f docker-compose.local.yml up -d
```

打开 `http://localhost:8637`，使用 `admin@example.com` / `changeme` 登录（或通过
`DEFAULT_USER` / `DEFAULT_PASSWORD` 环境变量覆盖）。

在独立模式下，容器会：

- 在内部端口（`5433`）上启动 PGlite，不对外暴露。
- 将数据存放在 `chorus-local-data` Docker 卷中，因此可在重启后持久保留。
- 当 `NEXTAUTH_SECRET` 留空时，在首次启动时生成一个随机的会话签名密钥并保存在同一个卷中（参见
  [会话密钥](#会话密钥nextauth_secret)）。
- 禁用 Redis（回退到进程内的内存事件总线，仅支持单实例）。
- 在启动时自动执行 Prisma 迁移。

## 生产（外部 PostgreSQL + Redis）

面向生产环境，尤其是多副本部署时，请将应用对接到外部 PostgreSQL 与 Redis。创建
`docker-compose.yml`：

```yaml
services:
  app:
    image: chorusaidlc/chorus-app:latest
    ports:
      - "8637:8637"
    environment:
      - DATABASE_URL=postgresql://chorus:chorus@db:5432/chorus
      - REDIS_URL=redis://default:chorus-redis@redis:6379
      # Session-signing secret. Leave empty and the container generates one on
      # first start and persists it in the chorus-app-data volume (single
      # replica only). For production, or more than one app container, set it
      # explicitly in .env or the environment:  openssl rand -base64 32
      # Changing the secret invalidates Default Auth and Super Admin sessions.
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-}
      - COOKIE_SECURE=${COOKIE_SECURE:-false}
      - DEFAULT_USER=admin@example.com
      - DEFAULT_PASSWORD=your-password
    volumes:
      - chorus-app-data:/app/data
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass chorus-redis
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "chorus-redis", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: chorus
      POSTGRES_PASSWORD: chorus
      POSTGRES_DB: chorus
    volumes:
      - chorus-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U chorus -d chorus"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  chorus-app-data:
  chorus-data:
  redis-data:
```

如果想自己提供会话密钥，请在启动前生成一次，写进 compose 文件旁边的 `.env`：

```bash
echo "NEXTAUTH_SECRET=$(openssl rand -base64 32)" >> .env
```

然后运行：

```bash
docker compose up -d
```

打开 `http://localhost:8637`，使用你在 `DEFAULT_USER` / `DEFAULT_PASSWORD` 中设置的凭据
登录。

:::caution[HTTPS 与安全 Cookie]
上面的 Compose 文件设置了 `COOKIE_SECURE=false`，以支持纯 HTTP 部署（例如内网测试）。只应
在可信内网中使用 `COOKIE_SECURE=false`，此时 Cookie 会通过未加密的 HTTP 发送。当你在生产
环境以 HTTPS 部署时，请设置 `COOKIE_SECURE=true` 以启用安全 Cookie。
:::

## 对接现有 PostgreSQL 运行

如果你已经有 PostgreSQL 和 Redis 在运行，可直接启动容器：

```bash
docker run -d \
  -p 8637:8637 \
  -e DATABASE_URL=postgresql://user:pass@your-db-host:5432/chorus \
  -e REDIS_URL=redis://default:password@your-redis-host:6379 \
  -v chorus-app-data:/app/data \
  -e COOKIE_SECURE=false \
  -e DEFAULT_USER=admin@example.com \
  -e DEFAULT_PASSWORD=your-password \
  chorusaidlc/chorus-app:latest
```

此示例将生成的密钥保存在 `chorus-app-data` 中，容器重建后仍可复用。
若自行管理密钥，请注入持久的 `NEXTAUTH_SECRET`，并在每次启动时复用同一值。

## 环境变量

### 数据库与会话密钥

<div class="table-scroll" role="region" aria-label="必需环境变量" tabindex="0">

  | 变量 | 说明 |
  | --- | --- |
  | `DATABASE_URL` | PostgreSQL 连接字符串。格式：`postgresql://user:password@host:port/dbname`。也可以改为设置单独的 `DB_*` 变量（见下文）。**若省略**，入口脚本会自动启动一个嵌入式 PGlite 实例。 |
  | `NEXTAUTH_SECRET` | 用于签发会话令牌的密钥。请使用随机字符串（例如 `openssl rand -base64 32`）。**在 Docker 中可以留空**：容器会在首次启动时生成一个并保存在 `/app/data/.secret`。运行多个应用容器时，必须在每个副本上显式设置同一个值。参见[会话密钥](#会话密钥nextauth_secret)。 |

</div>

### 数据库（`DATABASE_URL` 的替代方案）

若未设置 `DATABASE_URL`，入口脚本会用以下单独变量拼装它：

<div class="table-scroll" role="region" aria-label="数据库环境变量" tabindex="0">

  | 变量 | 说明 |
  | --- | --- |
  | `DB_HOST` | PostgreSQL 主机 |
  | `DB_PORT` | PostgreSQL 端口（默认：`5432`） |
  | `DB_USERNAME` | PostgreSQL 用户名 |
  | `DB_PASSWORD` | PostgreSQL 密码 |
  | `DB_NAME` | 数据库名 |

</div>

### Redis

<div class="table-scroll" role="region" aria-label="Redis 环境变量" tabindex="0">

  | 变量 | 说明 |
  | --- | --- |
  | `REDIS_URL` | 完整的 Redis 连接字符串。格式：`redis://username:password@host:port`。优先级高于以下单独变量。 |
  | `REDIS_HOST` | Redis 主机（在未设置 `REDIS_URL` 时使用） |
  | `REDIS_PORT` | Redis 端口（默认：`6379`） |
  | `REDIS_USERNAME` | Redis 用户名（默认：`default`） |
  | `REDIS_PASSWORD` | Redis 密码 |

</div>

### 身份认证

<div class="table-scroll" role="region" aria-label="身份认证环境变量" tabindex="0">

  | 变量 | 说明 |
  | --- | --- |
  | `DEFAULT_USER` | 内置登录的邮箱地址（绕过 OIDC）。首次登录时自动创建该用户及其工作区。 |
  | `DEFAULT_PASSWORD` | 默认用户的密码（明文，运行时通过 bcrypt 比对）。 |
  | `NEXTAUTH_URL` | 应用对外的基础 URL（默认：`http://localhost:8637`）。在反向代理后运行时请设置此项。 |
  | `COOKIE_SECURE` | 设为 `"false"` 可为纯 HTTP 部署禁用安全 Cookie（docker-compose 中默认为 `"false"`）。以 HTTPS 部署到生产环境时设为 `"true"`。 |

</div>

### 日志

<div class="table-scroll" role="region" aria-label="日志环境变量" tabindex="0">

  | 变量 | 默认值 | 说明 |
  | --- | --- | --- |
  | `LOG_LEVEL` | `info`（生产）/ `debug`（开发） | 服务端最低日志级别。可选：`trace`、`debug`、`info`、`warn`、`error`、`fatal`、`silent`。设为 `info` 可抑制 Prisma 查询日志。 |
  | `NEXT_PUBLIC_LOG_LEVEL` | `warn`（生产）/ `debug`（开发） | 浏览器端最低日志级别。可选：`debug`、`info`、`warn`、`error`。 |

</div>

生产 Docker 镜像始终将 JSON 输出到 stdout（可直接对接 CloudWatch / ELK）。彩色美化输出仅在
本地开发时可用。

### 超级管理员

<div class="table-scroll" role="region" aria-label="超级管理员环境变量" tabindex="0">

  | 变量 | 说明 |
  | --- | --- |
  | `SUPER_ADMIN_EMAIL` | 超级管理员账号的邮箱（可访问 `/admin` 面板）。 |
  | `SUPER_ADMIN_PASSWORD_HASH` | 超级管理员密码的 bcrypt 哈希。生成方式：`node -e "console.log(require('bcryptjs').hashSync('your-password', 10))"` |

</div>

## 启动行为

容器的入口脚本每次启动都会执行相同的流程：

1. 检查 `NEXTAUTH_SECRET`。若为空，或等于以前示例文件中出现过的占位值，容器会在
   `/app/data/.secret` 中读取或生成密钥，并在日志中说明做了什么（但绝不打印密钥本身）。你自己
   显式设置的密钥会原样使用。
2. 若未设置 `DATABASE_URL` 且未提供任何 `DB_*` 变量，入口脚本会在内部端口（`5433`）上启动
   一个嵌入式 PGlite 实例。当**已配置**外部数据库时，不会启动 PGlite。
3. 执行 `prisma migrate deploy`，应用任何待处理的数据库迁移。
4. 若数据库尚未就绪，每 10 秒重试一次（最多 30 次，约五分钟）。
5. 迁移成功后，Next.js 服务在 `8637` 端口启动。

:::note[迁移自动执行]
你无需自行执行迁移。在启动时应用待处理的迁移是入口脚本的一部分，因此升级就是用新版镜像对接
同一个数据库并启动它。由于新镜像可能在首次启动时迁移你的数据库结构，请在升级前备份数据库或
数据卷，参见[运维自托管的 Chorus](/zh/guides/deploy-operations/)。
:::

## 会话密钥（`NEXTAUTH_SECRET`）

`NEXTAUTH_SECRET` 用于签发 Chorus 自己的会话 JWT（内置登录和超级管理员登录）。
OIDC 令牌由身份提供方签名，智能体 API 密钥也单独校验。轮换此密钥会使 Chorus 自签名会话失效，
不会轮换 API 密钥，也不会使身份提供方的 OIDC 令牌失效。

:::caution[从 0.18.0 或更早版本升级]
如果部署此前使用公开占位密钥，0.18.1 会忽略该值，改为复用或生成私有密钥。
受影响的 Chorus 自签名会话随之失效，相关用户需要重新登录。显式传入的安全密钥会原样保留，
继续使用该值，用户就无需重新登录。CDK 重新部署时需显式保留 `nextAuthSecret`，
参见 [CDK 密钥配置](/zh/guides/deploy-production/)。

旧 Compose 文件若未挂载 `/app/data`，每次容器重建都会丢失生成的密钥。
请添加上文所示的卷，或显式配置持久的 `NEXTAUTH_SECRET`。
:::

### 容器的行为

- **显式设置非占位值**：直接使用，不读写密钥文件。
- **未设置、为空或使用已知公开占位值**：复用 `/app/data/.secret`；文件不存在或为空时，
  生成 32 个随机字节，编码为 64 个十六进制字符，以 `0600` 权限保存。环境变量中的占位值还会触发警告。
- **持久化密钥不可用**：文件不是普通文件、不可读、保存了公开占位值，或生成、写入失败时，
  容器会在迁移前停止。已保存的公开占位值不会被悄悄接受或替换。

日志不会打印密钥值。要保留自动生成的密钥，请把 `/app/data` 放在持久卷上。多副本部署必须向每个实例显式注入
**同一个**密钥；[AWS CDK 栈](/zh/guides/deploy-production/)已通过 Secrets Manager 共享该值。

### 设置与轮换密钥

用 `openssl rand -base64 32` 生成一次，存入部署的密钥库或受保护的 `.env`，后续启动复用。
有效密钥改变后，内置邮箱／密码登录的用户和超级管理员需要重新登录。删除 `.secret` 只会影响自动生成的密钥；
已显式配置密钥时，删除该文件不会轮换密钥。

Docker 入口脚本和 npm `chorus` 启动器会自动处理密钥。直接用 `next start` 或 `node server.js`
启动时不会自动生成，需自行配置安全密钥。应用遇到已知占位值时会记录错误，但不会替换该值；
未设置密钥时，无法签发 Chorus 自签名会话。

## 镜像细节

- **基础镜像：** `node:22-alpine`
- **内部端口：** `8637`
- **架构：** `linux/amd64`、`linux/arm64`

## 后续步骤

- **[生产环境部署](/zh/guides/deploy-production/)**：从全局 npm 包运行，以及完整的 AWS CDK
  演练。
- **[运维自托管的 Chorus](/zh/guides/deploy-operations/)**：首次登录引导、部署侧身份认证，以及升级前的
  备份。
