Ever Gauzy 111.48.2 Docker Compose 部署指南

在 Ubuntu 22.04 上用 Docker Compose 部署 Ever Gauzy v111.48.2(API+Web+PostgreSQL 三服务):镜像选型、.env 环境变量表、持久化目录、down/up 重启恢复闭环实测(约 20 秒全恢复、数据/账号存活)、等价 docker run 与常用运维命令,全部命令与回显实测。

Ever GauzyEver GauzyDockerDocker Compose自托管2026/10/23

Ever Gauzy 111.48.2 Docker Compose 部署指南

目标:在一台 Ubuntu 22.04 服务器上用 Docker Compose 完整部署 Ever Gauzy v111.48.2(API + Web 前端 + PostgreSQL 三件套),覆盖镜像选型、环境变量、数据持久化、重启恢复闭环与常用运维操作。全文命令与回显均已在 Ubuntu 22.04.5 LTS(Docker Engine 29.8.1 / Docker Compose v5.5.1)环境实测通过,实测版本号 v111.48.2(commit 5b3f778e1d12f21992b39b42218fd2fd354c21d6,由 GET /api/version 返回确认)。

一、环境信息

本节列出实测部署所用的软硬件与组件版本,作为复现基准。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64,12 vCPU / 31 GiB 内存)
  • Docker Engine:29.8.1
  • Docker Compose:v5.5.1(docker compose v2 插件语法)
  • Ever Gauzy:v111.48.2(ghcr.io/ever-co/gauzy-api:latest + ghcr.io/ever-co/gauzy-webapp:latest)
  • 数据库:PostgreSQL 17(postgres:17-alpine)
  • 服务端口:API 8144(容器内 3000)、Web 前端 8147(容器内 4200)
  • 数据目录:/opt/ever-gauzy/pgdata(数据库文件)、/opt/ever-gauzy/uploads(上传附件),均为 bind mount 持久化
  • Compose 项目名:evergauzy-111.48.2-stack

💡 说明:Ever Gauzy 是开源 ERP/HRM/ATS/时间追踪平台(认知与选型详见 《Ever Gauzy 111.48.2 介绍与选型指南》),平台由 NestJS 后端 API、Angular Web 前端与 PostgreSQL 数据库组成,天然适合 Compose 三服务编排。

二、镜像选型说明

Ever Gauzy 官方在 GitHub Container Registry(ghcr.io)发布预构建镜像,无需本地编译。实测三镜像的体积与角色如下:

docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' | grep -Ei 'gauzy|postgres:17-alpine'

实测输出:

ghcr.io/ever-co/gauzy-webapp:latest     157MB
ghcr.io/ever-co/gauzy-api:latest        3.1GB
postgres:17-alpine      424MB
镜像角色体积(实测)说明
ghcr.io/ever-co/gauzy-apiAPI 后端(NestJS)3.1GB内置 Node.js v24.16.0 运行时、编译产物 main.js 与全部 node_modules,入口 node main.js
ghcr.io/ever-co/gauzy-webappWeb 前端(nginx)157MBAngular 构建静态资源 + nginx,nginx 同时把 /api、/graphql 反代到 API 容器
postgres:17-alpine数据库424MBGauzy 生产推荐 PostgreSQL;alpine 变体体积小

选型要点:

  1. API 与 Web 分镜像:官方将前后端拆成两个镜像,前端只依赖 API_BASE_URL 指向的 API 地址,二者通过同一宿主机端口对(本文 8144/8147)配合。
  2. 数据库类型可选:Gauzy 支持 better-sqlite3(默认,仅评估用)、postgres、mysql(DB_TYPE 控制),生产一律选 postgres,本文采用 PostgreSQL 17。
  3. 版本固定:官方镜像以 latest 滚动发布,生产建议在 docker-compose.yml 中将 image: 改为具体版本 tag 或 digest 固定;本文部署时 latest 对应的实测版本为 v111.48.2(已通过 API 版本端点核实)。另有 docker-compose.demo.yml 形态可一键拉起含演示数据的完整环境,适合快速体验。

三、前置条件

部署前确认服务器满足以下条件:

  1. 已安装 Docker Engine 与 Compose 插件,且当前用户在 docker 组(或使用 sudo)。验证:
docker version --format 'Engine={{.Server.Version}}'

实测输出:

Engine=29.8.1
  1. 端口 8144、8147 未被占用。验证(无输出即空闲):
ss -ltn | grep -E ':8144|:8147'

实测输出(空闲时为空,占用时会列出监听行):

(无输出 = 空闲可用)
  1. 内存建议 ≥ 4 GiB。实测三容器稳态内存占用很小(见下表),但 API 首次启动建库、运行迁移会短暂冲高:
docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}' evergauzy-111-web evergauzy-111-api evergauzy-111-pg

实测输出:

evergauzy-111-web       3.785MiB / 31.3GiB      0.00%
evergauzy-111-api       446MiB / 31.3GiB        0.00%
evergauzy-111-pg        31.23MiB / 31.3GiB      0.00%

💡 说明:Web 前端是 nginx 静态服务,内存仅约 4 MiB;API 是常驻 Node 进程,空载约 450 MiB;PostgreSQL 空载约 31 MiB。三服务合计不足 500 MiB,小型团队(数十人规模)2 GiB 内存的服务器即可承载。

  1. 可访问 ghcr.io 拉取镜像(首次部署需下载约 3.7 GB)。拉取缓慢时可为 Docker 守护进程配置 HTTP 代理后再 docker compose pull。

四、部署步骤

本章按「目录 → .env → docker-compose.yml → 启动 → 验证」的顺序从零部署。全部文件集中在一个 Compose 项目目录中管理。

4.1 创建项目目录与 .env

创建项目目录与两个持久化子目录:

mkdir -p /opt/ever-gauzy/stack /opt/ever-gauzy/pgdata /opt/ever-gauzy/uploads
cd /opt/ever-gauzy/stack

编写 .env 文件(Compose 会自动加载该文件做变量插值)。生产密钥用 openssl rand -hex 64 逐个生成,实测生成的四个密钥如下(本实验环境实测值,正式部署请重新生成自己的密钥):

openssl rand -hex 64

实测输出(示例,本环境实际采用的值):

b24477a8eb51f1088d234377dd515c6bcc8a8707200d4be58a07d31bc68a2d66afc4a670015e744da5cdfbc363e9b2789a47c9905656c13bb6bff87bd976f4e5

将以下内容写入 /opt/ever-gauzy/stack/.env:

NODE_ENV=production
IS_DOCKER=true
APP_NAME=Gauzy
DEMO=false
ALLOW_SUPER_ADMIN_ROLE=true
API_HOST=api
API_PORT=3000
WEB_HOST=webapp
WEB_PORT=4200
API_BASE_URL=http://localhost:8144
CLIENT_BASE_URL=http://localhost:8147
DB_ORM=typeorm
DB_TYPE=postgres
DB_HOST=db
DB_PORT=5432
DB_NAME=gauzy
DB_USER=postgres
DB_PASS=q1w2e3r4
DB_SSL_MODE=false
FILE_PROVIDER=LOCAL
JWT_SECRET=b24477a8eb51f1088d234377dd515c6bcc8a8707200d4be58a07d31bc68a2d66afc4a670015e744da5cdfbc363e9b2789a47c9905656c13bb6bff87bd976f4e5
JWT_REFRESH_TOKEN_SECRET=38f1ee38ad9c19ca4781db5be97e252cf748df9bf318888d150b856339cd98fa657a407af17c63eb8661886760f945cc055d8b41cc93d0b8d1758daae6a9f13a
JWT_VERIFICATION_TOKEN_SECRET=fae51bd89a16a25ca43191fa1a3a8462067369837a86c53500275e059f10f74fd0ba4ac099b662f7fb2375817e06944b24351a11c72dff571442ed040c4bcdc9
EXPRESS_SESSION_SECRET=b16c76bdb4f2fdd1c334bcd135477b3f1479f413a7fbac373327eb166ccdcadc1303d02fd9afc05ade83f7379a362f6eeb9e89db90e8c8d3e99cfec91f14afa6
DEMO_SUPER_ADMIN_EMAIL=admin@ever.co
DEMO_SUPER_ADMIN_PASSWORD=q1w2e3r4
DEMO_ADMIN_EMAIL=local.admin@ever.co
DEMO_ADMIN_PASSWORD=q1w2e3r4
DEMO_EMPLOYEE_EMAIL=employee@ever.co
DEMO_EMPLOYEE_PASSWORD=q1w2e3r4
MAIL_FROM_ADDRESS=gauzy@ever.co
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=
MAIL_PASSWORD=
DEFAULT_CURRENCY=USD
DEFAULT_COUNTRY=US
THROTTLE_ENABLED=true
THROTTLE_TTL=60000
THROTTLE_LIMIT=60000
TRUST_PROXY=1
AUTH_MAX_FAILED_ATTEMPTS=10
AUTH_LOCKOUT_SECONDS=900

⚠️ 注意:JWT_SECRET 等四个安全密钥在生产模式(NODE_ENV=production 且 DEMO!=true)下不能为空或保留公开默认值,API 启动时会强校验并拒绝启动,这是官方刻意的安全设计;务必用 openssl rand -hex 64 重新生成。DB_PASS 与三个 DEMO_*_PASSWORD 为本文演示口令,正式部署请替换为强口令。

4.2 编写 docker-compose.yml

将以下内容写入 /opt/ever-gauzy/stack/docker-compose.yml(与实测部署完全一致的配置;显式声明项目名,避免多套部署互相干扰):

name: evergauzy-111.48.2-stack

services:
  db:
    image: postgres:17-alpine
    container_name: evergauzy-111-pg
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_NAME:-gauzy}
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASS:-q1w2e3r4}
    volumes:
      - /opt/ever-gauzy/pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} || exit 1']
      interval: 10s
      timeout: 5s
      retries: 12
    networks:
      - evergauzy-net

  api:
    image: ghcr.io/ever-co/gauzy-api:latest
    container_name: evergauzy-111-api
    restart: on-failure
    environment:
      NODE_ENV: ${NODE_ENV:-production}
      IS_DOCKER: ${IS_DOCKER:-true}
      APP_NAME: ${APP_NAME:-Gauzy}
      DEMO: ${DEMO:-false}
      ALLOW_SUPER_ADMIN_ROLE: ${ALLOW_SUPER_ADMIN_ROLE:-true}
      API_HOST: ${API_HOST:-api}
      API_PORT: ${API_PORT:-3000}
      API_BASE_URL: ${API_BASE_URL:-http://localhost:8144}
      CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:8147}
      FILE_PROVIDER: ${FILE_PROVIDER:-LOCAL}
      DB_ORM: ${DB_ORM:-typeorm}
      DB_TYPE: ${DB_TYPE:-postgres}
      DB_HOST: ${DB_HOST:-db}
      DB_PORT: ${DB_PORT:-5432}
      DB_NAME: ${DB_NAME:-gauzy}
      DB_USER: ${DB_USER:-postgres}
      DB_PASS: ${DB_PASS:-q1w2e3r4}
      DB_SSL_MODE: ${DB_SSL_MODE:-false}
      JWT_SECRET: ${JWT_SECRET}
      JWT_REFRESH_TOKEN_SECRET: ${JWT_REFRESH_TOKEN_SECRET}
      JWT_VERIFICATION_TOKEN_SECRET: ${JWT_VERIFICATION_TOKEN_SECRET}
      EXPRESS_SESSION_SECRET: ${EXPRESS_SESSION_SECRET}
      DEMO_SUPER_ADMIN_EMAIL: ${DEMO_SUPER_ADMIN_EMAIL}
      DEMO_SUPER_ADMIN_PASSWORD: ${DEMO_SUPER_ADMIN_PASSWORD}
      DEMO_ADMIN_EMAIL: ${DEMO_ADMIN_EMAIL}
      DEMO_ADMIN_PASSWORD: ${DEMO_ADMIN_PASSWORD}
      DEMO_EMPLOYEE_EMAIL: ${DEMO_EMPLOYEE_EMAIL}
      DEMO_EMPLOYEE_PASSWORD: ${DEMO_EMPLOYEE_PASSWORD}
      MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-gauzy@ever.co}
      MAIL_HOST: ${MAIL_HOST:-smtp.gmail.com}
      MAIL_PORT: ${MAIL_PORT:-465}
      MAIL_USERNAME: ${MAIL_USERNAME:-}
      MAIL_PASSWORD: ${MAIL_PASSWORD:-}
      DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD}
      DEFAULT_COUNTRY: ${DEFAULT_COUNTRY:-US}
      THROTTLE_ENABLED: ${THROTTLE_ENABLED:-true}
      THROTTLE_TTL: ${THROTTLE_TTL:-60000}
      THROTTLE_LIMIT: ${THROTTLE_LIMIT:-60000}
      TRUST_PROXY: ${TRUST_PROXY:-1}
      AUTH_MAX_FAILED_ATTEMPTS: ${AUTH_MAX_FAILED_ATTEMPTS:-10}
      AUTH_LOCKOUT_SECONDS: ${AUTH_LOCKOUT_SECONDS:-900}
    entrypoint: './entrypoint.compose.sh'
    command: ['node', 'main.js']
    depends_on:
      db:
        condition: service_healthy
    ports:
      - '8144:3000'
    volumes:
      - /opt/ever-gauzy/uploads:/srv/gauzy/apps/api/public
    networks:
      - evergauzy-net

  webapp:
    image: ghcr.io/ever-co/gauzy-webapp:latest
    container_name: evergauzy-111-web
    restart: on-failure
    environment:
      NODE_ENV: ${NODE_ENV:-production}
      WEB_HOST: ${WEB_HOST:-webapp}
      WEB_PORT: ${WEB_PORT:-4200}
      API_HOST: ${API_HOST:-api}
      API_PORT: ${API_PORT:-3000}
      API_BASE_URL: ${API_BASE_URL:-http://localhost:8144}
      CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:8147}
      DEMO: ${DEMO:-false}
      DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD}
    entrypoint: './entrypoint.compose.sh'
    command: ['nginx', '-g', 'daemon off;']
    depends_on:
      db:
        condition: service_healthy
      api:
        condition: service_started
    ports:
      - '8147:4200'
    networks:
      - evergauzy-net

networks:
  evergauzy-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.67.0/24

配置要点逐项说明:

  • name::显式 Compose 项目名,down 等操作只影响本项目,避免与其他部署同名互删。
  • db 服务:POSTGRES_* 三变量初始化数据库;healthcheck 用 pg_isready 就绪探测,供 api 以 condition: service_healthy 精准等待,避免 API 先于数据库启动导致连接失败。
  • api 服务:entrypoint.compose.sh 是镜像自带的等待脚本(内部用 docker-compose-wait 等 DB_HOST:DB_PORT 可连后再 exec "$@",见 4.4 启动日志);DB_TYPE=postgres 切换生产数据库;上传目录 bind mount 到镜像的 /srv/gauzy/apps/api/public。
  • webapp 服务:nginx 监听容器内 4200,静态托管 Angular 产物并把 API 请求反代到 api:3000;映射到宿主 8147。
  • API_BASE_URL / CLIENT_BASE_URL:浏览器侧访问地址,端口必须与 ports: 映射的宿主端口一致(8144/8147),否则前端调不到 API。
  • networks 显式子网:固定 10.89.67.0/24 避免与服务器上其他 Docker 网段冲突,多套环境部署时各选一个空闲网段。

4.3 启动

首次启动(镜像已在本地时):

cd /opt/ever-gauzy/stack
docker compose up -d

实测输出(数据库就绪后 API 才启动,Web 最后启动):

 Container evergauzy-111-pg Healthy
 Container evergauzy-111-api Starting
 Container evergauzy-111-api Started
 Container evergauzy-111-pg Waiting
 Container evergauzy-111-pg Healthy
 Container evergauzy-111-web Starting
 Container evergauzy-111-web Started

实测 up -d 命令耗时 11.64s(镜像已拉取的二次部署;首次部署需先完成约 3.7 GB 镜像下载)。若镜像未拉取,先执行 docker compose pull。

API 容器启动日志节选(等待数据库 → exec 主进程):

+ export 'WAIT_HOSTS=db:5432'
+ ./wait
[INFO  wait] --------------------------------------------------------
[INFO  wait]  docker-compose-wait 2.9.0
[INFO  wait] ---------------------------
[DEBUG wait] Starting with configuration:
[DEBUG wait]  - Hosts to be waiting for: [db:5432]
[INFO  wait] Checking availability of host [db:5432]
[INFO  wait] Host [db:5432] is now available!
[INFO  wait] docker-compose-wait - Everything's fine, the application can now start!

API 就绪日志(启动约 25 秒后;API 会自动对空库执行 ORM 建表,首次启动生成 290 张业务表):

[Nest] 1  - 10/23/2026, 2:41:22 AM     LOG [NestApplication] Nest application successfully started +8ms

4.4 验证

依次验证容器状态、API、Web 与账号登录:

docker compose ps --format 'table {{.Name}}\t{{.Service}}\t{{.Status}}'

实测输出:

NAME                SERVICE   STATUS
evergauzy-111-api   api       Up 14 seconds
evergauzy-111-pg    db        Up 25 seconds (healthy)
evergauzy-111-web   webapp    Up 14 seconds

API 版本与健康检查:

curl -s http://localhost:8144/api/version

实测输出:

{"name":"api","version":"v111.48.2","commit":"5b3f778e1d12f21992b39b42218fd2fd354c21d6"}
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8144/api/health

实测输出:

200

/api/health 的详细结果可在 API 日志中看到,数据库/存储/缓存三项全部 up:

Health check result: {"status":"ok","info":{"database":{"status":"up"},"storage":{"status":"up"},"cache":{"status":"up","cacheType":"memory"}},"error":{},"details":{"database":{"status":"up"},"storage":{"status":"up"},"cache":{"status":"up","cacheType":"memory"}}}

Web 前端首页:

curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8147/

实测输出:

200

超级管理员登录(DEMO_SUPER_ADMIN_EMAIL/PASSWORD 在首启时写入数据库,实测登录返回用户对象与 JWT):

curl -s -X POST http://localhost:8144/api/auth/login -H 'Content-Type: application/json' \
  -d '{"email":"admin@ever.co","password":"q1w2e3r4"}'

实测输出(节选):

{"user":{"deletedAt":null,"createdAt":"2026-10-23T01:18:09.420Z",...,"firstName":"Super","lastName":"Admin","email":"admin@ever.c...

💡 说明:DEMO=false 时平台只初始化超级管理员账号(来自 DEMO_SUPER_ADMIN_EMAIL/PASSWORD),不灌入演示员工/项目/时间记录等业务数据,适合作为干净的生产起点。浏览器访问 http://服务器IP:8147/ 用该账号即可进入管理界面。

五、数据持久化说明

Ever Gauzy 的全部业务数据落两处 bind mount,宿主机目录即数据目录:

宿主目录容器内路径内容
/opt/ever-gauzy/pgdata/var/lib/postgresql/data(db)PostgreSQL 全部数据文件
/opt/ever-gauzy/uploads/srv/gauzy/apps/api/public(api)头像/附件等上传文件

数据库目录实测体积(首启建表 + 种子账号后):

du -sh /opt/ever-gauzy/pgdata

实测输出:

115M    /opt/ever-gauzy/pgdata

重启恢复闭环实测(bind mount 数据在容器删除重建后依然保留)。执行完整的 down(删容器+网络)→ up -d(重建):

cd /opt/ever-gauzy/stack
docker compose down

实测输出:

 Container evergauzy-111-pg Removed
 Network evergauzy-111482-stack_evergauzy-net Removing
 Network evergauzy-111482-stack_evergauzy-net Removed

实测 down 耗时 4.28s。随后重建:

docker compose up -d

实测 up -d 耗时 11.64s,恢复轮询(Web 返回 200 且 API 健康检查 200)在 up 返回后 3s 内达成——从发出 down 到全服务恢复约 20 秒。

数据存活验证一:重启前登录过的 admin@ever.co 账号重启后重新登录成功(账号数据存活);验证二:重启前通过 API 创建的任务重启后计数仍为 1(业务数据存活):

curl -s "http://localhost:8144/api/tasks/count?organizationId=8426a8c4-92f6-4fed-9da4-769b33288367" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出:

1

结论:pgdata 与 uploads 两个 bind mount 目录即是全部持久化状态,备份这两个目录(或用 pg_dump 逻辑备份)即可整体备份平台。数据库逻辑备份实测:

docker exec evergauzy-111-pg pg_dump -U postgres -d gauzy > /opt/ever-gauzy/gauzy-backup.sql

实测备份产物约 3.4 MB(3365059 字节,仅含种子数据与结构)。

六、环境变量表

Compose 部署的配置全部经 .env → 容器环境变量注入。下表为部署相关核心变量(实测取值来自本环境):

变量实测取值说明
NODE_ENVproduction运行模式;production 下强校验安全密钥
IS_DOCKERtrue声明运行于镜像环境,影响路径与行为分支
DEMOfalse是否演示模式;false 只种超级管理员账号
ALLOW_SUPER_ADMIN_ROLEtrue允许超级管理员角色(自托管必需)
API_BASE_URLhttp://localhost:8144API 对外地址(浏览器可达),须与端口映射一致
CLIENT_BASE_URLhttp://localhost:8147Web 对外地址,邮件里的链接也用它
DB_TYPEpostgres数据库类型(sqlite/mysql/postgres 可选)
DB_ORMtypeormORM 层(typeorm/mikro-orm 可选)
DB_HOST / DB_PORTdb / 5432数据库地址(Compose 服务名)/端口
DB_NAME / DB_USER / DB_PASSgauzy / postgres / q1w2e3r4库名/账号/口令(演示口令,请替换)
DB_SSL_MODEfalse数据库 SSL 开关
JWT_SECRET 等 4 个密钥openssl rand -hex 64 生成访问令牌/刷新令牌/验证令牌/会话签名密钥
DEMO_SUPER_ADMIN_EMAIL / PASSWORDadmin@ever.co / q1w2e3r4首启创建的超级管理员
DEMO_ADMIN_EMAIL / DEMO_EMPLOYEE_EMAILlocal.admin@ever.co / employee@ever.co预留的管理员/员工演示账号
FILE_PROVIDERLOCAL文件存储方式(本地目录/S3 等)
MAIL_HOST / MAIL_PORTsmtp.gmail.com / 465发件 SMTP;不配则邮件功能不可用
DEFAULT_CURRENCY / DEFAULT_COUNTRYUSD / US新组织的默认币种/国家
THROTTLE_ENABLED / THROTTLE_TTL / THROTTLE_LIMITtrue / 60000 / 60000API 限流开关/窗口 ms/窗口内次数
TRUST_PROXY1信任的反向代理跳数,取真实客户端 IP
AUTH_MAX_FAILED_ATTEMPTS / AUTH_LOCKOUT_SECONDS10 / 900登录失败锁定阈值/锁定时长秒
API_PORT / WEB_PORT3000 / 4200容器内监听端口(与宿主映射解耦)

💡 说明:Gauzy 的完整变量集有 1000 余项(覆盖集成、AI、支付、Redis 队列等),上表仅收录本部署形态实际注入生效的部分,其余变量保持官方默认即可满足本部署形态。

七、配置参数说明

本节解释几个最容易踩错的关键参数:

  1. API_BASE_URL/CLIENT_BASE_URL 与端口映射强绑定:二者是「浏览器视角」的地址。本部署把 API 映射到宿主 8144、Web 映射到 8147,故分别写 http://localhost:8144 与 http://localhost:8147。若部署到公网域名,应改为 https://gauzy.example.com 形式,否则前端页面调 API 会跨域失败、邮件里的确认链接也指错地址。
  2. DEMO 与种子数据:DEMO=false 时首启只建超级管理员;DEMO=true 会灌入完整演示数据集(员工、项目、时间记录等),适合快速体验但生产禁用。
  3. 安全密钥强校验:NODE_ENV=production 且 DEMO!=true 时,四个密钥为空或为公开默认值会让 API 拒绝启动(防弱密钥上生产)。这是官方在 .env.sample 中明文声明的设计,不是故障。
  4. DB_TYPE=postgres 依赖链:Gauzy 默认 better-sqlite3(单文件,仅评估用)。生产走 PostgreSQL 时,ORM 在首次启动自动完成建表(实测生成 290 张表),无需手工执行迁移脚本。
  5. 限流参数:THROTTLE_TTL=60000(60 秒窗口)+ THROTTLE_LIMIT=60000(窗口内最多 60000 次)是宽松默认,按团队规模调小 THROTTLE_LIMIT 可抗滥用。
  6. TRUST_PROXY=1:API 置于 nginx 等反代后时必须设置,否则限流与审计记录到的是代理 IP。

八、配置文件挂载

本部署形态下各服务的配置来源如下:

  • 数据库初始化参数:经 environment 注入(POSTGRES_DB/USER/PASSWORD),无额外配置文件。
  • API 配置:全部来自环境变量,无挂载的配置文件;仅需挂载上传目录 /opt/ever-gauzy/uploads:/srv/gauzy/apps/api/public(FILE_PROVIDER=LOCAL 时文件落这里)。
  • Web(nginx)配置:镜像内置,其模板按 API_BASE_URL 等环境变量在启动时渲染,无需自定义挂载;如需加 TLS,可在其上再放一层宿主 nginx/caddy 反代 8147。
  • .env 文件:Compose 自动读取项目目录下的 .env 做变量插值;修改后执行 docker compose up -d 重建使变更生效。

九、等价 docker run

不用 Compose 时,以下三条命令等价于上述三服务(先建网络,再按依赖顺序启动)。生产仍建议用 Compose 统一管理:

docker network create --subnet 10.89.67.0/24 evergauzy-net

docker run -d --name evergauzy-111-pg --network evergauzy-net \
  -e POSTGRES_DB=gauzy -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=q1w2e3r4 \
  -v /opt/ever-gauzy/pgdata:/var/lib/postgresql/data \
  postgres:17-alpine

docker run -d --name evergauzy-111-api --network evergauzy-net -p 8144:3000 \
  -e NODE_ENV=production -e IS_DOCKER=true -e DEMO=false -e ALLOW_SUPER_ADMIN_ROLE=true \
  -e DB_TYPE=postgres -e DB_HOST=db -e DB_PORT=5432 -e DB_NAME=gauzy \
  -e DB_USER=postgres -e DB_PASS=q1w2e3r4 \
  -e API_BASE_URL=http://localhost:8144 -e CLIENT_BASE_URL=http://localhost:8147 \
  -e JWT_SECRET=${JWT_SECRET} -e JWT_REFRESH_TOKEN_SECRET=${JWT_REFRESH_TOKEN_SECRET} \
  -e JWT_VERIFICATION_TOKEN_SECRET=${JWT_VERIFICATION_TOKEN_SECRET} \
  -e EXPRESS_SESSION_SECRET=${EXPRESS_SESSION_SECRET} \
  -e DEMO_SUPER_ADMIN_EMAIL=admin@ever.co -e DEMO_SUPER_ADMIN_PASSWORD=q1w2e3r4 \
  -v /opt/ever-gauzy/uploads:/srv/gauzy/apps/api/public \
  ghcr.io/ever-co/gauzy-api:latest

docker run -d --name evergauzy-111-web --network evergauzy-net -p 8147:4200 \
  -e NODE_ENV=production -e API_HOST=api -e API_PORT=3000 \
  -e API_BASE_URL=http://localhost:8144 -e CLIENT_BASE_URL=http://localhost:8147 \
  ghcr.io/ever-co/gauzy-webapp:latest

⚠️ 注意:等价 docker run 没有健康检查联动,api 启动早于数据库就绪时会因连接失败退出重试(镜像策略为失败自动重启),最终仍能起来,但启动顺序不如 Compose 的 service_healthy 精准。${JWT_SECRET} 等值先在 shell 中导出后再执行。

十、常用运维命令

以下命令均在 /opt/ever-gauzy/stack 项目目录下执行(实测环境为 Docker Compose v5.5.1)。

查看服务状态:

docker compose ps

跟踪 API 日志:

docker compose logs -f api

停止/启动(保留容器与数据):

docker compose stop    # 实测耗时 0.59s
docker compose start   # 实测命令耗时 11.46s,全服务恢复约 15s

删除容器与网络(数据目录不受影响):

docker compose down    # 实测耗时 4.28s

修改 .env 后应用配置:

docker compose up -d   # 有配置变化的容器会被重建

升级镜像版本:

docker compose pull && docker compose up -d

进入数据库命令行:

docker exec -it evergauzy-111-pg psql -U postgres -d gauzy

逻辑备份与恢复:

docker exec evergauzy-111-pg pg_dump -U postgres -d gauzy > /opt/ever-gauzy/gauzy-backup.sql
cat /opt/ever-gauzy/gauzy-backup.sql | docker exec -i evergauzy-111-pg psql -U postgres -d gauzy

十一、常见问题

Q1:API 容器反复重启,日志报数据库连接失败? API 的等待脚本最多等 30 秒(docker-compose-wait 默认 Timeout before failure: 30 seconds)。若 PostgreSQL 初始化(大磁盘首格)超过该时长,API 会失败退出;镜像 restart: on-failure 会自动重试,通常下一轮即成功。若持续失败,检查 DB_HOST 是否为服务名 db、DB_PASS 是否与 db 服务的 POSTGRES_PASSWORD 一致。

Q2:浏览器打开 8147 页面正常,但登录/数据请求全部失败? 检查 API_BASE_URL:它是「浏览器」发起 API 请求的目标地址。若写成了容器内地址或未映射的端口(如 http://api:3000、http://localhost:3000),页面就会调不通。必须与 ports: 的宿主映射一致(本文为 http://localhost:8144,公网部署换成对应域名)。

Q3:容器起不来,日志提示安全密钥校验失败? NODE_ENV=production 且 DEMO=false 时,JWT_SECRET、JWT_REFRESH_TOKEN_SECRET、JWT_VERIFICATION_TOKEN_SECRET、EXPRESS_SESSION_SECRET 任一为空或为公开默认值都会被拒绝启动。用 openssl rand -hex 64 重新生成四把密钥写入 .env 后 docker compose up -d。

Q4:首次启动后登录页账号密码是什么? DEMO=false 时只有超级管理员:.env 中 DEMO_SUPER_ADMIN_EMAIL/DEMO_SUPER_ADMIN_PASSWORD(本文为 admin@ever.co / q1w2e3r4,生产务必替换强口令)。

Q5:端口 8144/8147 被占用怎么改? 同时改两处保持一致:docker-compose.yml 中对应服务的 ports 宿主端口,以及 .env 的 API_BASE_URL/CLIENT_BASE_URL,然后 docker compose up -d 重建。

Q6:调用 REST API 时带对了 Bearer Token 仍返回 403 Forbidden? Gauzy 多租户接口要求请求头携带租户 ID,且服务端按原始头大小写精确匹配 Tenant-Id(tenant-id/Tenant-Id 可,Tenant-ID 不行——HTTP 头本应大小写不敏感,这是该版本的实现细节)。实测对比:Tenant-Id 头通过校验,Tenant-ID 头返回 403 {"message":"Forbidden resource",...}。另外值须与 JWT 中的 tenantId 一致。

十二、参考链接