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 反代集成、升级与备份恢复。
Mindwendel v0.4.9 Docker Compose 部署指南
目标:使用 Docker Compose 一键部署 Mindwendel v0.4.9-kits-1,覆盖 PostgreSQL 依赖、rustfs(S3 兼容存储)配置、环境变量模板、反向代理集成、数据持久化与升级路径。附带 WebSocket 连通性验证步骤。
一、方案总览
Mindwendel 的官方 Docker Compose 方案由三个服务组成:
| 服务 | 镜像 | 职责 | 端口 |
|---|---|---|---|
mindwendel | ghcr.io/b310-digital/mindwendel:latest | 主应用(Phoenix LiveView) | 4000 (容器内) → 自定义宿主端口 |
db | postgres:16 | PostgreSQL 数据库 | 5432 (容器内) → 127.0.0.1:自定义宿主端口 |
rustfs | rustfs/rustfs:latest | S3 兼容对象存储(可选,用于加密文件附件) | 内部网络访问,不对外暴露 |
网络架构
┌────────────────────────────────────────────────────┐
│ 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 浏览器端完整验证
- 浏览器访问
http://localhost:8134/ - 点击 "Start brainstorming"
- 输入挑战题(如 "今天晚上吃什么?"),点击 "Start"
- 在标签页 A 输入一个想法(如 "火锅")
- 打开标签页 B 访问同一个头脑风暴 URL
- 在标签页 B 输入另一个想法(如 "烧烤")
- 验证两个标签页的想法实时同步,无需刷新
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 暂时关闭附件功能。