Talkyard v1.2026.006 配置参数详解

Talkyard v1.2026.006 自托管配置四层模型实测详解:.env、docker-compose、play-framework.conf、Nginx server 块的参数含义、${?ENV} 覆盖机制、app 容器重建生效规则、生产配置模板与常见问题。

TalkyardTalkyardScalaPostgreSQLRedisElasticsearchDocker2026/11/02

Talkyard v1.2026.006 配置参数详解

Talkyard 自托管生产部署(talkyard-prod-one 仓库 ty-prod-one-v1 分支)的配置分散在 .env、docker-compose.yml、docker-compose.override.yml、conf/app/play-framework.conf、conf/web/sites-enabled/talkyard-servers.conf 与 secrets/ 多个层面,且 app 侧支持 ${?ENV} 环境变量覆盖机制。本文以实测运行的 v1.2026.006-fa49d1cee 环境为基准,逐类讲清每个参数的位置、含义、生效方式与生产取值。部署步骤见 Talkyard v1.2026.006 Docker Compose 部署指南,选型背景见 Talkyard 介绍与选型指南。

一、环境信息

本文全部回显均来自下表环境的实测,非文档转述。

项目值
宿主机Ubuntu 24.04.5 LTS
Docker29.9.2
Docker Composev5.6.1
Talkyard 版本v1.2026.006-fa49d1cee(RELEASE_BRANCH=tyse-v1-regular)
部署目录/opt/data/quzuo/tmp/talkyard/talkyard-v1(官方生产惯例为 /opt/talkyard-v1)
Compose 项目名talkyard-v1-2026-006-stack
访问入口http://localhost:8139(web 容器端口映射 8139:80,容器内监听 80)
后端组件PostgreSQL 18.6 / Redis 8.6.1 / Elasticsearch 9.3.1 / OpenResty 1.29.2.5 / Play 2.9.12(Scala 2.13.18,Temurin 11.0.30)

栈运行状态实测:

$ docker compose ps --format '{{.Service}}\t{{.Status}}'
app	Up 26 minutes (healthy)
cache	Up 42 minutes (healthy)
egressp	Up 42 minutes (healthy)
rdb	Up 42 minutes (healthy)
rendr	Up 42 minutes
search	Up 42 minutes (healthy)
web	Up 42 minutes (healthy)
$ curl -s -o /dev/null -w 'http_code=%{http_code} time=%{time_total}s\n' http://localhost:8139/
http_code=200 time=0.012596s

解读:7 个常驻服务中 6 个带 healthcheck 且均 healthy;rendr 无健康检查状态是因为该服务当前是 sleep infinity 占位进程(Deno SSR 预留位),属正常形态。HTTP 200 说明 web→app 转发链路配置正确。

组件版本实测回显:

$ docker compose exec web nginx -v
nginx version: openresty/1.29.2.5
$ docker compose exec cache redis-server --version
Redis server v=8.6.1 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=16af4e2dbf02df6b

解读:网关并非普通 nginx 而是 OpenResty(内置 Lua JIT),这是 talkyard-servers.conf 里能直接写 content_by_lua_block、ssl_certificate_by_lua_block 做 ACME 自动证书的前提;Redis 8.6.1 使用 jemalloc 分配器。

二、前置条件

阅读并实操本文需要:

  • 已按 部署指南 完成 talkyard-prod-one 栈的 docker compose up -d,7 服务运行正常;
  • 终端位于部署目录(本文示例均假定 cd 到 talkyard-v1 目录,或等效加 -p talkyard-v1-2026-006-stack);
  • 具备 docker compose、Nginx、Play Framework 配置语法的基础知识;
  • 若修改参数,需了解本文 3.3 节"app 容器必须删除重建"的生效规则,这是 Talkyard 配置排障中最高频的坑。

三、参数查看与修改方式

Talkyard 的配置不是单一文件,而是"compose 插值层 → 容器资源层 → 应用参数层 → 网关层"四层叠加,改哪层、怎么生效各不相同。

3.1 配置分层模型

文件作用域代表参数生效方式
.envdocker compose 插值与容器环境变量VERSION_TAG、网络子网/IP 槽位、TALKYARD_HOSTNAMEdocker compose up -d 重建受影响容器
docker-compose.yml服务定义、安全选项、资源限制(官方维护,一般不改)image 表达式、cap_drop、ulimits、secrets同上
docker-compose.override.yml本地覆盖端口映射、PLAY_HEAP_MEMORY_MB、ES_JAVA_OPTS、内存 limit同上
conf/app/play-framework.confapp(Play/Scala)业务参数hostname、smtp、featureFlags、postgresql.host修改后需删除并重建 app 容器
conf/web/sites-enabled/talkyard-servers.confNginx server 块、TLS、ACMElisten、backlog、ssl_certificate修改后需重载/重启 web 容器
secrets/postgres_password.txt数据库口令(compose secret)—挂载于容器 /run/secrets/postgres_password

解读:官方 compose 文件头部注释明确 "You can override this in docker-compose.override.yml",即升级时不要直接改 docker-compose.yml,把本地差异全部放进 override 文件,这样 upgrade-if-needed.sh 拉取新版本时不会冲突。

3.2 查看最终生效的配置

镜像 tag 由表达式 ${PINNED_VERSION_TAG:-${VERSION_TAG}} 决定,查看解析结果:

$ docker compose config --images
debiki/talkyard-search:v1.2026.006-fa49d1cee
debiki/talkyard-app:v1.2026.006-fa49d1cee
debiki/talkyard-rendr:v1.2026.006-fa49d1cee
debiki/talkyard-cache:v1.2026.006-fa49d1cee
debiki/talkyard-rdb:v1.2026.006-fa49d1cee
debiki/talkyard-egressp:v1.2026.006-fa49d1cee
debiki/talkyard-web:v1.2026.006-fa49d1cee

解读:PINNED_VERSION_TAG 留空时全部回退到 VERSION_TAG;把 PINNED_VERSION_TAG 设为某个具体 tag 即可"钉住"版本,自动升级脚本会跳过它——这是灰度/锁版本的核心开关。

查看 app 容器实际拿到的环境变量:

$ docker compose exec -T app sh -c 'env | grep -E "TALKYARD|BECOME|PLAY_HEAP|no_proxy|http_proxy" | sort'
BECOME_OWNER_EMAIL_ADDRESS=admin@talkyard.io
http_proxy=http://egressp:4750
no_proxy=''
PLAY_HEAP_MEMORY_MB=512
TALKYARD_HOSTNAME=localhost
TALKYARD_SECURE=false

解读:TALKYARD_*、BECOME_OWNER_EMAIL_ADDRESS 来自 .env,PLAY_HEAP_MEMORY_MB 来自 override;http_proxy 指向 egressp 出网代理是官方写死在 compose 里的(防 SSRF),no_proxy='' 表示 app 的一切外联都必须走代理。

查看 web 容器生效的网关参数:

$ docker compose exec -T web sh -c 'env | grep -E "^TY_|^CDN" | sort'
TY_MAX_AGE_15MIN=max-age=900, s-maxage=900, public
TY_MAX_AGE_DAY=max-age=86400, s-maxage=86400, public
TY_MAX_AGE_HOUR=max-age=3600, s-maxage=3600, public
TY_MAX_AGE_MONTH=max-age=2592000, s-maxage=2592000, public
TY_MAX_AGE_WEEK=max-age=604800, s-maxage=604800, public
TY_MAX_AGE_YEAR=max-age=2592000, s-maxage=2592000, public
TY_NGX_ERROR_LOG_LEVEL=info
TY_NGX_LIMIT_CONN_PER_IP=60
TY_NGX_LIMIT_CONN_PER_SERVER=10000
TY_NGX_LIMIT_RATE=50k
TY_NGX_LIMIT_RATE_AFTER=5m
TY_NGX_LIMIT_REQ_BODY_SIZE=25m
TY_NGX_LIMIT_REQ_PER_IP=30
TY_NGX_LIMIT_REQ_PER_IP_BURST=200
TY_NGX_LIMIT_REQ_PER_SERVER=200
TY_NGX_LIMIT_REQ_PER_SERVER_BURST=2000

解读:这组 TY_NGX_* 变量是镜像内置默认值(override 未设置也生效),涵盖限连、限流、请求体上限与 CDN 缓存时长;其中 TY_NGX_LIMIT_REQ_BODY_SIZE=25m 与 app 侧 talkyard.maxImportDumpBytes=25100100(约 25MB)配套,改导入上限时两处要一起改。

3.3 修改 app 参数:必须删除重建 app 容器

play-framework.conf 挂载进容器是只读的,且官方在文件内留下了明确注释:

# For changes to environment variables to take effect, the relevant container (typically
# the app container) needs to be deleted (docker compose rm app) and recreated.

即:仅 docker compose restart app 不够,要 docker compose rm 掉 app 容器再 up -d 重建(重建时 compose 才会重新注入新的环境变量/挂载)。验证参数是否真正生效,直接看 JVM 启动参数:

$ docker compose exec app ps -ef | grep -o 'Xms[0-9]*m -Xmx[0-9]*m'
-Xms512m -Xmx512m

解读:PLAY_HEAP_MEMORY_MB=512 被 app 入口脚本翻译成 -Xms512m -Xmx512m,证明 override 里的堆配置已生效。search 容器的 ES_JAVA_OPTS 是另一处独立配置,本实测环境恰好也取了 512m。

3.4 ${?ENV} 环境变量覆盖机制

play-framework.conf 尾部的 "From environment variables" 段使用 Play 的可选插值语法 ${?VAR}:环境变量存在则覆盖前面的字面值,不存在则保留字面值。实测环境 .env 只设置了三个变量,其余键仍取文件字面值。

play-framework.conf 键覆盖它的环境变量
talkyard.becomeOwnerEmailAddressBECOME_OWNER_EMAIL_ADDRESS
talkyard.hostnameTALKYARD_HOSTNAME
talkyard.portTALKYARD_PORT
talkyard.secureTALKYARD_SECURE
talkyard.createSiteHostnameCREATE_SITE_HOSTNAME
talkyard.maintenanceApiSecretTY_SYSMAINT_API_SECRET
talkyard.emailWebhooksApiSecretTY_EMAIL_WEBHOOKS_API_SECRET
talkyard.featureFlagsTY_FEATURE_FLAGS
play.http.secret.keyPLAY_SECRET_KEY
talkyard.e2eTestPassword / forbiddenPassword / mayFastForwardTimeE2E_TEST_PASSWORD / FORBIDDEN_PASSWORD / MAY_FAST_FORWARD_TIME(仅测试)

解读:这套机制让生产部署可以不改 play-framework.conf 本体,只在 .env 里集中管理站点域名、密钥与开关,配置文件随仓库升级无痛覆盖。

3.5 修改 web 网关参数

talkyard-servers.conf 以只读方式挂载到容器 /etc/nginx/sites-enabled(compose 中 ./conf/web/sites-enabled:/etc/nginx/sites-enabled:ro)。修改宿主文件后需让 nginx 重新加载配置或重启 web 容器;由于挂载是 ro,容器内改不动,必须在宿主机侧编辑。

四、参数总览

4.1 .env 核心变量

变量实测值说明
COMPOSE_PROJECT_NAMEtalkyard-v1-2026-006-stack卷名/网络名前缀,避免与同机其他栈冲突
DOCKER_REG_ORGdebiki镜像组织
RELEASE_BRANCHtyse-v1-regular自动升级读取的版本分支
VERSION_TAGv1.2026.006-fa49d1cee当前镜像 tag
PINNED_VERSION_TAG(空)非空则钉住版本、跳过自动升级
FE_PUB_NET_SUBNET 等 5 个10.89.60-65.0/24五张网络的子网(见 5.9)
FE_PUB_NET_WEB_IP 等 12 个10.89.x.y各容器固定 IP 槽位
TALKYARD_SECUREfalse是否强制 HTTPS
TALKYARD_HOSTNAMElocalhost站点域名
BECOME_OWNER_EMAIL_ADDRESSadmin@talkyard.io站长邮箱

解读:网络子网/IP 槽位全部变量化,是为了在同一台机器上并排跑多套 Talkyard 时不撞段;单实例生产可直接沿用官方默认值。

4.2 play-framework.conf 参数清单

键实测值/默认说明
talkyard.becomeOwnerEmailAddressadmin@talkyard.io首个以此邮箱注册的用户成为站长
talkyard.hostnamelocalhost站点地址
talkyard.port(注释掉)容器内监听端口,默认 80/9000 体系
talkyard.securefalsefalse 表示允许 HTTP
play.http.secret.key随机串(本文不展示)会话签名密钥,不改占位值则拒绝启动
talkyard.smtp.host / user / password""(空)SMTP 服务器,空则不发信
talkyard.smtp.fromAddresssupport@your-organization.com发件人
talkyard.smtp.port / requireStartTls587 / trueSTARTTLS 方案
talkyard.smtp.tlsPort / connectWithTls(注释)465 / true直连 TLS 方案,二者互斥
talkyard.smtp.checkServerIdentitytrue校验服务器证书
talkyard.googleApiKey / akismetApiKey""(空)Google reCAPTCHA / Akismet 反垃圾
talkyard.securityComplaintsEmailAddresssupport@example.com安全投诉邮箱
talkyard.maxImportDumpBytes25100100允许导入/恢复的 SQL dump 上限(约 25MB)
talkyard.mayImportSitefalse是否允许把其他 Talkyard 站点导入本机
talkyard.redis.hostcacheRedis 服务名
talkyard.nginx.hostwebNginx 服务名
talkyard.postgresql.host / port / database / userrdb / 5432 / talkyard / talkyard数据库连接(口令走 secret 文件)
talkyard.featureFlags""(空)功能开关串
talkyard.maxGroupMentionNotifications50群组 @ 提及通知上限
talkyard.createSiteHostname / cdnOrigin(注释)多站点创建 / CDN 源
talkyard.authn.{google,facebook,twitter,github}.*端点 URL 内置,clientID/Secret 注释第三方登录

解读:文件按 "Required settings → Email server → Spam detection → Backup and restore → Other parts → Advanced → From environment variables → Testing → Authentication" 组织,必填项只有邮箱、域名、secret.key 三处;其余都有可用默认值。

4.3 override 与 compose 资源参数(本实测环境)

参数实测值来源
web ports'8139:80'override(官方为 80:80 + 443:443)
app PLAY_HEAP_MEMORY_MB512override(官方 mem/4g.yml 为 1024)
search ES_JAVA_OPTS-Xms512m -Xmx512moverride
search 内存 limit1.6Goverride deploy.resources.limits.memory
search bootstrap.memory_lock / ulimits memlocktrue / -1官方 compose
cache sysctl net.core.somaxconn511官方 compose
日志 driverlocal官方 x-logging 锚点

解读:本实验环境把 4G 档再压缩(heap 512、ES 512、search 限 1.6G)以适配实验室内存预算;生产按官方 mem/ 档位或 6.2 节模板取值即可。

五、核心参数分类详解

5.1 站点身份与访问

talkyard.hostname、talkyard.port、talkyard.secure 决定应用生成绝对 URL 的方式;becomeOwnerEmailAddress 决定谁是站长。实测环境:

talkyard.hostname="localhost"
talkyard.secure=false

对应 .env 中 TALKYARD_SECURE=false、TALKYARD_HOSTNAME=localhost,因此直接以 http://localhost:8139 访问,返回正常:

$ curl -s -o /dev/null -w 'http_code=%{http_code} time=%{time_total}s\n' http://localhost:8139/
http_code=200 time=0.012596s

站长身份实测:用 becomeOwnerEmailAddress(admin@talkyard.io)对应的账号 tyadmin 登录后,/-/reply 响应中的用户摘要回显:

{"id":100,"username":"tyadmin","fullName":null,"isAuthenticated":true,"isAdmin":true}

解读:isAdmin:true 证明"先以站长邮箱注册者得站点"的机制生效。生产上 hostname 必须填真实域名、secure=true,否则 Cookie 的 Secure 属性与 HSTS 行为都不对。

5.2 密钥与会话安全

play.http.secret.key 是 Play 的会话签名密钥,官方注释原文:

# Replace "change_this" with say 80 random characters. The value is secret.
# The server will refuse to start until you've changed this.

即保留占位值时 app 直接拒绝启动;实测环境使用了一条约 80 字符的随机串,可用 PLAY_SECRET_KEY 环境变量注入而不落盘到 conf。

CSRF 防护使用 TyCoXsrf cookie + X-XSRF-TOKEN 请求头。实测登录成功回显:

{"weakSessionId":"","xsrfTokenIfNoCookies":""}

解读:两个字段为空串说明浏览器/客户端 cookie 正常、无弱会话告警。压测或 API 集成时必须先取 TyCoXsrf cookie 值再放入 X-XSRF-TOKEN 头,否则写操作会被网关拒绝。

5.3 SMTP 邮件与反垃圾

邮件段:smtp.host/user/password/fromAddress/port/requireStartTls/checkServerIdentity,或改用 tlsPort=465 + connectWithTls=true——官方注释明确 "don't try both at the same time"(两种 TLS 方案互斥)。实测环境 smtp.host="",即不配置外发通道,注册验证邮件、密码重置邮件都不会发出,仅适合内网测试。

反垃圾段:googleApiKey(reCAPTCHA)与 akismetApiKey(Akismet)留空即关闭对应检测;securityComplaintsEmailAddress 是展示给举报者的投诉邮箱。

实测邮箱格式校验(与 SMTP 无关,属应用层规则):用 .test 域名注册被拒:

422 Unprocessable Entity
Bad email address: Invalid email address [TyEBADEMLADR_-PWD]

解读:Talkyard 内置邮箱合法性校验(拒绝保留/无效 TLD),生产配置 SMTP 前,站长邮箱要选真实可达域名,否则创建站长这一步就会失败。

5.4 数据库、缓存与系统组件

talkyard.postgresql.host="rdb"、port=5432、database/user=talkyard 走 compose 服务名解析;口令不在 conf 里,官方注释:

# Password in docker secret:  /run/secrets/postgres_password

由 secrets/postgres_password 挂载注入,rdb 容器侧对应 POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password。talkyard.redis.host="cache"、talkyard.nginx.host="web" 同理指向服务名。

实测数据库与缓存:

$ docker compose exec rdb psql -U talkyard -d talkyard -c 'select version()'
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
$ docker compose exec cache redis-cli ping
PONG

解读:psql 实测还查到 shared_buffers=16384(8kB 页即 128MB),是小内存档 Postgres 容器的默认值;生产 4G+ 机器可评估上调。redis-cli ping 返回 PONG 说明 app↔cache 依赖健康。

备份/导入段:maxImportDumpBytes=25100100 限制后台可恢复的 SQL dump 大小(与 web 的 TY_NGX_LIMIT_REQ_BODY_SIZE=25m 配套);mayImportSite=false 关闭"把其他 Talkyard 站点导入本实例"的多站点能力,单站点生产保持默认即可。

5.5 功能开关与行为阈值

talkyard.featureFlags="" 是 Talkyard 的灰度功能开关串(形如 FlagA;FlagB),可通过 TY_FEATURE_FLAGS 环境变量注入而无需改文件;maxGroupMentionNotifications=50 限制一次群组提及触发的通知数量,防轰炸。运维类密钥 maintenanceApiSecret(TY_SYSMAINT_API_SECRET)与 emailWebhooksApiSecret(TY_EMAIL_WEBHOOKS_API_SECRET)只从环境变量读取,conf 中字面值为空。

Testing 段(e2eTestPassword、forbiddenPassword、mayFastForwardTime)仅端到端测试用,生产不要设置。

解读:featureFlags 是 Talkyard 特色——很多新特性先藏在 flag 后面,升级后功能"没出现"时先查这里,而不是找配置项。

5.6 第三方登录(OAuth)

talkyard.authn {} 块内置了 Google/Facebook/Twitter/GitHub 四家的 authorizationURL/accessTokenURL/scope,clientID 与 clientSecret 全部注释。启用任一渠道:在对应平台注册 OAuth App 回调填 https://<域名>/-/login-oauth-redirect?... 形态的官方端点,取消注释填入凭据,重建 app 容器。

解读:URL 端点内置意味着升级时不会因平台改版而漏配,只需补凭据;不启用第三方登录时该块完全不影响运行。

5.7 Nginx 网关参数

talkyard-servers.conf 含三个 server 块:

  1. HTTP server:listen 80 backlog=8192;/.well-known/acme-challenge 用 content_by_lua_block 调 resty.acme.autossl 应答 Let's Encrypt HTTP-01 挑战;默认 include server-limits.conf/server-locations.conf 直接服务 HTTP;注释掉的 return 302 https://... 模板用于强制跳转(官方建议 302 而非 301,留将来放开 HTTP 的余地)。
  2. HTTPS server:listen 443 ssl default_server backlog=8192 + http2 on;先加载自签兜底证书 /etc/nginx/generated/https-cert-self-signed-fallback.pem(注释原文:Required, or Nginx won't start),再由 ssl_certificate_by_lua_block 的 autossl 在首个 HTTPS 请求约 10 秒内换成 LE 证书。
  3. 通配符证书模板(整块注释):自备证书时 ssl_certificate /etc/certbot/live/... 的写法示例,可配合 go-acme/lego 容器定时取证书。

两条关键注释:

  • [BACKLGSZ]:backlog=8192 必须与 prepare-os.sh 设置的 net.core.somaxconn=8192 一致,且只能出现在一个 listen 上,否则 nginx 报 "duplicate listen options" 拒启。
  • [ipv6_probl]:宿主机禁用 IPv6 时保留 listen [::]:80 会报 [emerg] socket() [::]:80 failed (97: Address family not supported by protocol),默认已注释。

解读:web 容器的限流/缓存参数(TY_NGX_LIMIT_*、TY_MAX_AGE_*,见 4.3 实测回显)与 server 块分离——server 块管路由与 TLS,行为阈值走环境变量,改限流不需要动挂载文件。

5.8 Compose 资源与安全参数

镜像表达式 image: ${DOCKER_REG_ORG}/talkyard-app:${PINNED_VERSION_TAG:-${VERSION_TAG}} 统一了七个服务的版本来源。资源侧实测生效值:app PLAY_HEAP_MEMORY_MB=512、search ES_JAVA_OPTS='-Xms512m -Xmx512m' + deploy.resources.limits.memory: 1.6G;search 还有 bootstrap.memory_lock: 'true' 与 ulimits.memlock: -1(锁内存不换页)、nofile 65536;cache 容器 sysctls: net.core.somaxconn: 511。

安全侧全服务统一 security_opt: no-new-privileges=true + cap_drop: ALL,再按需 cap_add:web 加 CHOWN/DAC_OVERRIDE/SETGID/SETUID/NET_BIND_SERVICE(compose 注释逐条给出了不加会报的 nginx 错误原文);app 加 CHOWN/FOWNER/DAC_OVERRIDE/SETUID/SETGID(切换到 appuser/appgroup 与读取 secret);rdb 另加 DAC_READ_SEARCH/SYS_NICE;egressp 额外 read_only: true。日志统一 driver: local(自动轮转防写满磁盘)。backup 服务 profiles: [backup] 不随 up 启动,且 cpu_shares: 256、cpu_quota: 60000 / cpu_period: 100000(限 60% CPU,官方注释:否则小 VM 上内核可能 panic [100_kernel_panic])。

解读:这套 capability 白名单是 Talkyard 自托管的亮点之一——七个容器没有一个拥有 FULL 权限,排障时若见 Operation not permitted 优先核对 cap_add 而不是粗暴提权。

5.9 网络分区与 secrets

五张网络(名称自动加项目前缀):

网络internal成员用途
fe_pub_net否web面向互联网
fe_int_net是web、cache、appnginx 回源 Redis/app
be_int_net是app、rdb、search、rendr、backup应用与数据库
eg_int_net是app、egresspapp 到出网代理
eg_pub_net否egressp代理出互联网(webhook、链接预览)

子网与 IP 槽位全部来自 .env(10.89.60-65/24,web=10.89.60.21/61.21,app=10.89.61.41/62.41/63.41,rdb=10.89.62.61,search=10.89.62.71,egressp=10.89.63.99/65.99 等)。secrets 目前仅 postgres_password(backup_password 注释预留),app 侧读取路径固定 /run/secrets/postgres_password(compose 注释 [postgres_pw_path]:不可配置)。

解读:除 web 与 egressp 外没有任何容器能直接出网,app 的外联全部经 http_proxy=http://egressp:4750(smokescreen SSRF 代理)——这是"把论坛暴露到公网也不被内网探测"的结构性保证。

六、生产配置模板

6.1 生产 .env 模板(单域名,4GB 内存档)

COMPOSE_PROJECT_NAME=talkyard
DOCKER_REG_ORG=debiki
RELEASE_BRANCH=tyse-v1-regular
VERSION_TAG=v1.2026.006-fa49d1cee
PINNED_VERSION_TAG=
# 网络子网/IP 槽位沿用官方默认段(与本机其他容器不冲突即可)
TALKYARD_SECURE=true
TALKYARD_HOSTNAME=forum.example.com
BECOME_OWNER_EMAIL_ADDRESS=you@example.com
PLAY_SECRET_KEY=<openssl rand -base64 60 生成的随机串>

解读:相对实测环境只改三处语义——secure=true、真实域名、注入密钥;PINNED_VERSION_TAG 保持空以接受自动升级(升级机制见 CLI 篇)。4GB 档对应官方 mem/4g.yml(设 PLAY_HEAP_MEMORY_MB=1024)。

6.2 override 模板(实测文件全文,2GB 紧凑档)

# Lab overrides: based on official mem/4g.yml, plus port remap for testing.
# (Official mem/4g.yml sets PLAY_HEAP_MEMORY_MB=1024; here reduced to 512 to
# keep the JVM heap within the lab's 256-512 MB budget.)
services:
  web:
    ports:
      - '8139:80'
  app:
    environment:
      PLAY_HEAP_MEMORY_MB: 512
  search:
    environment:
      ES_JAVA_OPTS: '-Xms512m -Xmx512m'
    deploy:
      resources:
        limits:
          memory: 1.6G

解读:生产直接上 4G 档时,把 app 的 heap 改回 1024 并去掉 8139 端口重映射(恢复官方 80:80/443:443)即可;search 的 heap 与 limit 保持配套(limit ≥ heap 的 3 倍是实测 512/1.6G 的比例)。

6.3 内存预算实测

app 0.66% 1.159GiB/15.57GiB 7.45%
web 0.00% 33.08MiB 0.21%
cache 0.10% 13.89MiB 0.09%
rendr 0.00% 672KiB 0.00%
rdb 0.14% 116.8MiB 0.73%
search 0.31% 1.178GiB/1.6GiB 73.65%
egressp 0.00% 6.938MiB 0.04%

解读:docker stats --no-stream 实测整栈约 2.5GiB——heap 512M 的 JVM 实际 RSS 达 1.159GiB(堆外+Metaspace),search 顶在 1.6G limit 的 73.65%。因此 2GB 内存的机器跑不满配,生产建议 ≥4GB;卷占用实测 pg-data 73.06MB、es-data 109.2kB、web-generated 8.63kB、redis-data/pub-files/priv-files 0B(新站基线)。

七、常见问题

Q1:改了 .env 或 app 环境变量,重启容器后没生效? A:官方注释要求删除并重建 app 容器(docker compose rm 后 up -d),restart 不会重新注入变量。验证手段见 3.3 节:ps -ef | grep -o 'Xms[0-9]*m -Xmx[0-9]*m' 实测回显 -Xms512m -Xmx512m。

Q2:app 容器起不来,日志提到 secret key? A:play.http.secret.key 保留占位 change_this 时服务器拒绝启动(官方注释原文 "The server will refuse to start until you've changed this")。用 PLAY_SECRET_KEY 环境变量注入约 80 字符随机串即可。

Q3:compose 里写 TALKYARD_SECURE: '${TALKYARD_SECURE}' 后 app 崩溃? A:官方 compose 注释实测过该坑:映射语法会把变量置为空字符串,而 Play 要求 bool,直接拒启。必须用列表式透传(- TALKYARD_SECURE),让变量要么有值要么不出现。

Q4:nginx 报 "duplicate listen options for 0.0.0.0:80"? A:backlog=8192 出现在了两个 listen 上。[BACKLGSZ] 注释说明 backlog 只能指定在一处,且需与 prepare-os.sh 设置的 net.core.somaxconn=8192 一致。

Q5:宿主机禁用 IPv6 后 web 起不来? A:见 [ipv6_probl] 注释:[emerg] socket() [::]:80 failed (97: Address family not supported by protocol)。保持模板中 listen [::]:80 注释状态即可。

Q6:search 容器反复 OOMKilled? A:ES_JAVA_OPTS 堆与 deploy.resources.limits.memory 必须配套。实测 512m 堆配 1.6G limit 稳定(RSS 1.178GiB,73.65%);堆超过 limit 一半以上就会触发容器 OOM。

Q7:创建站长总被拒? A:实测四道校验的原始回显:用户名 <3 字符报 The username is too short; it must be at least 3 characters [DwE805T4];admin 是保留名报 Username is reserved: 'admin' [EdE5PKW01];密码 8 位报 Password too short, should be 10 chars [TyEPWMIN_];.test 邮箱报 Bad email address [TyEBADEMLADR_-PWD]。详见部署指南排障章节。

Q8:本地 HTTP 测试要不要配证书? A:不用。talkyard.secure=false + TALKYARD_SECURE=false 即可跑 HTTP;HTTPS server 块里的自签兜底证书保证 nginx 在没拿到 LE 证书前也能启动(见 5.7)。

八、参考链接

Talkyard v1.2026.006 配置参数详解 - Quzuo