Marmot 0.11.1 Docker Compose 部署指南

真实服务器上 Docker Compose 部署 Marmot 0.11.1 全程实测:加密密钥、起栈、强制改密、插件预置、摄取编目演示库,含端口冲突、代理超时、503、404 排障实录。

MarmotMarmotDocker ComposePostgreSQL2026/11/02

Marmot 0.11.1 Docker Compose 部署指南

本文在真实服务器上以 Docker Compose 部署 Marmot 0.11.1(官方推荐入门形态),全程实测:生成加密密钥、起栈、首次登录与强制改密、创建摄取管道把演示库编目进目录,并实录端口冲突、插件自动安装缓慢、503、404 等排障过程。概念与选型见《Marmot 数据目录:介绍与选型指南》,配置项含义见《Marmot 0.11.1 配置参数详解》,命令行操作见《Marmot 0.11.1 常用 CLI 命令详解》。

一、环境与版本

项目值
组件Marmot(marmotdata/marmot)
版本v0.11.1(2026-10-11 发布,GitHub Releases 实查)
镜像ghcr.io/marmotdata/marmot:0.11.1(259 MB,含 Web UI 与 CLI)
依赖PostgreSQL 14+(本例 postgres:16-alpine)
Web 端口8141(原计划 8142 被占用,见排障一)
网络自定义 bridge marmot-net,显式子网 10.89.56.0/24
Compose 项目名marmot-0.11.1-stack(显式 name:,防多栈互删)

镜像拉取走代理(192.168.1.62:7897)实测成功;注意代理仅对宿主机侧镜像拉取有效,Marmot 进程内下载插件时容器内访问该代理会超时(见排障三)。

二、Compose 编排

工作目录 /opt/data/quzuo/tmp/marmot/,docker-compose.yaml 基于官方 Docker Compose 部署文档(marmotdata.io/docs/Deploy/Docker-Compose,2026-11-01 查询)改写:固定镜像版本、显式项目名与子网、Web 端口改 8141、数据库凭据走 .env:

name: marmot-0.11.1-stack

services:
  marmot:
    image: ghcr.io/marmotdata/marmot:0.11.1
    container_name: marmot-0.11.1-server
    ports:
      - "8141:8080"
    environment:
      MARMOT_DATABASE_HOST: postgres
      MARMOT_DATABASE_PORT: "5432"
      MARMOT_DATABASE_USER: marmot
      MARMOT_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
      MARMOT_DATABASE_NAME: marmot
      MARMOT_DATABASE_SSLMODE: disable
      MARMOT_SERVER_ENCRYPTION_KEY: ${MARMOT_ENCRYPTION_KEY}
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - marmot-net
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    container_name: marmot-0.11.1-db
    environment:
      POSTGRES_USER: marmot
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: marmot
    volumes:
      - marmot_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U marmot"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - marmot-net
    restart: unless-stopped

volumes:
  marmot_data:

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

要点:

  • 数据库服务不对外发布端口,只在 marmot-net 内部网络以主机名 postgres 访问;
  • MARMOT_SERVER_ENCRYPTION_KEY 用于静态加密目录中保存的数据源凭据,官方要求生产环境必须设置(否则启动时拒绝存储凭据,除非显式 allow_unencrypted);
  • depends_on + pg_isready 健康检查保证 Marmot 启动时数据库已就绪,首次启动会自动跑完全部 schema 迁移。

三、生成加密密钥

官方 CLI 提供密钥生成命令。镜像内自带同一二进制,可在起栈前用 compose 一次性任务生成(实测回显逐行照抄):

$ docker compose run --no-deps --rm marmot generate-encryption-key
 Container marmot-0111-stack-marmot-run-09cd46ded9b5 Creating 
 Container marmot-0111-stack-marmot-run-09cd46ded9b5 Created 

  Your encryption key:

    4iUpeCZkLJ3jvCjksOZA2yzV1ewOVSPc9p2vKUa6qp0=

  Set it as an environment variable:

    export MARMOT_SERVER_ENCRYPTION_KEY="4iUpeCZkLJ3jvCjksOZA2yzV1ewOVSPc9p2vKUa6qp0="

  Store this key somewhere safe — losing it means losing access
  to encrypted credentials. See https://marmotdata.io/docs/Deploy
  for full configuration options.

密钥是 32 字节 base64(输出里缩进的那一行)。把它安全保存:丢失意味着目录里所有加密存储的数据源凭据作废。

四、环境变量文件与启动

同目录创建 .env:

POSTGRES_PASSWORD=q1w2e3r4
MARMOT_ENCRYPTION_KEY=4iUpeCZkLJ3jvCjksOZA2yzV1ewOVSPc9p2vKUa6qp0=

启动(实测回显):

$ docker compose up -d
 Container marmot-0.11.1-db Creating 
 Container marmot-0.11.1-db Created 
 Container marmot-0.11.1-server Creating 
 Container marmot-0.11.1-server Created 
 Container marmot-0.11.1-db Starting 
 Container marmot-0.11.1-db Started 
 Container marmot-0.11.1-db Waiting 
 Container marmot-0.11.1-db Healthy 
 Container marmot-0.11.1-server Starting 
 Container marmot-0.11.1-server Started 

启动状态与端口映射确认:

$ docker compose ps
NAME                   IMAGE                              COMMAND                  SERVICE    CREATED          STATUS                    PORTS
marmot-0.11.1-db       postgres:16-alpine                 "docker-entrypoint.s…"   postgres   34 minutes ago   Up 34 minutes (healthy)   5432/tcp
marmot-0.11.1-server   ghcr.io/marmotdata/marmot:0.11.1   "/usr/local/bin/marm…"   marmot     5 minutes ago    Up 5 minutes              0.0.0.0:8141->8080/tcp, [::]:8141->8080/tcp

日志确认迁移与服务就绪(实测节选):

$ docker compose logs marmot | head -8
No config file found, using defaults and environment variables
Starting Marmot...
{"level":"info","sequence":1,"name":"000001_init_extensions.sql","direction":"up","time":"2026-11-01T15:33:49Z","message":"Running migration"}
...
{"level":"info","address":"0.0.0.0:8080","swagger_ui":"http://0.0.0.0:8080/swagger/index.html","time":"2026-11-01T16:01:23Z","message":"Server started"}
{"level":"info","time":"2026-11-01T16:01:23Z","message":"Plugin loading complete"}

Web 探活:

$ curl -s -o /dev/null -w 'root HTTP %{http_code}\n' http://localhost:8141/
root HTTP 200

五、首次登录与强制改密

浏览器打开 http://<服务器IP>:8141/,官方默认凭据 admin:admin。程序化登录端点为 POST /api/v1/users/login(JSON 体 {"username":"admin","password":"admin"},响应 access_token 即下文示例中的 <token>,用 Bearer 头携带)。实测安全行为:默认口令登录后,任何写操作都会被服务端拒绝,返回 403 与明确提示(实测回显):

$ curl -s -X POST http://localhost:8141/api/v1/users/apikeys \
    -H "Authorization: Bearer <默认口令登录的token>" -H 'Content-Type: application/json' \
    -d '{"name":"docs-test","expires_in_days":7}'
{"error":"Password change required"}

程序化改密(等价于 UI 上的强制改密引导):

$ curl -s -X POST http://localhost:8141/api/v1/users/update-password \
    -H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
    -d '{"current_password":"admin","new_password":"q1w2e3r4"}'
{"access_token":"eyJhbGciOiJIUzI1NiIs...","token_type":"Bearer","expires_in":86400,"requires_password_change":false}

(access_token 为 JWT,实测输出节选首尾,中间省略。)

新密码要求至少 8 位。改密后返回新 token,requires_password_change 变为 false。本例统一使用示例口令 q1w2e3r4,生产环境请换强口令。

改密后用新口令登录并签发 API Key(供 CLI 与摄取使用,完整密钥仅创建时显示一次):

$ curl -s -X POST http://localhost:8141/api/v1/users/apikeys \
    -H "Authorization: Bearer <新token>" -H 'Content-Type: application/json' \
    -d '{"name":"docs-test","expires_in_days":7}'
{"id": "37dabeb0-c80c-47e7-9055-0c44b00729e5", "user_id": "b8c1d2dc-74a9-457c-8a64-eba9c3e77fa6", "name": "docs-test", "key": "mrmt_u_Ao77PO69zOSsTHkYzF-N2qTRaRQoZUc2HbsdYyMfv_I=", "expires_at": "2026-11-08T15:42:01.907402Z"}

六、插件自动安装:第一次启动的真实代价

Marmot 的数据源发现能力由插件提供(OCI 制品,ghcr.io/marmotdata/plugins)。默认 plugins.autoinstall: true,服务端启动时会从注册表逐个安装全部核心插件(70+ 个)。实测该环境到 ghcr 直连每个插件约 1.5–2 分钟,全部装完需 2 小时以上;期间 /api/v1/plugins 返回 {"plugins":[],"loading":true},摄取相关接口返回 503(见排障四)。

两种生产做法(均已实测验证):

  1. 接受首启全量安装:网络直连 ghcr 的环境,让它在后台装完,功能全量可用;
  2. 预置缓存 + 关闭自动安装:把已下载的插件缓存目录挂进 ~/.marmot/plugins/cache,并设 MARMOT_PLUGINS_AUTOINSTALL=false。实测重启后 12 秒内注册表就绪:
  marmot:
    # ...
    environment:
      MARMOT_PLUGINS_AUTOINSTALL: "false"
    volumes:
      - ./plugins-cache:/home/marmot/.marmot/plugins/cache
$ curl -s -H "Authorization: Bearer <token>" http://localhost:8141/api/v1/plugins | head -c 60
{"plugins":[{"id":"mysql","name":"MySQL",...   # loading:false,22 个插件就绪

七、建演示库并摄取编目

在栈内 PostgreSQL 建一个演示业务库 shop(三张表带外键,供血缘验证),实测回显:

$ docker compose exec -T postgres psql -U marmot -d postgres -c "CREATE DATABASE shop;"
CREATE DATABASE
$ docker compose exec -T postgres psql -U marmot -d shop -c "CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TIMESTAMPTZ DEFAULT now()); CREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id), total NUMERIC(10,2), status TEXT DEFAULT 'new', ordered_at TIMESTAMPTZ DEFAULT now()); CREATE TABLE order_items (id SERIAL PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id), sku TEXT NOT NULL, qty INTEGER NOT NULL, price NUMERIC(10,2)); INSERT INTO customers(name,email) VALUES ('zhang','zhang@example.com'),('li','li@example.com'); INSERT INTO orders(customer_id,total) VALUES (1,99.5),(2,33.0); INSERT INTO order_items(order_id,sku,qty,price) VALUES (1,'SKU-A',2,49.75),(2,'SKU-B',1,33.0);"
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 2
INSERT 0 2
INSERT 0 2
$ docker compose exec -T postgres psql -U marmot -d shop -c "\dt"
           List of relations
 Schema |    Name     | Type  | Owner  
--------+-------------+-------+--------
 public | customers   | table | marmot
 public | order_items | table | marmot
 public | orders      | table | marmot
(3 rows)

创建服务端摄取管道(PostgreSQL 插件,主机名即栈内服务名 postgres)并触发,实测回显:

$ curl -s -X POST http://localhost:8141/api/v1/ingestion/schedules \
    -H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
    -d '{"name":"shop-postgres","plugin_id":"postgresql","enabled":true,"config":{"host":"postgres","port":5432,"user":"marmot","password":"q1w2e3r4","ssl_mode":"disable","tags":["demo","postgres"]}}'
{"id":"b100d251-b336-401b-82b4-a8a6f3f366d7","name":"shop-postgres","plugin_id":"postgresql","config":{...},"cron_expression":"","enabled":true,"created_by":"b8c1d2dc-74a9-457c-8a64-eba9c3e77fa6","created_at":"2026-11-01T16:02:19.407008Z","updated_at":"2026-11-01T16:02:19.407008Z"}

$ curl -s -X POST http://localhost:8141/api/v1/ingestion/schedules/b100d251-b336-401b-82b4-a8a6f3f366d7/trigger -H "Authorization: Bearer <token>" -w 'HTTP %{http_code}\n'
{"id":"118d102b-4622-4253-a383-710c0d06cfb2","schedule_id":"b100d251-b336-401b-82b4-a8a6f3f366d7","status":"pending",...}
HTTP 201

约 2 秒后运行完成,目录里出现 221 个资产(shop 库 3 表 + 实例上其余数据库的结构,include_databases 默认 true 会编目实例上所有库):

$ marmot assets list --limit 3
┌──────────────────────────────────────┬────────────────┬──────────┬────────────┬────────────────┐
│                  ID                  │      NAME      │   TYPE   │  PROVIDERS │      TAGS      │
├──────────────────────────────────────┼────────────────┼──────────┼────────────┼────────────────┤
│ 347ebba6-22cd-4ea3-a3ce-460241bc6c54 │ postgres       │ Database │ PostgreSQL │ demo, postgres │
│ 521f0510-0d22-4fae-9974-caff5f444218 │ shop           │ Database │ PostgreSQL │ demo, postgres │
│ 131ddb7e-1926-4228-b939-2b884937c72f │ pg_am          │ Table    │ PostgreSQL │ demo, postgres │
└──────────────────────────────────────┴────────────────┴──────────┴────────────┴────────────────┘
Showing 3 of 221 assets

CLI 用法见《Marmot 0.11.1 常用 CLI 命令详解》。若只想编目指定库,把插件配置 include_databases 设为 false。

八、排障实录

排障一:8142 端口被占,起栈失败。 开工前 ss -ltn 实查 8142 空闲,但 docker compose up 仍报:

Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint marmot-0.11.1-server (...): Bind for 0.0.0.0:8142 failed: port is already allocated

ss -ltnp 定位到 docker-proxy 持有 8142,docker ps 确认是同机另一在途服务(Koha 的 apache 侧端口映射 8142→8081)在检查之后、起栈之前占用了该端口。处置:不动他人服务,本栈 Web 端口改用 8141(改 compose 的 ports: 后 docker compose up -d 自动重建生效)。教训:并发部署时端口检查与占用之间存在竞态,起栈失败先 ss -ltnp + docker ps 双查,而不是重启服务。

排障二:generate-encryption-key 输出是多行说明文本。 直接 | tail -1 取到的是文档链接行而非密钥。密钥在「Your encryption key:」之后的缩进行,脚本化提取建议按该锚点行取下一行,或人工复制。

排障三:容器内代理不可达。 给一次性任务设置 HTTPS_PROXY=http://192.168.1.62:7897 下载插件,日志出现:

proxyconnect tcp: dial tcp 192.168.1.62:7897: i/o timeout

该代理只服务于宿主机侧(docker 守护进程拉镜像可用);栈内进程访问它超时,而直连 ghcr 反而可用(服务端容器直连逐个下载成功)。处置:一次性任务不设代理环境变量。

排障四:插件注册表未就绪导致 503。 首启全量安装期间,CLI 侧摄取启动运行被拒:

  Starting run...
  ❌ Failed to start run: request failed with status 503

根因:服务端 loading:true,摄取运行接口尚未可用。处置二选一:等 Plugin loading complete 日志出现,或按第六节预置缓存 + 关闭 autoinstall 后重启(秒级就绪)。

排障五:test-connection 接口 404。 按官方 API 文档(marmotdata.io/api 的 OpenAPI 规范)POST /api/v1/ingestion/test-connection,plugin_id 分别试 marmotdata/postgresql 与 postgresql 均返回 {"error":"Not found"}。该规范对应托管云 API 版本(标题 Marmot API (0.1)),自托管 v0.11.1 未提供此端点;同前缀的 GET/POST /api/v1/ingestion/schedules 与 /trigger 实测可用。结论:连通性验证直接建管道 + 触发一次运行即可,plugin_id 用 /api/v1/plugins 返回的裸名(如 postgresql)。

排障六:默认口令下写接口 403。 见第五节,属安全设计而非故障:admin:admin 登录后 requires_password_change:true,先改密再签发 API Key。

九、运维要点

  • 备份:marmot_data 卷(目录数据全在 PostgreSQL 里,含凭据密文)+ 加密密钥(.env 中 MARMOT_ENCRYPTION_KEY,必须与备份同存,否则密文凭据不可恢复);
  • 升级:改 compose 镜像 tag → docker compose up -d,schema 迁移自动执行;跨版本先备份;
  • 对外暴露:生产建议置于反代之下并设 server.root_url(OAuth/CLI 登录必需),TLS 配置见《Marmot 0.11.1 配置参数详解》;
  • 遥测:默认开启匿名使用遥测(telemetry.enabled: true),介意请在配置中关闭;
  • 收工:docker compose down 停止并移除本栈全部服务与网络,数据卷保留(down -v 才删)。
$ docker compose down
 Container marmot-0.11.1-server Removing 
 Container marmot-0.11.1-server Removed 
 Container marmot-0.11.1-db Removing 
 Container marmot-0.11.1-db Removed 
 Network marmot-0111-stack_marmot-net Removing 
 Network marmot-0111-stack_marmot-net Removed
Marmot 0.11.1 Docker Compose 部署指南 - Quzuo