Meetable Docker Compose 部署指南
Meetable(latest=main@7d2d22ab,镜像 2026-10-16 构建)Docker Compose 部署实测指南:官方四容器栈逐段解析、.env 四个必填项、entrypoint 自动迁移行为、管理员创建(passkey/tinker 双路径)、事件页/iCal/JSON/短链闭环验收、scheduler 缺口补位与升级备份,全部命令逐字回显(实测端口 8158,2026-11-02)
Meetable Docker Compose 部署指南
目标:在单机 Docker 环境把 Meetable 从零部署到功能闭环验收。本文全部配置、命令与输出均为 2026-11-02 实测逐字记录(取证全文见
/opt/data/quzuo/tmp/meetable/evidence.md)。甄别注记:① 批次计划文档称 Meetable 为 Rails 应用、README 提 launchd——实测均不成立,Meetable 是 Laravel/PHP 应用,README 全文无 launchd 字样,官方推荐安装方式即本文的 Docker Compose(README "Setup" 首选)。② 本环境浏览器自动化工具不可用,页面验收降级为 HTTP 状态码 +
<title>+ API 闭环(§7)。③ 计划端口 8157 实测已被占用,改用 8158。
一、实测环境
| 项 | 值 |
|---|---|
| 宿主 OS | Ubuntu 24.04.5 LTS(内核 7.0.0-38-generic) |
| Docker / Compose | 29.9.2 / v5.6.1 |
| 应用镜像 | ghcr.io/aaronpk/meetable:latest(created 2026-10-16T22:21:49Z,即 main@7d2d22ab7f85) |
| Web 端口 | 127.0.0.1:8158(官方默认 127.0.0.1:8080,本实测仅改宿主端口) |
| 部署目录 | /opt/data/quzuo/tmp/meetable/ |
二、获取官方编排文件
README 指定的两条命令(实测逐字执行):
curl -O https://raw.githubusercontent.com/aaronpk/Meetable/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/aaronpk/Meetable/main/.env.example
2.1 官方 docker-compose.yml 逐段解析(实测原文)
公共锚点 x-app-config——app 与 worker 共用同一镜像与配置:
x-app-config: &app-config
image: ghcr.io/aaronpk/meetable:latest
restart: unless-stopped
env_file:
- .env
environment:
# Overriding these to force the app to use the docker compose services instead of what might be defined in .env
- DB_HOST=db
- REDIS_HOST=redis
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
- SESSION_DRIVER=redis
⚠️ 关键设计:environment 列表强制覆盖 .env 中的同名五项——你在 .env 里把 CACHE_DRIVER 改成 file 也不会生效,compose 栈内应用永远走 db 容器与 redis 容器。实测 php artisan about 显示 Cache=redis、Database=mysql,与 .env 无关。
四个服务:
services:
app: # Web 前端(Apache+PHP 同容器)
<<: *app-config
ports: ["127.0.0.1:8080:80/tcp"] # 本实测改 8158;默认只绑回环,公网需反代
depends_on: [db, redis]
volumes: [meetable_storage:/var/www/html/storage/app] # 上传图片持久化
worker: # 队列消费者(头像下载、webmention 抓取等异步任务)
<<: *app-config
command: ["php", "artisan", "queue:work"]
volumes: [meetable_storage:/var/www/html/storage/app]
db: # 业务数据库
image: mariadb:11.8 # LTS 版;MARIADB_AUTO_UPGRADE=1 支持旧数据目录原地升级
environment:
MYSQL_DATABASE: ${DB_DATABASE:-laravel}
MYSQL_USER: ${DB_USERNAME:-meetable}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
volumes: [meetable_db:/var/lib/mysql]
redis:
image: redis:8-alpine
volumes: [meetable_redis:/data]
volumes: {meetable_storage: , meetable_db: , meetable_redis: }
端口映射修改(本实测):"127.0.0.1:8080:80/tcp" → "127.0.0.1:8158:80/tcp"。
三、.env 必填四项
README 明示最小配置集,实测有效:
APP_KEY=<openssl rand -hex 16 的输出> # 32 字符随机串,加密会话/签名 URL 用
APP_URL=http://127.0.0.1:8158 # 无结尾斜杠;影响 permalink/ICS 内 URL
DB_PASSWORD=Me3tBl3-S3cret-Pw # mariadb 业务账户密码(无默认,必须设)
DB_ROOT_PASSWORD=Ro0t-S3cret-Pw # mariadb root 密码(compose 变量插值用)
本实测完整 .env(其余为分类默认值,全量参数解析见 C1):
APP_NAME=Meetable
APP_ENV=production
APP_KEY=0645ee0b0e5d68ae9d97d589bb1b0440
APP_URL=http://127.0.0.1:8158
DB_CONNECTION=mysql
DB_PORT=3306
DB_DATABASE=meetable
DB_USERNAME=meetable
DB_PASSWORD=…
DB_ROOT_PASSWORD=…
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
REDIS_HOST=redis
FILESYSTEM_DRIVER=local
AUTH_METHOD=session
AUTH_SHOW_LOGIN=true
AUTH_SHOW_LOGOUT=true
ALLOW_MANAGE_EVENTS=admins
ALLOW_MANAGE_SITE=admins
四、启动与首次初始化(实测日志)
$ docker compose up -d
Network meetable_default Created
Container meetable-redis-1 Started
Container meetable-db-1 Started
Container meetable-app-1 Started
Container meetable-worker-1 Started
$ docker compose ps
NAME IMAGE STATUS PORTS
meetable-app-1 ghcr.io/aaronpk/meetable:latest Up 127.0.0.1:8158->80/tcp
meetable-db-1 mariadb:11.8 Up 3306/tcp
meetable-redis-1 redis:8-alpine Up 6379/tcp
meetable-worker-1 ghcr.io/aaronpk/meetable:latest Up 80/tcp
app 容器 entrypoint(镜像内 /usr/local/bin/entrypoint.sh)实测行为:mysqladmin ping 轮询等库就绪 → 若首参为 apache2-foreground 则 php artisan storage:link + php artisan migrate --force → exec 原命令拉起 Apache。关键日志:
Waiting for MySQL database to be ready...
Database is ready!
Creating storage symlink...
Running database migrations...
2026_10_15_000000_response_photos_removed ...... 2.14ms DONE (60+ 条全部 DONE)
Starting original command: apache2-foreground
AH00163: Apache/2.4.68 (Debian) PHP/8.2.34 configured -- resuming normal operations
即:首启无需手动 migrate,compose 起来即完成建表(60+ 迁移,users/events/responses/passkeys/discord_notifications 等)。
五、创建管理员
5.1 官方路径(浏览器,passkey)
AUTH_METHOD=session 下访问 /login,实测页面为 "Create Admin User" 表单(name/email + WebAuthn passkey 注册,POST /auth/create-user)。首个注册者即管理员;此后 Web 界面不再允许创建用户。⚠️ passkey 注册依赖真实浏览器的 WebAuthn(https 或 localhost 安全上下文),headless 自动化不可行。
5.2 无浏览器降级路径(tinker,本实测采用)
模型命名空间为旧式 App\User/App\Event(不是 App\Models\User,实测 Class "App\Models\User" not found);create() 受 mass-assignment 限制只写入时间戳(报 Field 'identifier' doesn't have a default value),需 forceCreate:
$ docker exec meetable-app-1 php artisan tinker --execute="
\$u = App\User::forceCreate(['name'=>'Admin','email'=>'admin@example.org',
'identifier'=>'http://example.org/admin','is_admin'=>1,
'default_timezone'=>'Asia/Shanghai']);
echo 'user_id='.\$u->id;"
user_id=1
identifier 为用户唯一 ID(URL 形态)。之后可用 §5.1 的 passkey 流程绑定登录,或按需切换 GitHub/OIDC 认证后由白名单用户登录自动成为管理员(见 C1)。
5.3 建演示事件(同样注意 key 长度)
docker exec meetable-app-1 php artisan tinker --execute="
\$e = App\Event::forceCreate(['key'=>'quzuo1demo01','slug'=>'quzuo-demo-event',
'name'=>'Quzuo Demo Event','status'=>'scheduled','start_date'=>'2026-11-10',
'end_date'=>'2026-11-10','start_time'=>'19:00:00','end_time'=>'20:00:00',
'timezone'=>'Asia/Shanghai','sort_date'=>'2026-11-10 19:00:00',
'location_name'=>'Online','created_by'=>1,'rsvps_enabled'=>1]);
echo 'event_id='.\$e->id;"
⚠️ 实测踩坑:Event::find_from_url() 用正则 [0-9a-zA-Z]{12} 精确匹配 12 位 key;建事件时给了 6 位 quzuo1,事件页与短链一律 404,改成 12 位 quzuo1demo01 后立即 200。Web 界面创建的事件 key 由系统自动生成 12 位,手工造数据必须遵守此约束。
六、"签到/核验"流程按实甄别
Meetable 没有签到二维码(全量路由 grep qr|checkin 无匹配)。其到场登记 = 登录用户在事件页 RSVP(POST /event/{event}/rsvp,yes/if-needed/no),管理员在 /event/{id}/responses 查看与审核。本实测验证的对外闭环是 事件 permalink / 短链 / JSON / iCal 订阅 URL:
GET /2026/11/quzuo-demo-event-quzuo1demo01 → 200 <title>Quzuo Demo Event | November 10, 2026 | Meetable</title>
GET /quzuo1demo01 → 301 → 上列 canonical URL
GET /event/quzuo1demo01.json → 200 {"meeting_url":false,"meeting_state":"none"}
GET /ics/events.ics → 200 VCALENDAR;DTSTART:20261110T110000Z(19:00 CST→UTC 正确)
GET /tag/test.ics → 200
七、部署级验收清单(HTTP 降级验收,实测全过)
| 检查项 | 命令/URL | 实测结果 |
|---|---|---|
| 首页 | curl -s http://127.0.0.1:8158/ | grep '<title>' | 200,Meetable | Meetable;建事件后含 Quzuo Demo Event |
| 归档页 | /archive | 200,Past Events | Meetable |
| 登录入口 | /login | 200,含 Create Admin User 表单 |
| 事件页 | /2026/11/<slug>-<12位key> | 200(§5.3 踩坑修复后) |
| iCal | /ics/events.ics | 200,text/calendar,VEVENT 完整 |
| 队列 | docker exec meetable-worker-1 php artisan tinker --execute="echo DB::table('jobs')->count();" | 0 pending |
| MCP 端点 | POST /mcp(GET 返回 405) | 405(仅 POST,符合预期) |
八、scheduler 缺口与补位(重要)
php artisan schedule:list 实测列出 4 个定时任务(event:notify、discord:notify、discord:sync-events 每分钟;recurring:schedule 每日 0 点),但 compose 栈内无任何进程执行 schedule:run(app 仅 apache2、worker 仅 queue:work,ps 实测确认)。纯 compose 部署下会议提醒、Discord 同步、周期事件生成不会触发。补位方案(任选其一):
# docker-compose.override.yml 追加第五容器
scheduler:
image: ghcr.io/aaronpk/meetable:latest
restart: unless-stopped
env_file: [.env]
entrypoint: ["/bin/sh","-c","while true; do php artisan schedule:run --no-interaction; sleep 60; done"]
depends_on: [db, redis]
volumes: [meetable_storage:/var/www/html/storage/app]
或宿主机 crontab:* * * * * docker exec meetable-app-1 php artisan schedule:run >> /dev/null 2>&1。
九、生产化、升级与备份
- 反代+TLS:端口只绑
127.0.0.1是有意的(README:put a reverse proxy with TLS in front)。Nginx 直接proxy_pass http://127.0.0.1:8158即可(容器内已是 Apache 全栈,无需 fastcgi)。APP_URL同步改为公网 https 域名。 - 升级:无版本发布,升级=滚动 main。
docker compose pull && docker compose up -d,entrypoint 自动migrate --force;先docker run --rm -v meetable_meetable_db:/var/lib/mysql alpine tar czf /backup/db-pre-$(date +%F).tar.gz -C /var/lib/mysql .备份卷(或 mariadb 容器内mariadb-dump)。回滚:给 compose 钉住旧 image digest(本实测 digestsha256:3232653bf985…)。 - 备份三件套:
meetable_db卷(业务数据)、meetable_storage卷(上传图片)、.env(含 APP_KEY,丢失则会话失效、签名链接作废)。
十、常见故障速查
| 现象 | 原因 | 处置 |
|---|---|---|
| 事件页/短链 404 | key 非 12 位字母数字(§5.3) | 修正 key 或走 Web 界面创建 |
| tinker 建用户报 identifier 无默认值 | mass-assignment | 用 forceCreate |
改了 .env 的 CACHE_DRIVER 不生效 | compose 锚点强制覆盖(§2.1) | 改 compose 的 environment |
| 提醒/周期事件不生成 | 栈内无 scheduler(§8) | 补 scheduler 容器 |
| app 容器反复重启 | DB 密码与 mariadb 卷内旧密码不一致 | docker compose down 后清卷重来,或进 mariadb 改密 |
| worker 报 redis 连接失败 | 首启时序 | restart: unless-stopped 会自愈,无需干预 |
十一、关联文档
- 本专栏:《Meetable 介绍与选型指南》、《Meetable latest 配置参数详解》、《Meetable latest 常用 CLI 命令与工具详解》
- 站内同类部署参考:《Gancio 1.28.2 Docker Compose 部署指南》(同生态活动类项目的 compose 部署对照)