Mindwendel v0.4.9 Docker Compose 部署指南

使用 Docker Compose 一键部署 Mindwendel v0.4.9:ghcr.io/b310-digital/mindwendel 镜像 + PostgreSQL 16 + rustfs(S3 兼容存储),覆盖 compose 文件编写、环境变量模板、数据库初始化、rustfs 桶创建、WebSocket 连通性验证、Nginx 反代集成、升级与备份恢复。

MindwendelMindwendel实时协作白板2026/10/27

Mindwendel v0.4.9 Docker Compose 部署指南

目标:使用 Docker Compose 一键部署 Mindwendel v0.4.9-kits-1,覆盖 PostgreSQL 依赖、rustfs(S3 兼容存储)配置、环境变量模板、反向代理集成、数据持久化与升级路径。附带 WebSocket 连通性验证步骤。

一、方案总览

Mindwendel 的官方 Docker Compose 方案由三个服务组成:

服务镜像职责端口
mindwendelghcr.io/b310-digital/mindwendel:latest主应用(Phoenix LiveView)4000 (容器内) → 自定义宿主端口
dbpostgres:16PostgreSQL 数据库5432 (容器内) → 127.0.0.1:自定义宿主端口
rustfsrustfs/rustfs:latestS3 兼容对象存储(可选,用于加密文件附件)内部网络访问,不对外暴露

网络架构

┌────────────────────────────────────────────────────┐
│  docker compose network: mindwendel-net            │
│  (subnet 10.89.56.0/24 — 自定义避免冲突)           │
│                                                    │
│  ┌──────────────┐     ┌──────────────┐             │
│  │  mindwendel  │────▶│     db       │             │
│  │  (app:4000)  │     │ (postgres)   │             │
│  └──────┬───────┘     └──────────────┘             │
│         │                                          │
│         └─────────────▶┌──────────────┐             │
│                        │   rustfs     │             │
│                        │  (s3:9000)   │             │
│                        └──────────────┘             │
└────────────────────────────────────────────────────┘
           ▲
           │ host 端口
   8134/tcp (自定义选 8128–8152 区间,ss 确认空闲)

💡 技术拓展:S9 批次约定自定义 Docker 网络的子网从 10.89.56.0/24 – 10.89.75.0/24 区间选取,避免与默认 172.17.0.0/16、常见内网 192.168.0.0/16 冲突。Web 端口从 8128–8152 选取,ss -ltn 确认为空闲端口后写入 compose。

二、前置条件

  • Docker ≥ 24.0
  • Docker Compose v2 (docker compose 插件) ≥ 2.24
  • 宿主有足够磁盘空间(Postgres 数据 + rustfs 数据 + 镜像 ≈ 2 GB)

验证环境:

docker --version
docker compose version
ss -ltn | grep -E ':(8128|8134|5432)' || echo "端口未占用 ✓"

预期输出:

Docker version 27.4.0, build <hash>
Docker Compose version v2.36.0
端口未占用 ✓

三、创建目录与环境文件

3.1 目录结构

mkdir -p /opt/data/quzuo/tmp/mindwendel/compose
mkdir -p /opt/data/quzuo/tmp/mindwendel/compose/postgres-data
mkdir -p /opt/data/quzuo/tmp/mindwendel/compose/rustfs-data
cd /opt/data/quzuo/tmp/mindwendel/compose

3.2 生成安全密钥

# Secret Key Base(64 字符,Phoenix session 加密)
export SECRET_KEY_BASE=$(openssl rand -base64 48 | tr -d '\n')

# Vault 加密密钥(32 字节 base64,附件加密用)
export VAULT_ENCRYPTION_KEY_BASE64=$(openssl rand -base64 32)

# PostgreSQL root 密码
export POSTGRES_PASSWORD=$(openssl rand -base64 16 | tr -d '\n')

# Mindwendel 数据库密码
export MINDWENDEL_DB_PASSWORD=$(openssl rand -base64 16 | tr -d '\n')

# rustfs 存储凭证
export RUSTFS_ACCESS_KEY=$(openssl rand -hex 16)
export RUSTFS_SECRET_KEY=$(openssl rand -hex 32)

echo "生成完毕,已导出为临时环境变量"

3.3 编写 .env 文件

cat > .env <<EOF
# PostgreSQL (db service)
POSTGRES_USER=postgres
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=mindwendel_prod
PGDATA=/var/lib/postgresql/data/pgdata

# Mindwendel DB 凭证
MINDWENDEL_DB_NAME=mindwendel_prod
MINDWENDEL_DB_USER=mindwendel
MINDWENDEL_DB_PASSWORD=${MINDWENDEL_DB_PASSWORD}

# Mindwendel 安全密钥
SECRET_KEY_BASE=${SECRET_KEY_BASE}
VAULT_ENCRYPTION_KEY_BASE64=${VAULT_ENCRYPTION_KEY_BASE64}

# rustfs (S3 兼容后端)
RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY}
RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}
RUSTFS_REGION=local

# Mindwendel URL 配置
# 本机无域名时先用 localhost + HTTP 验证
MW_URL_HOST=localhost
MW_URL_PORT=8134
MW_URL_SCHEME=http
MW_ENDPOINT_HTTP_PORT=4000

# 功能开关
MW_DEFAULT_LOCALE=en
MW_FEATURE_BRAINSTORMING_REMOVAL_AFTER_DAYS=30
MW_FEATURE_IDEA_FILE_UPLOAD=false
EOF
chmod 600 .env
echo ".env 文件已生成 ✓"

四、编写 docker-compose.yml

# /opt/data/quzuo/tmp/mindwendel/compose/docker-compose.yml
name: mindwendel-v049-stack

networks:
  mindwendel-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.56.0/24
          gateway: 10.89.56.1

volumes:
  mindwendel_pgdata:
  mindwendel_rustfs:

services:
  mindwendel:
    image: ghcr.io/b310-digital/mindwendel:latest
    container_name: mindwendel-v049-app
    restart: unless-stopped
    networks:
      - mindwendel-net
    environment:
      # ---- 数据库连接 ----
      DATABASE_HOST: db
      DATABASE_PORT: "5432"
      DATABASE_NAME: ${MINDWENDEL_DB_NAME}
      DATABASE_USER: ${MINDWENDEL_DB_USER}
      DATABASE_USER_PASSWORD: ${MINDWENDEL_DB_PASSWORD}
      DATABASE_SSL: "false"
      POOL_SIZE: "10"
      DATABASE_TIMEOUT: "15000"

      # ---- Phoenix Endpoint ----
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      URL_HOST: ${MW_URL_HOST}
      URL_PORT: ${MW_URL_PORT}
      URL_SCHEME: ${MW_URL_SCHEME}
      MW_ENDPOINT_HTTP_PORT: "4000"

      # ---- 功能开关 ----
      MW_DEFAULT_LOCALE: ${MW_DEFAULT_LOCALE}
      MW_FEATURE_BRAINSTORMING_REMOVAL_AFTER_DAYS: ${MW_FEATURE_BRAINSTORMING_REMOVAL_AFTER_DAYS}
      MW_FEATURE_IDEA_FILE_UPLOAD: ${MW_FEATURE_IDEA_FILE_UPLOAD}
      MW_FEATURE_LEGAL_PRIVACY_LINKS: "false"

      # ---- 文件存储(MW_FEATURE_IDEA_FILE_UPLOAD=true 时启用)----
      OBJECT_STORAGE_SCHEME: "http://"
      OBJECT_STORAGE_HOST: rustfs
      OBJECT_STORAGE_PORT: "9000"
      OBJECT_STORAGE_REGION: ${RUSTFS_REGION}
      OBJECT_STORAGE_BUCKET: mindwendel
      OBJECT_STORAGE_USER: ${RUSTFS_ACCESS_KEY}
      OBJECT_STORAGE_PASSWORD: ${RUSTFS_SECRET_KEY}

      # ---- 加密 ----
      VAULT_ENCRYPTION_KEY_BASE64: ${VAULT_ENCRYPTION_KEY_BASE64}

      # ---- AI 功能(默认关闭)----
      MW_AI_ENABLED: "false"

      # ---- 应用 URL ----
      PHX_SERVER: "true"

    ports:
      - "127.0.0.1:8134:4000"
    volumes:
      - mindwendel_pgdata:/var/lib/postgresql/data
    depends_on:
      db:
        condition: service_healthy
      rustfs:
        condition: service_started
    healthcheck:
      test: ["CMD", "/app/bin/mindwendel", "eval", "MindwendelWeb.Endpoint.status()"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

  db:
    image: postgres:16-alpine
    container_name: mindwendel-v049-db
    restart: unless-stopped
    networks:
      - mindwendel-net
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${MINDWENDEL_DB_NAME}
      PGDATA: /var/lib/postgresql/data/pgdata
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - mindwendel_pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${MINDWENDEL_DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

  rustfs:
    image: rustfs/rustfs:latest
    container_name: mindwendel-v049-rustfs
    restart: unless-stopped
    networks:
      - mindwendel-net
    environment:
      RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY}
      RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY}
      RUSTFS_REGION: ${RUSTFS_REGION}
    volumes:
      - mindwendel_rustfs:/data
    # 注意:rustfs 不对外暴露端口,mindwendel 通过内部网络访问

⚠️ 重要安全提示:compose 文件中 ports 绑定为 127.0.0.1:8134:4000 和 127.0.0.1:5432:5432,仅允许本机访问。如需外网访问,应通过反向代理转发到 0.0.0.0,或直接改为 "8134:4000" 但请确保防火墙限制来源。

五、启动部署

5.1 拉起容器

cd /opt/data/quzuo/tmp/mindwendel/compose
docker compose up -d

预期输出(节选):

Creating network "mindwendel-v049-stack_mindwendel-net" with driver "bridge"
Creating volume "mindwendel-v049-stack_mindwendel_pgdata" with default driver
Creating volume "mindwendel-v049-stack_mindwendel_rustfs" with default driver
Creating mindwendel-v049-db ... done
Creating mindwendel-v049-rustfs ... done
Creating mindwendel-v049-app ... done

5.2 等待健康检查通过

docker compose ps

预期输出(运行 60 秒后):

NAME                    IMAGE                                   STATUS          PORTS
mindwendel-v049-app     ghcr.io/b310-digital/mindwendel:latest   Up 60 seconds   127.0.0.1:8134->4000/tcp
mindwendel-v049-db      postgres:16-alpine                      Up 65 seconds   healthy         127.0.0.1:5432->5432/tcp
mindwendel-v049-rustfs  rustfs/rustfs:latest                    Up 65 seconds

5.3 数据库初始化(首次部署必需)

compose 启动时 Postgres 已初始化数据库,但 Mindwendel 还需要创建业务用户和运行迁移。

# 在 db 容器内创建应用用户
docker exec -it mindwendel-v049-db psql -U postgres <<'SQL'
CREATE USER mindwendel WITH PASSWORD current_setting('app.envvar_MINDWENDEL_DB_PASSWORD');
GRANT ALL PRIVILEGES ON DATABASE mindwendel_prod TO mindwendel;
\c mindwendel_prod
GRANT ALL ON SCHEMA public TO mindwendel;
SQL

或者使用 compose exec:

docker compose exec db psql -U postgres -c "CREATE USER mindwendel WITH PASSWORD '${MINDWENDEL_DB_PASSWORD}';"
docker compose exec db psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE mindwendel_prod TO mindwendel;"
docker compose exec db psql -U postgres -d mindwendel_prod -c "GRANT ALL ON SCHEMA public TO mindwendel;"

5.4 执行数据库迁移

docker compose exec mindwendel /app/bin/mindwendel eval 'Mindwendel.Release.migrate'

预期输出:

... migrated: 20250215000001_create_brainstormings
... migrated: 20250215000002_create_ideas
...
Migrated 7 new migrations

5.5 rustfs 桶创建(开启文件附件时需要)

docker compose exec mindwendel /app/bin/mindwendel rpc 'ExAws.S3.put_bucket("mindwendel", "local") |> ExAws.request!() |> IO.inspect()'

预期输出:

{:ok, %{status_code: 200, headers: [...], body: ""}}

六、验证部署

6.1 HTTP 健康检查

curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8134/
curl -s http://localhost:8134/ | grep -o '<title>[^<]*</title>'

预期输出:

200
<title>mindwendel</title>

6.2 LiveView WebSocket 连通性验证

Mindwendel 的实时协作基于 Phoenix LiveView 协议,使用 phx-js 前缀的 WebSocket 帧。我们可以从浏览器或命令行验证 WebSocket 握手和升级。

# 使用 curl 模拟 WebSocket 握手请求
curl -sv \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://localhost:8134/live \
  2>&1 | grep -E '(101 Switching Protocols|Upgrade|Sec-WebSocket-Accept)'

预期输出(节选):

< HTTP/1.1 101 Switching Protocols
< Upgrade: websocket
< Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

💡 技术拓展:101 Switching Protocols 是 WebSocket 协议升级成功的标志。Phoenix LiveView 在 /live 端点上处理 WebSocket 连接,客户端发送的帧格式为 phx-js 协议(比原始 Phoenix Channels 协议更精简)。连接建立后,Mindwendel 的每个头脑风暴页面会创建一个 LiveView 进程,通过 pub/sub 广播事件给同页面的其他用户。

6.3 浏览器端完整验证

  1. 浏览器访问 http://localhost:8134/
  2. 点击 "Start brainstorming"
  3. 输入挑战题(如 "今天晚上吃什么?"),点击 "Start"
  4. 在标签页 A 输入一个想法(如 "火锅")
  5. 打开标签页 B 访问同一个头脑风暴 URL
  6. 在标签页 B 输入另一个想法(如 "烧烤")
  7. 验证两个标签页的想法实时同步,无需刷新

6.4 容器日志观察

docker compose logs -f mindwendel | head -30

预期输出(节选):

[info] GET /brainstorming/new
[info] Sent 200 in 10ms
[info] CONNECTED TO PUB/SUB mindwendel:brainstorming:...
[info] LiveView session started for ...
[info] INSERT INTO "ideas" ...
[info] UPDATE "brainstormings" SET ...

七、Nginx 反向代理集成

生产环境需要 HTTPS 终止 + WebSocket 升级支持。

7.1 在 compose 中加 Nginx 服务(可选方案)

# 追加到 docker-compose.yml 的 services: 下
  nginx:
    image: nginx:alpine
    container_name: mindwendel-v049-nginx
    restart: unless-stopped
    networks:
      - mindwendel-net
    depends_on:
      - mindwendel
    ports:
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro

7.2 或使用宿主 Nginx(推荐)

server {
    listen 80;
    server_name mindwendel.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name mindwendel.example.com;

    ssl_certificate     /etc/letsencrypt/live/mindwendel.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mindwendel.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8134;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "Upgrade";
        proxy_read_timeout                 86400s;
        proxy_send_timeout                 86400s;
    }
}

⚠️ 同时需要把 compose 中的端口改为 "0.0.0.0:4000:4000"(不对外暴露,由 Nginx 通过内部网络访问),或者保持原样用 127.0.0.1:4000 + 宿主 Nginx 反代。

八、升级与数据迁移

8.1 镜像升级

cd /opt/data/quzuo/tmp/mindwendel/compose

# 1. 拉新镜像
docker compose pull mindwendel

# 2. 停止应用
docker compose stop mindwendel

# 3. 执行数据库迁移(升级可能引入新表/新字段)
docker compose run --rm mindwendel /app/bin/mindwendel eval 'Mindwendel.Release.migrate'

# 4. 重新启动
docker compose up -d mindwendel

# 5. 清理旧镜像
docker image prune -f

8.2 数据备份与恢复

备份 PostgreSQL:

docker compose exec db pg_dump -U postgres mindwendel_prod > mindwendel-backup-$(date +%Y%m%d).sql

恢复 PostgreSQL:

cat mindwendel-backup-20261027.sql | docker compose exec -T db psql -U postgres mindwendel_prod

8.3 备份 rustfs(文件附件)

# rustfs 数据存储在 mindwendel_rustfs 卷中
docker run --rm -v mindwendel_rustfs:/data -v $(pwd):/backup alpine tar czf /backup/mindwendel-rustfs-$(date +%Y%m%d).tar.gz -C /data .

九、日常运维

日志

# 实时跟踪 mindwendel 日志
docker compose logs -f mindwendel

# 仅看错误级别
docker compose logs -f mindwendel 2>&1 | grep -i error

# PostgreSQL 日志
docker compose logs -f db

进入容器调试

# 进入 mindwendel 容器 shell
docker compose exec mindwendel sh

# 进入 Elixir 远程控制台(连接正在运行的 OTP 进程)
docker compose exec mindwendel /app/bin/mindwendel remote

清理(收工清理实验环境)

cd /opt/data/quzuo/tmp/mindwendel/compose

# 停止并删除容器、网络
docker compose down

# 删除数据卷(⚠️ 会删除所有数据,仅实验环境用)
docker compose down -v

# 删除镜像(⚠️ 确认不再需要)
docker rmi ghcr.io/b310-digital/mindwendel:latest postgres:16-alpine rustfs/rustfs:latest

# 清理残留网络
docker network ls | grep mindwendel
docker network rm mindwendel-v049-stack_mindwendel-net 2>/dev/null || echo "网络已清理"

十、常见问题

DATABASE_SSL 警告

[warning] Environment variable DATABASE_SSL has invalid value: "false". Expected "true" or "false". Defaulting to "true".

在 compose 中将 DATABASE_SSL 显式设为字符串 "false"(注意 YAML 布尔类型和字符串的区别,必须加引号)。

数据库用户权限不足

[error] Postgrex.Protocol ... password authentication failed for user "mindwendel"

重新执行 §5.3 的用户创建步骤,确认密码与 .env 中 MINDWENDEL_DB_PASSWORD 一致。

LiveView 连接超时断开

反代层必须设置 proxy_read_timeout / proxy_send_timeout ≥ 86400s。无反代时检查防火墙是否有会话超时。

rustfs 桶不存在

[error] %ExAws.S3.Exceptions.NoSuchBucket{...}

执行 §5.5 创建桶,或在 .env 中设置 MW_FEATURE_IDEA_FILE_UPLOAD=false 暂时关闭附件功能。