Mealie 3.30.0 Docker Compose 部署指南

Mealie v3.30.0 Docker Compose 部署实测:SQLite 单服务与 PostgreSQL 双服务两套 compose 全文、首次启动日志五段逐行解读(gosu 降权/62 步 alembic 迁移/默认用户初始化/APP SETTINGS dump/dual-stack 监听)、部署后验证闭环、docker run 等价形态、数据卷布局与 healthcheck 机制、7 条实测排障。

MealieMealie自托管DockerPythonFastAPIPostgreSQL2026/11/02

Mealie 3.30.0 Docker Compose 部署指南

目标:把 Mealie v3.30.0 用 Docker Compose 跑起来——SQLite 单服务形态与 PostgreSQL 双服务形态各一套可直接抄用的 compose,逐行解读首次启动日志(降权、迁移、初始化、配置 dump),并给出部署后验证、docker run 等价形态与常见排障。本文全部命令与日志均为本地实测回显(实测日期 2026-11-02),非官方文档转述。配置项逐项详解见本专栏《Mealie 3.30.0 配置参数详解》,API 与备份运维见《Mealie 3.30.0 常用 CLI 命令与工具详解》。

一、环境信息

本次实测的宿主机与镜像信息:

  • 镜像:ghcr.io/mealie-recipes/mealie:v3.30.0(解包体积约 1.47GB,内含 NLTK 模型与前端构建产物)
  • 镜像内版本指纹:GIT_COMMIT_HASH=9552df62118224a377d9c9b0a62706a62220ab86(首次启动 APP SETTINGS dump 实测),容器内 Python 3.14.8
  • 配套数据库镜像:postgres:17(实测 dump 头部版本 17.11 (Debian))
  • 后端监听:容器内 9000/tcp(API_PORT 默认值,官方明确 Docker 形态下不要改)
  • 实测实例:
    • PostgreSQL 形态:mealie-3.30.0-app(:8152)+ mealie-3.30.0-db(postgres:17,不对外发布端口)
    • SQLite 形态:mealie-3.30.0-sqlite(:8157,单服务)
    • docker run 一次性验证实例::8160(验证后已清理)
  • 数据卷:PG 形态映射 /app/data,PG 数据另存 pgdata 卷;SQLite 形态映射 /app/data 为宿主机目录

二、部署形态选择:SQLite 还是 PostgreSQL

Mealie 数据库引擎二选一(DB_ENGINE,默认 sqlite):

维度SQLite(默认)PostgreSQL
compose 服务数1 个2 个(+postgres)
配置成本零配置,库文件落在 data/mealie.db需配 DB_ENGINE=postgres 与 POSTGRES_* 四件套
模糊搜索不可用可用(官方明确仅 PG 启用;迁移日志中对应 b3dbb554ba53, postgres fuzzy search 一步,库内实测有 pg_trgm 扩展)
并发单写者;高并发可开 SQLITE_MIGRATE_JOURNAL_WAL=true(默认 False)多连接并发,官方建议 1-20 用户以上规模使用
备份工具冷拷贝 mealie.db 或应用内备份pg_dump/pg_restore 或应用内备份

结论:家庭单实例、无模糊搜索诉求 → SQLite;多人使用、需要模糊搜索、已有 PG 运维习惯 → PostgreSQL。两种形态下文都给出实测 compose。

三、SQLite 形态:单服务部署(实测)

3.1 compose 文件全文

name: mealie-3.30.0-sqlite

services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:v3.30.0
    container_name: mealie-3.30.0-sqlite
    restart: always
    ports:
      - "8157:9000"
    volumes:
      - /opt/data/quzuo/tmp/mealie/sqlite-data:/app/data
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Asia/Shanghai
      BASE_URL: http://127.0.0.1:8157
      ALLOW_SIGNUP: "false"

要点:

  • PUID/PGID:容器默认以 UID/GID 911 运行(官方默认值);宿主机数据目录属主不是 911 时显式对齐(本例 1000),否则 data/ 写不进去。
  • TZ:官方默认 UTC,不设则日期错位;实测设为 Asia/Shanghai 后,日志中每日任务排期正确换算(DAILY_SCHEDULE_TIME: 23:45 本地时间 → 日志 Daily tasks scheduled for 2026-11-02 15:45:00+00:00,即 23:45 CST 的 UTC 表示)。
  • ALLOW_SIGNUP: "false":v1.4.0 起官方默认即 false(匿名注册关闭),显式写出以防误改。
  • BASE_URL:通知与 OIDC 回调用的外部地址,单机测试写 http://127.0.0.1:<端口> 即可。

3.2 启动

docker compose up -d

约 15 秒后健康检查通过(docker ps 显示 Up (healthy)),curl -s http://127.0.0.1:8157/api/app/about 返回 200 且 "version": "v3.30.0"。SQLite 形态首次启动日志中可见 "DB_ENGINE": "sqlite"——不设任何 DB_* 变量时缺省即 SQLite。

四、PostgreSQL 形态:双服务部署(实测)

4.1 compose 文件全文

name: mealie-3.30.0-stack

services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:v3.30.0
    container_name: mealie-3.30.0-app
    restart: always
    ports:
      - "8152:9000"
    volumes:
      - /opt/data/quzuo/tmp/mealie/data:/app/data
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Asia/Shanghai
      BASE_URL: http://127.0.0.1:8152
      ALLOW_SIGNUP: "false"
      HTTP_ALLOW_LIST: 10.89.56.0/24
      DB_ENGINE: postgres
      POSTGRES_USER: mealie
      POSTGRES_PASSWORD: mealie
      POSTGRES_SERVER: postgres
      POSTGRES_PORT: 5432
      POSTGRES_DB: mealie
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - mealie-net

  postgres:
    container_name: mealie-3.30.0-db
    image: postgres:17
    restart: always
    volumes:
      - /opt/data/quzuo/tmp/mealie/pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: mealie
      POSTGRES_USER: mealie
      PGUSER: mealie
      POSTGRES_DB: mealie
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 30s
      timeout: 20s
      retries: 3
    networks:
      - mealie-net

networks:
  mealie-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.56.0/24

要点:

  • DB_ENGINE: postgres + POSTGRES_SERVER: postgres(compose 服务名即容器内 DNS 名)。POSTGRES_* 四件套的官方默认值恰好是 mealie/mealie/postgres/5432/mealie,与 postgres 镜像侧对齐即可连通;生产环境务必改密码,或用 POSTGRES_PASSWORD_FILE 走 Docker Secrets(见配置篇)。
  • depends_on.postgres.condition: service_healthy:必须配合 postgres 服务自定义 pg_isready 健康检查。Mealie 启动时数据库连接失败会直接终止(Database connection established. 是迁移的前置日志),仅 depends_on 服务名而不等就绪,冷启动时 PG 尚未 accept 连接会导致 Mealie 重启一轮才成功。
  • HTTP_ALLOW_LIST: 10.89.56.0/24:本例为 URL 抓取导入闭环测试专门放行内网网段——Mealie 默认拦截服务端发起的对私网/内网地址的请求(SSRF 防护),要让"贴内网 URL 导入食谱"成功,必须把目标网段加进该白名单。纯公网使用可不设。
  • PG 实例不发布任何宿主机端口,仅通过 mealie-net 内部网络访问。

4.2 首次启动日志逐段解读(实测全文见取证)

docker compose up -d && docker logs -f mealie-3.30.0-app 从冷启动到就绪的完整日志分五段:

① 降权切用户(entrypoint run.sh 行为):

Switching to dedicated user

        User uid:    1000
        User gid:    1000

容器以 root 启动时,run.sh 用 gosu 切换到 PUID/PGID 指定的用户再重新执行自身,并先 chown -R $PUID:$PGID /app。

② 连接数据库并跑 alembic 迁移:

INFO ... - start: database initialization
INFO ... - Database connection established.
INFO ... - Context impl PostgresqlImpl.
INFO ... - Will assume transactional DDL.
INFO ... - Migration needed. Performing migration...
INFO ... - Running upgrade  -> 6b0f5f32d602, Initial tables
...
INFO ... - Running upgrade afdc42353aa4 -> a9f823e9e441, 'add end_date to group meal plans'
INFO ... - Running upgrade a9f823e9e441 -> 071e03b4f851, 'backfill missing ai provider settings'

空库一次跑完 62 个 alembic upgrade 步骤(从 Initial tables 到 head 071e03b4f851),另有 3 个数据级迁移(Running new model migration (migrate_recipe_last_made_to_household) 等,v2 引入 Household 隔离层时的数据搬迁)。其中 b3dbb554ba53, postgres fuzzy search 这一步即 PG 专属模糊搜索的落库动作。整个迁移在 1 秒内完成(日志时间戳 02:30:12 → 02:30:13)。

③ 初始化默认数据:

INFO ... - Database contains no users, initializing...
INFO ... - Generating Default Group and Household
INFO ... - Generating Default User
INFO ... - end: database initialization

空库首次启动自动创建默认 Group(DEFAULT_GROUP=Home)、Household(DEFAULT_HOUSEHOLD=Family)与默认用户 changeme@example.com / MyPassword。这就是官方文档标注的首登凭据,也是公网部署的最大风险点(见第八节)。

④ 打印全量生效配置:

INFO ... - ------APP SETTINGS------
INFO ... - {
    "LOG_LEVEL": "info",
    "BASE_URL": "http://127.0.0.1:8152",
    "HOST_IP": "*",
    "API_PORT": 9000,
    "TOKEN_TIME": 48,
    "ALLOW_SIGNUP": false,
    "DB_ENGINE": "postgres",
    "DAILY_SCHEDULE_TIME": "23:45",
    "GIT_COMMIT_HASH": "9552df62118224a377d9c9b0a62706a62220ab86",
    ...(全量 90+ 键,含 theme/branding/SMTP/LDAP/OIDC/AI 各分组)
}

这份 dump 是核对"环境变量到底生效没有"的最快手段——每个变量的最终值都在,逐项含义见配置篇。随后是 APP FEATURES 段,逐一判定 SMTP/LDAP/OIDC 是否可用并给出原因(如 SMTP ... Reason: Missing required values for ['SMTP_HOST', 'SMTP_FROM_EMAIL'])。

⑤ 就绪:

INFO ... - Application startup complete.
INFO ... - Uvicorn running on socket ('::', 9000, 0, 0) (Press CTRL+C to quit)

注意监听地址是 ('::', 9000)——官方文档说明:API_HOST 缺省为空时开一个 IPv6 dual-stack socket,同一端口同时接受 IPv4/IPv6;仅当宿主机不支持 dual-stack 时才回退 IPv4 0.0.0.0。

五、部署后验证闭环(实测)

按以下顺序验证,全部通过即部署成功:

  1. 存活:curl -s http://127.0.0.1:8152/api/app/about → 200,返回体含 "version": "v3.30.0"(该路径即容器健康检查探针,见第七节)。
  2. 登录:POST /api/auth/token(form 表单 username=changeme@example.com&password=MyPassword)→ 200,返回 access_token(JWT,exp 为签发后 TOKEN_TIME=48 小时)。
  3. 写路径:携带 Bearer token POST /api/recipes 创建食谱 → 201 返回 slug(实测 "fan-qie-chao-dan");GET /api/recipes 可列出。
  4. 立即改密:登录后第一件事修改默认用户密码。改密后旧密码登录返回 401——实测在 PG 实例上复现:改密后再用 changeme/MyPassword 请求 token 返回 {"detail":"Unauthorized"},证明密码修改真实生效、旧凭据已失效。

六、docker run 等价形态(实测)

不想写 compose 时,单条 docker run 即可拉起 SQLite 形态(一次性实例 :8160 实测,验证后已清理):

docker run -d --name mealie-runtest -p 8160:9000 \
  -v /opt/data/quzuo/tmp/mealie/runtest-data:/app/data \
  -e BASE_URL=http://127.0.0.1:8160 \
  -e ALLOW_SIGNUP=false \
  -e TZ=Asia/Shanghai \
  ghcr.io/mealie-recipes/mealie:v3.30.0

实测时间线:启动约 15 秒后 /api/app/about 返回 200 且 version v3.30.0;日志依次出现 Generating Default Group and Household、Generating Default User,APP SETTINGS 中 "DB_ENGINE": "sqlite"(缺省即 SQLite,无需任何 DB 变量);用默认凭据登录拿到 token(长度 224),POST /api/recipes 返回 201(slug runtest-bi-huan-yan-zheng)。compose 与 docker run 在镜像行为上完全等价,compose 只是把端口/卷/环境固化下来。

七、数据卷与健康检查机制

7.1 /app/data 卷实测布局

data/
├── .secret              # 64B,JWT 签名密钥;丢失=所有已发 token 失效
├── .session_secret      # 64B,会话密钥
├── mealie.log           # 应用日志(同时输出到 stdout)
├── mealie.db            # 仅 SQLite 形态:库文件实测 1.5MB / 70 张表
├── backups/             # 应用内备份 zip(命名 mealie_v3.30.0_<UTC时间戳>.zip)
├── recipes/             # 按食谱 UUID 分目录存图片/附件
├── users/               # 用户头像
├── groups/              # 组级资源(如 cookbook 模板)
├── templates/           # 食谱模板
└── .temp/               # 临时下载/导入缓存

两个关键认知:① .secret 持久化在数据卷内,实测容器重建(compose recreate)后旧 access_token 仍然有效;② 备份 zip 文件名用 UTC 时间戳——实测 02:37(CST)生成的备份名为 mealie_v3.30.0_2026.11.01.18.37.37.zip,核对时间注意 +8 小时。

7.2 健康检查

镜像内置 healthcheck.sh(实测全文):

#!/bin/bash

PORT="${API_PORT:-9000}"

if [ -n "$TLS_CERTIFICATE_PATH" ] && [ -n "$TLS_PRIVATE_KEY_PATH" ]; then
  PROTO="https"
else
  PROTO="http"
fi

# -k: TLS certificate is likely not issued for 127.0.0.1, so don't verify
curl -fsk "${PROTO}://127.0.0.1:${PORT}/api/app/about" > /dev/null

即探针就是 GET /api/app/about(无需认证)。自写 compose 时可直接复用:

    healthcheck:
      test: ["CMD", "/app/healthcheck.sh"]
      interval: 30s
      timeout: 10s
      retries: 3

八、常见坑与排障(实测/日志证据)

  1. 默认凭据暴露公网:首次启动即有 changeme@example.com/MyPassword 管理员。ALLOW_SIGNUP=false 只挡注册、不挡这个内置账号。公网部署必须:改密 + 用反代限制来源 + 考虑关掉 API_DOCS(默认 True,/docs 暴露全部 API 结构)。
  2. PUID/PGID 不匹配:数据目录属主与 PUID 不一致时,run.sh 的 chown -R 只在 root 启动路径执行;若以非 root 起容器且目录不可写,迁移阶段直接失败。把 PUID/PGID 显式设为数据目录属主是最省事的解法。
  3. 端口占用:日志报 ERROR [Errno 98] Address already in use (while attempting to bind on address ('::', 9000)) 即容器内 9000 已被占(例如误在运行中的容器里手动再起 mealie 进程)。宿主机端口冲突则在 compose 改 ports 映射,不要动 API_PORT。
  4. 内网 URL 抓取导入失败:贴一个解析到私网地址的链接导入食谱会被 SSRF 防护拦截。实测解法:HTTP_ALLOW_LIST 加入目标网段(本例 10.89.56.0/24),重启后 POST /api/recipes/create-url 成功导入(返回 slug mapo-tofu)。注意 compose 改了环境变量要 up -d 重建容器才生效——实测旧日志里 HTTP_ALLOW_LIST 显示为空串,就是"启动早于 compose 编辑"的时序问题,用 docker inspect 确认当前容器实际注入值可快速定位此类问题。
  5. PG 未就绪抢跑:不给 postgres 配 pg_isready 健康检查、depends_on 不带 condition: service_healthy,冷启动时 Mealie 会因连不上库而退出重启,拉长就绪时间。
  6. 模糊搜索"不生效":SQLite 形态无 fuzzy search,属预期行为(官方文档 + 迁移日志 postgres fuzzy search 步骤仅在 PG 库出现),不是 bug;需要该特性就换 PG 形态。
  7. 升级:直接改镜像 tag 后 docker compose up -d,新容器启动时自动跑增量迁移(本例全新库一次跑完 62 步;老库只跑未应用步骤)。升级前先用应用内备份或 pg_dump/冷拷贝兜底(操作见 CLI 篇)。

九、参考链接

Mealie 3.30.0 Docker Compose 部署指南 - Quzuo