ManageMeals 1.9.3 配置参数详解
ManageMeals api v1.9.3/web v1.7.3 全量配置面:后端 .env 样例 60 参数分类详解、前端 9 项与抓取器/同步器参数、compose 修改生效回滚闭环实测(JWT 30s 过期/分页 5 逐字回显)、生产模板与必改清单、FAQ(实测日期 2026-11-02)
ManageMeals 1.9.3 配置参数详解
目标:把 ManageMeals(manage-meals-api v1.9.3 / manage-meals-web v1.7.3,均 2026-09-25 发布)的全部可配置参数讲透——官方
.env样例共 60 个后端参数、前端 9 个、抓取器 9 个、依赖服务若干,逐项说明默认值、生效方式与自托管取舍,并用实测演示「改参数 → 重建 → 验证生效 → 回滚」的完整闭环。全部回显来自实测环境(Ubuntu 24.04.5 LTS / x86_64 / Docker 29.9.2 / Docker Compose v5.6.1,实测日期 2026-11-02)。栈怎么部署见站内文章 《ManageMeals Docker Compose 部署指南》,组件选型评估见 《ManageMeals 介绍与选型指南》。⚠️ 验收方式说明:本文参数行为验证全部通过「容器内
printenv+ HTTP API 实测回显 + 数据库字段抽查」完成;本次实测环境无浏览器自动化工具,未做 UI 层走查。
一、环境信息
本文假设你已按部署指南把 8 服务栈跑起来,以下为本实测环境的配置相关事实,读者对照替换。
| 项目 | 值 |
|---|---|
| 应用版本 | manage-meals-api v1.9.3、manage-meals-web v1.7.3(ghcr 仅发 :latest,用 digest 固定,见部署指南 2.2) |
| 配置载体 | 仅 docker-compose.yaml 的 environment: 段——官方镜像没有配置文件挂载点 |
| 参数权威来源 | 后端仓库 .env 样例(实测取回 60 键,见 2.2);前端/抓取器参数来自官方 compose 样例与源码 |
| Compose 项目 | manage-meals-v1.9.3-stack,目录内维护 compose 文件 |
| 演示账号 | admin@example.com / q1w2e3r4(API 直连测试用) |
| 数据库凭据 | MongoDB root / q1w2e3r4;Typesense API key q1w2e3r4;JWT 密钥 access-jwt-secret-q1w2e3r4 / refresh-jwt-secret-q1w2e3r4 |
| API 地址 | http://localhost:8148(容器内 3000);Web 地址 http://localhost:8146 |
💡 提示:本文所有示例凭据(
q1w2e3r4等)均为实验环境明文占位,生产环境必须换成强随机值,见「六、生产配置模板」。
二、前置条件
配置操作前确认三件事:栈在运行、样例文件在手、验证工具可用。
2.1 服务栈运行中
cd /opt/manage-meals && docker compose ps
NAME STATUS
manage-meals-v193-stack-manage-meals-api-1 Up
manage-meals-v193-stack-manage-meals-web-1 Up
manage-meals-v193-stack-mongo-1 Up
manage-meals-v193-stack-mongo-express-1 Up
manage-meals-v193-stack-recipe-scraper-1 Up
manage-meals-v193-stack-search-sync-1 Up
manage-meals-v193-stack-typesense-1 Up
manage-meals-v193-stack-valkey-1 Up
8 服务全部 Up 才能做参数生效验证。
2.2 后端全量参数样例文件
ManageMeals 后端仓库提供 .env 样例,列出了 API 认识的全部环境变量。实测取回并统计键数:
grep -c '=' api-.env.example
awk -F= '{print $1}' api-.env.example | sort | uniq -d
60
共 60 个键,无重复。raw.githubusercontent.com 在国内存在间歇超时(实测重试可成),拿不到时可直接看后端仓库 manage-meals-api 根目录的 .env 样例文件。
2.3 验证工具
curl:调 API 验证参数行为;python3(或jq):解析登录返回与解码 JWT 载荷,验证有效期类参数;docker compose exec <服务> printenv:确认注入值真正进入容器进程环境。
三、参数查看与修改方式
ManageMeals 的配置模型非常简单:环境变量是唯一入口。本章先确认这一点,再给出查看、修改、验证、回滚四步的标准操作。
3.1 确认没有配置文件挂载
官方镜像不含 app.yaml / nginx.conf 之类挂载点,配置全部经 environment: 注入。用容器内环境实测佐证——API 容器内环境变量总数只有 18 个(12 个注入项 + PATH/HOME 等 Docker 基础变量),镜像内没有预置 .env:
docker compose exec -T manage-meals-api printenv | grep -cE '.'
18
这意味着:.env 样例里的其余 48 个参数当前一个都没注入,API 完全靠代码内置默认值运行(这正是 5.x 各节「默认即可用」结论的来源)。
3.2 查看当前生效值
看 compose 声明了什么:
docker compose config | grep -A 20 'manage-meals-api'
看进程实际收到了什么(以此为准):
docker compose exec -T manage-meals-api printenv | grep -E '^(MONGO_|REDIS_|TYPESENSE_|ACCESS_|REFRESH_|AUTH_|RECIPE_|USER_)' | sort
ACCESS_JWT_SECRET=access-jwt-secret-q1w2e3r4
AUTH_ACCESS_TOKEN_EXPIRE_SEC=600
AUTH_REFRESH_TOKEN_EXPIRE_SEC=2629746
MONGO_DB=manage_meals
MONGO_URL=mongodb://root:q1w2e3r4@mongo:27017/
RECIPE_SCRAPER_URL=http://recipe-scraper:8000
REDIS_URL=redis://valkey:6379
REFRESH_JWT_SECRET=refresh-jwt-secret-q1w2e3r4
TYPESENSE_API_KEY=q1w2e3r4
TYPESENSE_HOST=typesense
TYPESENSE_PORT=8108
USER_REGISTER_ENABLED=true
Web 侧 9 项同样验证:
docker compose exec -T manage-meals-web printenv | grep -E '^(PUBLIC_|PASSWORD_|API_URL|COOKIE_|ORIGIN|BODY_SIZE)' | sort
API_URL=http://manage-meals-api:3000/v1
BODY_SIZE_LIMIT=Infinity
COOKIE_ACCESS_TOKEN=mmeals_access_token
COOKIE_ACCESS_TOKEN_EXPIRE_SEC=600
COOKIE_REFRESH_TOKEN=mmeals_refresh_token
COOKIE_REFRESH_TOKEN_EXPIRE_SEC=2629746
ORIGIN=http://localhost:8146
PASSWORD_MIN_LENGTH=6
PUBLIC_MAIN_TITLE=ManageMeals
💡 提示:
compose config显示的是「声明值」,printenv显示的是「进程值」。排查配置不生效时,两边各看一眼即可定位是 compose 没写对,还是服务没重建。
3.3 修改与生效:改 compose → up -d 指定服务
ManageMeals 没有热加载机制,改参数的标准动作是:编辑 compose 的 environment: → docker compose up -d <受影响服务>(Compose 检测到配置变化会自动 Recreated)→ printenv 确认 → 功能验证。下面用两个真实参数完整走一遍。
实验设计:把 AUTH_ACCESS_TOKEN_EXPIRE_SEC 从 600 改为 30(登录令牌 30 秒过期),并新增 ITEMS_PER_PAGE: 5(列表分页大小)。先采基线:
AT=$(curl -s -X POST http://localhost:8148/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"q1w2e3r4"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["accessToken"])')
python3 -c "
import base64,json
p=json.loads(base64.urlsafe_b64decode('$AT'.split('.')[1]+'=='))
print('JWT iat=%d exp=%d diff=%d'%(p['iat'],p['exp'],p['exp']-p['iat']))"
curl -s "http://localhost:8148/v1/recipes" -H "Authorization: Bearer $AT" \
| python3 -c 'import sys,json;d=json.load(sys.stdin);print("page=%s total=%s perPage=%s"%(d["page"],d["total"],d["perPage"]))'
JWT iat=1793559439 exp=1793560039 diff=600
page=1 total=1 perPage=20
基线:令牌有效期 600 秒、分页 20(未注入 ITEMS_PER_PAGE 时的代码默认值)。改 compose 后重建 API:
docker compose up -d manage-meals-api
Container manage-meals-v193-stack-manage-meals-api-1 Recreated
Container manage-meals-v193-stack-manage-meals-api-1 Starting
Container manage-meals-v193-stack-manage-meals-api-1 Started
验证生效——新登录令牌差值变为 30,列表 perPage 变为 5:
patched JWT iat=1793559462 exp=1793559492 diff=30
patched recipes page=1 total=1 perPage=5
docker compose exec -T manage-meals-api printenv | grep -E '^(AUTH_ACCESS|ITEMS)'
AUTH_ACCESS_TOKEN_EXPIRE_SEC=30
ITEMS_PER_PAGE=5
再用「等 32 秒后旧令牌必失效」验证过期语义真实生效:
立即调用:http=200
32s 后同令牌调用:
{"statusCode":401,"error":"Unauthorized","message":"Invalid Authorization header"}
http=401
💡 技术提示:
up -d不带服务名会检查全部服务,只重建配置有变化的;带服务名更快。改manage-meals-web的变量同理up -d manage-meals-web。另外注意USER_REGISTER_ENABLED: "true"这类布尔值在 compose 中要加引号,避免 YAML 布尔解析歧义。
3.4 回滚
改生产配置前先备份 compose,验证完用备份复原:
cp docker-compose.yaml /tmp/compose.bak # 修改前执行
cp /tmp/compose.bak docker-compose.yaml && docker compose up -d manage-meals-api
docker compose exec -T manage-meals-api printenv | grep AUTH_ACCESS
AUTH_ACCESS_TOKEN_EXPIRE_SEC=600
实测复原后重新登录 JWT diff=600、perPage=20,diff /tmp/compose.bak docker-compose.yaml 无差异,环境回到基线。
四、参数总览
4.1 manage-meals-api(60 项全量)
下表为 .env 样例全部 60 键的分类总览。「自托管必要性」列的判读口径:必配=最小部署必须注入;默认即可=不注入走内置默认且实测可用;条件=启用对应功能才配;不适用=官方 SaaS 计费/压测专用,自托管个人部署无需关心。
| # | 参数 | 样例值 | 分类 | 自托管必要性 |
|---|---|---|---|---|
| 1 | MONGO_URL | mongodb://root:example@localhost:27017/ | 数据源 | 必配 |
| 2 | MONGO_DB | manage_meals | 数据源 | 必配 |
| 3 | REDIS_URL | redis://localhost:6379 | 数据源 | 必配 |
| 4 | TYPESENSE_HOST | localhost | 搜索 | 必配 |
| 5 | TYPESENSE_PORT | 8108 | 搜索 | 必配 |
| 6 | TYPESENSE_API_KEY | secret | 搜索 | 必配(改强值) |
| 7 | RECIPE_SCRAPER_URL | http://recipe-scraper:8000 | 抓取 | 必配 |
| 8 | ACCESS_JWT_SECRET | secret | 认证 | 必配(改强值) |
| 9 | REFRESH_JWT_SECRET | secret | 认证 | 必配(改强值) |
| 10 | AUTH_ACCESS_TOKEN_EXPIRE_SEC | 30 | 认证 | 默认即可(实测注入 600) |
| 11 | AUTH_REFRESH_TOKEN_EXPIRE_SEC | 300 | 认证 | 默认即可(实测注入 2629746) |
| 12 | PASSWORD_MIN_LENGTH | 6 | 认证 | 默认即可(5.x 实测) |
| 13 | BCRYPT_SALT_ROUNDS | 10 | 认证 | 默认即可(实测 $2b$10$) |
| 14 | USER_REGISTER_ENABLED | true | 认证 | 默认即可(关注册时改) |
| 15 | RESET_PASS_JWT_SECRET | secret | 认证 | 条件(用忘记密码) |
| 16 | RESET_PASS_JWT_EXPIRE_SEC | 7200 | 认证 | 条件 |
| 17 | EMAIL_VERIFY_ENABLED | true | 邮件验证 | 条件(开邮箱验证) |
| 18 | EMAIL_VERIFY_JWT_SECRET | secret | 邮件验证 | 条件 |
| 19 | EMAIL_VERIFY_JWT_EXPIRE_SEC | 172800 | 邮件验证 | 条件 |
| 20 | APP_URL | https://example.com | 全局 | 条件(邮件链接指向) |
| 21 | ITEMS_PER_PAGE | 20 | 分页 | 默认即可(实测注入 5 生效) |
| 22 | MAX_FILE_SIZE_BYTES | 10485760 | 限制 | 默认即可(10MB) |
| 23 | DEFAULT_RECIPE_IMG | https://example.com | 图片 | 默认即可(官方占位图) |
| 24 | HELP_CONTACT_EMAIL | alice@example.com | 支持 | 条件 |
| 25 | INFRA_ENDPOINT_KEY | example | 运维 | 条件(健康检查) |
| 26 | RABBITMQ_URL | amqp://localhost | 异步 | 条件(消息队列) |
| 27 | PROCESS_USER_POST_REGISTER | false | 异步 | 默认即可 |
| 28 | PROCESS_IMAGE_POST_IMPORT | false | 异步 | 默认即可 |
| 29 | S3_KEY | s3key | 对象存储 | 条件(图片上传) |
| 30 | S3_SECRET | s3secret | 对象存储 | 条件 |
| 31 | S3_ENDPOINT | https://s3.example.com | 对象存储 | 条件 |
| 32 | S3_ORIGIN_ENDPOINT | https://bucket.s3.example.com | 对象存储 | 条件 |
| 33 | S3_CDN_ENDPOINT | https://bucket.fra1.cdn.digitaloceanspaces.com | 对象存储 | 条件 |
| 34 | S3_REGION | us-east-1 | 对象存储 | 条件 |
| 35 | S3_BUCKET | bucket | 对象存储 | 条件 |
| 36 | SMTP_HOST | localhost | 邮件 | 条件(发信) |
| 37 | SMTP_USER | info@example.com | 邮件 | 条件 |
| 38 | SMTP_PASS | asdfasdf123 | 邮件 | 条件 |
| 39 | SMTP_PORT | 587 | 邮件 | 条件 |
| 40 | SMTP_DEFAULT_FROM | ManageMeals <hello@example.com> | 邮件 | 条件 |
| 41 | OAUTH_ENABLED | true | OAuth | 条件(第三方登录) |
| 42 | OAUTH_GOOGLE_CLIENT_ID | client | OAuth | 条件 |
| 43 | OAUTH_GOOGLE_CLIENT_SECRET | secret | OAuth | 条件 |
| 44 | OAUTH_GOOGLE_CALLBACK_URI | https://example.com | OAuth | 条件 |
| 45 | OAUTH_FACEBOOK_CLIENT_ID | client | OAuth | 条件 |
| 46 | OAUTH_FACEBOOK_CLIENT_SECRET | secret | OAuth | 条件 |
| 47 | OAUTH_FACEBOOK_CALLBACK_URI | https://example.com | OAuth | 条件 |
| 48 | OAUTH_FRONTEND_CALLBACK_URL | https://example.com/auth/oauth/callback | OAuth | 条件 |
| 49 | GOCARDLESS_ACCESS_TOKEN | secret | 计费 | 不适用 |
| 50 | GOCARDLESS_ENV | SANDBOX | 计费 | 不适用 |
| 51 | GOCARDLESS_REDIRECT_URI | https://example.com | 计费 | 不适用 |
| 52 | GOCARDLESS_EXIT_URI | https://example.com | 计费 | 不适用 |
| 53 | GOCARDLESS_WEBHOOK_SECRET | secret | 计费 | 不适用 |
| 54 | PAYPAL_APP_CLIENT_ID | ppclientid | 计费 | 不适用 |
| 55 | PAYPAL_APP_SECRET | secret | 计费 | 不适用 |
| 56 | PAYPAL_API_URL | https://api-m.sandbox.paypal.com | 计费 | 不适用 |
| 57 | PREMIUM_PRICE_PENNIES | 190 | 计费 | 不适用 |
| 58 | MOCK_INSTANCE | no | 测试 | 不适用(保持默认) |
| 59 | MOCK_ALLOWED_URLS | /v1/auth/login,/v1/auth/refresh-token | 测试 | 不适用 |
| 60 | MULTIPLE_MOCK_USERS | false | 测试 | 不适用 |
样例值列保留官方 .env.example 原文(其中 asdfasdf123、example.com 等为官方占位示例),自托管时按第六章模板替换。
4.2 manage-meals-web(9 项)
前端参数实测注入 9 项(3.2 节 printenv 全量回显):PUBLIC_MAIN_TITLE、API_URL、ORIGIN、PASSWORD_MIN_LENGTH、COOKIE_ACCESS_TOKEN、COOKIE_REFRESH_TOKEN、COOKIE_ACCESS_TOKEN_EXPIRE_SEC、COOKIE_REFRESH_TOKEN_EXPIRE_SEC、BODY_SIZE_LIMIT。含义详见 5.12。
4.3 recipe-scraper(9 项可选,源码默认值)
抓取器零配置可用——实测容器内自定义变量 grep 无命中(exit=1),全部走内置默认:
| 参数 | 默认值 | 用途 |
|---|---|---|
PPLX_API_KEY | 空 | 配置后冷门站点走 Perplexity AI 兜底解析 |
DEFAULT_RECIPE_IMG | 官方占位图 URL | 抓取不到菜谱图时的兜底图 |
SCRAPER_CACHE_TIMEOUT | 10 | 缓存超时 |
SCRAPER_PROXY_URL | 空 | 抓受限站点时配上游代理 |
CACHE_REDIS_HOST / CACHE_REDIS_PORT / CACHE_REDIS_DB / CACHE_REDIS_URL / CACHE_REDIS_KEY_PREFIX | 未设 | 抓取结果 Redis 缓存(可选) |
4.4 search-sync 与依赖服务
- search-sync(5 项):
MONGO_URL、MONGO_DB、TYPESENSE_HOST、TYPESENSE_PORT、TYPESENSE_API_KEY,实测 printenv 与 API 侧同值。同步周期不可配置——源码内置cron.schedule("*/3 * * * *"),固定 3 分钟。 - mongo(3 项):
MONGO_INITDB_ROOT_USERNAME/MONGO_INITDB_ROOT_PASSWORD/MONGO_INITDB_DATABASE,仅首次初始化数据目录时生效。 - mongo-express(6 项):
ME_CONFIG_MONGODB_ADMINUSERNAME/ME_CONFIG_MONGODB_ADMINPASSWORD/ME_CONFIG_MONGODB_URL/ME_CONFIG_BASICAUTH/ME_CONFIG_BASICAUTH_USERNAME/ME_CONFIG_BASICAUTH_PASSWORD。 - typesense:无环境变量,配置走启动
command:(--data-dir /data --api-key=<密钥> --enable-cors)。
五、核心参数分类详解
以下按功能链路逐类展开,每条结论都标注实测或源码依据。
5.1 数据源与缓存(MONGO_URL / MONGO_DB / REDIS_URL)
MONGO_URL 是含账号密码的完整连接串,compose 网络内指向 mongo:27017;MONGO_DB 指定业务库名(实测库内 7 个集合:tags、syncs、shoppinglists、mealplans、users、cookbooks、recipes)。REDIS_URL 指向 valkey(Redis 协议兼容),API 用它缓存鉴权结果(源码 10 秒 TTL)——实测清缓存无业务影响,重启即清空。三者值改错的表现都是 API 起不来或 5xx,改后务必 docker compose logs manage-meals-api 复查。
5.2 认证、注册与密码策略
这是自托管最需要理解的一组,全部实测过:
-
JWT 双令牌:
ACCESS_JWT_SECRET/REFRESH_JWT_SECRET分别为两类令牌签名密钥,实测登录 accessToken 解码exp-iat=600,与AUTH_ACCESS_TOKEN_EXPIRE_SEC: 600一致;改 30 后重建即diff=30(3.3 节闭环)。两个密钥是生产必改项,泄露即可伪造任意用户令牌。 -
过期行为:令牌过期后调用返回 401
Invalid Authorization header;不带请求头返回 401Missing Authorization header。续期接口POST /v1/auth/refresh-token,字段名是token(传refreshToken报 400)。 -
PASSWORD_MIN_LENGTH(默认 6):实测用 5 位密码注册直接被 AJV 校验拒绝:http=400 {"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body/password must NOT have fewer than 6 characters"}Web 侧同名参数控制前端表单校验,两侧应配同值,否则会出现「前端放行、后端 400」的体验割裂。
-
BCRYPT_SALT_ROUNDS(默认 10):实测注册用户落库的password字段前缀$2b$10$,即默认 cost=10 生效。改此值只影响新密码哈希,已有用户仍是旧 cost,登录不受影响。 -
USER_REGISTER_ENABLED(默认 true):实测改"false"重建后,注册接口返回:{"statusCode":500,"error":"Internal Server Error","message":"User register is not enabled"}建好管理员账号后建议关闭公网自助注册。注意它是以 500 而非 403 表达拒绝(实现如此,勿按状态码写监控断言)。
-
忘记密码:
RESET_PASS_JWT_SECRET/RESET_PASS_JWT_EXPIRE_SEC(样例 7200 秒)支撑/v1/auth/forgot-password、/v1/auth/reset-password流程,但发信依赖 5.3 的 SMTP——没配 SMTP 时该功能形同虚设。
5.3 邮箱验证与 SMTP(EMAIL_VERIFY_* / SMTP_*)
EMAIL_VERIFY_ENABLED 样例值为 true,但不注入时实测不强制验证:新建用户 users 集合中 emailVerified: true 且未验证不拦登录。一旦开启,需同时配 EMAIL_VERIFY_JWT_SECRET、EMAIL_VERIFY_JWT_EXPIRE_SEC(样例 172800=48h)、APP_URL(验证链接域名)与完整 SMTP_* 五件套,否则用户收不到验证邮件会被锁在门外。个人自托管建议保持默认关闭。SMTP 五参数(SMTP_HOST/USER/PASS/PORT/DEFAULT_FROM)为通用 nodemailer 语义,样例端口 587(STARTTLS)。
5.4 OAuth 第三方登录(OAUTH_* 8 项)
OAUTH_ENABLED 总开关 + Google/Facebook 各三件套(client id/secret/callback URI)+ OAUTH_FRONTEND_CALLBACK_URL(前端回调页,形如 https://<你的域名>/auth/oauth/callback)。回调 URI 必须与 Google/Facebook 控制台登记值逐字符一致。纯自托管家庭用户通常用不上,整组不注入即可。
5.5 分页与请求限制(ITEMS_PER_PAGE / MAX_FILE_SIZE_BYTES / BODY_SIZE_LIMIT)
ITEMS_PER_PAGE 控制所有列表接口分页,实测未注入时 GET /v1/recipes 回显 "perPage":20,注入 5 后重建即回显 "perPage":5(3.3 节)。MAX_FILE_SIZE_BYTES(样例 10485760=10MB)限制 API 上传体积;前端 SvelteKit 侧对应 BODY_SIZE_LIMIT(实测注入 Infinity,即 Web 不拦,实际上限由 API 与 S3 决定)。
5.6 搜索链路(TYPESENSE_* + search-sync)
API 与 search-sync 各注入同一组 TYPESENSE_HOST/PORT/API_KEY,API_KEY 必须与 typesense 启动命令 --api-key= 完全一致,否则同步与查询全部 401。recipes 集合由 search-sync 首启自动创建(实测 default_sorting_field: rating)。索引延迟不是配置项:cron 固定 */3 * * * *,实测导入后约 3 分钟才可搜到;查询接口 /v1/search 必填 q、c、p 三参数(c 当前仅 recipes)。
5.7 抓取链路与图片兜底(RECIPE_SCRAPER_URL / scraper / DEFAULT_RECIPE_IMG)
URL 导入链路:API /v1/recipes/import?url= → RECIPE_SCRAPER_URL → scraper 解析。scraper 侧 9 个可选参数见 4.3;实测零注入可用。DEFAULT_RECIPE_IMG 在 API 与 scraper 两侧同名(源码默认官方占位图 https://whatacdn.fra1.cdn.digitaloceanspaces.com/mmeals/images/default1.jpg),自托管想换默认菜谱图时两侧同改。PROCESS_IMAGE_POST_IMPORT(默认 false)开启后导入图片转存走异步队列,依赖 RabbitMQ/S3,个人部署保持默认。
5.8 对象存储(S3_* 7 项)
菜谱图片上传功能需要 S3 兼容存储(官方按 DigitalOcean Spaces 写的样例,MinIO 亦可)。七个参数中 S3_ENDPOINT(API 写入用)、S3_ORIGIN_ENDPOINT、S3_CDN_ENDPOINT(浏览器读取用)三个 URL 最容易配混:写入走 endpoint,展示走 CDN 域名。不配 S3 时图片上传不可用,但 URL 导入的菜谱外链图片不受影响——实测未配 S3 的完整链路(导入 brownies)正常。
5.9 计费与订阅(GOCARDLESS_* / PAYPAL_* / PREMIUM_PRICE_PENNIES)
这 9 项服务于 ManageMeals 官方 SaaS 的订阅收费(GoCardless 扣款、PayPal 收款、PREMIUM_PRICE_PENNIES=190 即 $1.90 定价)。自托管个人部署全部不适用,保持不注入即可;/v1/premium、/v1/subscriptions 路由存在但无实际业务。
5.10 运维与健康检查(INFRA_ENDPOINT_KEY / RABBITMQ_URL)
API 提供顶层健康检查 GET /infra/health(不在 /v1 下、不走用户 JWT),用 INFRA_ENDPOINT_KEY 作访问密钥。实测未配 key 时:
http=403
{"statusCode":403,"error":"Forbidden","message":"Invalid infra key"}
配好后可安全接入 Uptime Kuma 等外部探活。RABBITMQ_URL 仅在启用异步处理(5.7/5.8 的 PROCESS_* 开关)时需要,最小部署不配。
5.11 Mock 与开发专用参数(MOCK_*)
MOCK_INSTANCE(默认 no)是官方开发/压测用的接口挡板,配合 MOCK_ALLOWED_URLS、MULTIPLE_MOCK_USERS 使用。生产环境务必保持 no/不注入——开启会绕过真实鉴权路径,属于高危配置。
5.12 Web 侧参数详解
PUBLIC_MAIN_TITLE:站点标题。PUBLIC_前缀是 SvelteKit 约定——该前缀变量会暴露给浏览器端代码,任何敏感值都不得用PUBLIC_前缀传。API_URL:SSR 服务端调后端地址(容器网络内http://manage-meals-api:3000/v1),浏览器不直连它。ORIGIN:站点对外地址,必须与浏览器实际访问地址逐字符一致(协议+域名+端口)。反代/域名访问时忘改是最常见的「登录后仍跳登录页」原因——Cookie 作用域不匹配。COOKIE_ACCESS_TOKEN/COOKIE_REFRESH_TOKEN:登录态 Cookie 名;两个*_EXPIRE_SEC应与 API 侧AUTH_*_EXPIRE_SEC同值,避免「Cookie 未过期而令牌已过期」的空窗(实测两侧均 600/2629746 对齐)。PASSWORD_MIN_LENGTH:前端注册校验,与 API 同名参数对齐(5.2)。BODY_SIZE_LIMIT:SvelteKit 请求体上限,Infinity表示前端不拦。
六、生产配置模板
本章给出「compose 插值 + .env 分离」的生产形态。模板中的 ${VAR} 是 Compose 变量插值语法(运行期由同目录 .env 填充),属合法占位写法,不是待填错误。
6.1 docker-compose.yaml(关键服务节选)
name: manage-meals
services:
manage-meals-api:
image: ghcr.io/managemeals/manage-meals-api@sha256:01757ea28191c0f92401aacee9c6050568e205cec7eb32646038ce965454de83
restart: unless-stopped
environment:
MONGO_URL: ${MM_MONGO_URL}
MONGO_DB: manage_meals
REDIS_URL: redis://valkey:6379
TYPESENSE_HOST: typesense
TYPESENSE_PORT: 8108
TYPESENSE_API_KEY: ${MM_TYPESENSE_KEY}
ACCESS_JWT_SECRET: ${MM_ACCESS_JWT_SECRET}
REFRESH_JWT_SECRET: ${MM_REFRESH_JWT_SECRET}
AUTH_ACCESS_TOKEN_EXPIRE_SEC: 600
AUTH_REFRESH_TOKEN_EXPIRE_SEC: 2629746
RECIPE_SCRAPER_URL: http://recipe-scraper:8000
USER_REGISTER_ENABLED: "false"
APP_URL: https://meals.example.com
depends_on: [mongo, valkey, recipe-scraper, typesense, search-sync]
ports: ["3000"]
manage-meals-web:
image: ghcr.io/managemeals/manage-meals-web@sha256:9886d09bbee8ea9da60a320757cb5eb15fa5740b701e5b5b86377d59b1ff0a56
restart: unless-stopped
environment:
PUBLIC_MAIN_TITLE: My Meals
API_URL: http://manage-meals-api:3000/v1
ORIGIN: https://meals.example.com
PASSWORD_MIN_LENGTH: 10
COOKIE_ACCESS_TOKEN: mmeals_access_token
COOKIE_REFRESH_TOKEN: mmeals_refresh_token
COOKIE_ACCESS_TOKEN_EXPIRE_SEC: 600
COOKIE_REFRESH_TOKEN_EXPIRE_SEC: 2629746
BODY_SIZE_LIMIT: Infinity
depends_on: [manage-meals-api]
ports: ["3000"]
(mongo/valkey/typesense/scraper/search-sync 服务定义与实测栈相同,敏感值同样改为 .env 插值注入;镜像改用 digest 固定保证可复现。生产不对外映射 api/typesense/scraper 端口,仅经 Web 与反代访问。)
6.2 同目录 .env(权限 600,不入库)
MM_MONGO_URL=mongodb://root:<强随机密码>@mongo:27017/
MM_TYPESENSE_KEY=<强随机密钥>
MM_ACCESS_JWT_SECRET=<openssl rand -hex 32 生成>
MM_REFRESH_JWT_SECRET=<openssl rand -hex 32 生成>
6.3 生产必改清单
| 项 | 参数 | 动作 |
|---|---|---|
| 1 | ACCESS_JWT_SECRET / REFRESH_JWT_SECRET | 强随机,泄露=任意伪造登录态 |
| 2 | MONGO_URL 密码 / MONGO_INITDB_ROOT_PASSWORD | 强随机 |
| 3 | TYPESENSE_API_KEY(三处一致) | 强随机 |
| 4 | ORIGIN / APP_URL | 改为真实域名,与反代一致 |
| 5 | USER_REGISTER_ENABLED | 建好账号后 "false" |
| 6 | mongo-express 服务 | 生产删除,或 ME_CONFIG_BASICAUTH: "true" 并设账号密码 |
| 7 | PASSWORD_MIN_LENGTH | 建议 ≥10,Web/API 同值 |
| 8 | api/typesense/scraper 宿主端口映射 | 移除,仅反代暴露 Web |
| 9 | compose 镜像引用 | :latest 改 digest 固定 |
| 10 | 备份 | mongodump 定期(命令见 CLI 工具篇) |
七、常见问题
Q1:改了 environment 但行为没变?
up -d 时注意看该服务是否出现 Recreated;若显示 Running,说明 Compose 认为配置未变(常见于改了 .env 但服务引用变量名拼错)。用 3.2 的 printenv 对比声明值与进程值定位。
Q2:登录成功却反复跳回登录页?
九成是 ORIGIN 与实际访问地址不一致(5.12),Cookie 域不匹配。改成浏览器地址栏同源值后 up -d manage-meals-web。
Q3:注册返回 500 "User register is not enabled"?
USER_REGISTER_ENABLED 为 "false"(实测回显见 5.2)。临时改回 "true" 建完账号再关。
Q4:注册 400 "must NOT have fewer than 6 characters"?
密码长度低于 PASSWORD_MIN_LENGTH(默认 6)。想调强策略时 Web/API 两侧同改(6.3 第 7 项)。
Q5:刚导入的菜谱搜不到?
search-sync 固定 3 分钟一轮(5.6),非配置错误。等一轮后看 docker compose logs search-sync 是否出现 Imported N recipes。
Q6:/infra/health 返回 403?
未配 INFRA_ENDPOINT_KEY 或 key 不匹配(实测回显见 5.10),属预期防护而非故障。
Q7:开了 EMAIL_VERIFY_ENABLED 后新用户无法登录?
验证邮件发不出去——SMTP 未配或 APP_URL 错误导致链接不可达。个人自托管建议保持不开启(5.3)。
Q8:导入图片显示不出 / 上传功能报错?
未配 S3_* 时图片上传不可用(5.8);URL 导入菜谱的外链图不受影响。
八、参考链接
- 站内:《ManageMeals Docker Compose 部署指南》、《ManageMeals 介绍与选型指南》
- 后端仓库(
.env样例 60 键出处):https://github.com/managemeals/manage-meals-api - 前端仓库:https://github.com/managemeals/manage-meals-web
- 镜像发布处(仅
:latest):https://ghcr.io/managemeals/ - 组织主页(scraper/search-sync 源码位于 api 仓库
scraper/、search/目录):https://github.com/managemeals