Plainpad 1.2.0 Docker Compose 部署实战
官方镜像 alextselegidis/plainpad + mysql:8.0 的 Docker Compose 部署 Plainpad 1.2.0 全流程实测:固定 APP_KEY 生成、首启自动安装与迁移播种日志逐行解读、首登与验证闭环、环境变量表、备份升级运维与六条实测排障实录(2026-11-02)。
Plainpad 1.2.0 Docker Compose 部署实战
目标:用官方镜像 alextselegidis/plainpad 以 Docker Compose 从零部署 Plainpad 1.2.0 并走通首登与升级/备份运维,全部命令与回显为 2026-11-02 实测。
一、环境信息
- 操作系统:Ubuntu 26.04.1 LTS(Resolute Raccoon),x86_64
- Docker:Docker version 29.9.2, build a82e528;Docker Compose version v5.6.1
- 部署栈:
plainpad(alextselegidis/plainpad:latest,镜像内应用版本 1.2.0,926MB)+mysql(mysql:8.0,1.09GB) - 对外端口:宿主 8139 → plainpad 服务 80;自定义网络显式子网 10.89.58.0/24
- compose 项目目录:
/opt/data/quzuo/tmp/plainpad/stack/,MySQL 数据 bind mount 到./data/mysql - 镜像拉取走代理 192.168.1.62:7897(Docker 守护进程已配置,直连超时即降级代理渠道)
二、镜像选型说明
Plainpad 官方发布渠道有两个运行时载体:
| 载体 | 内容 | 适用 |
|---|---|---|
| GitHub Releases 发布包(zip) | 纯应用源码,需自备 PHP 8.2+/MySQL | 已有 LAMP/宝塔类主机 |
Docker Hub alextselegidis/plainpad | PHP 8.5.12 + Apache 2.4.69(Debian)+ 应用,80 端口 | 官方推荐的快速部署 |
本文采用官方镜像。数据库侧官方文档示例为 mysql:8.0(镜像 .env.example 固定 DB_CONNECTION=mysql,入口脚本以 MySQL PDO 等待就绪——不支持 SQLite/PostgreSQL,与部分资料流传的「内置 SQLite」说法不符,实测以官方文档为准)。
⚠️ 注意:官方 Docker 文档页写有「The current image is version 1.1.1」,为文档滞后表述。实查 latest 镜像内
config/app.php为'version' => env('APP_VERSION', '1.2.0'),即 latest 已含 1.2.0 应用。以实测为准,勿被文档版本号误导。
三、前置条件
- Docker 与 Compose v2+ 可用:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
- 目标端口空闲(本例 8139):
ss -ltn | grep ':8139'
# (无输出即空闲)
- 自定义网络子网不冲突(本例取 10.89.58.0/24,先查已占网段):
docker network ls --format '{{.Name}}' | grep -vE '^(bridge|host|none)$'
# marmot-0111-stack_marmot-net
# papermerge-353-stack_default
- 拉取镜像(首次约 1 分钟):
docker pull alextselegidis/plainpad:latest
实测回显(节选):
Digest: sha256:e3503aa017ab98734bf4ab623930636380390ebdc5f0f4ed60809faae5a53fd9
Status: Downloaded newer image for alextselegidis/plainpad:latest
docker.io/alextselegidis/plainpad:latest
四、部署步骤
4.1 生成应用密钥 APP_KEY
Plainpad 用 APP_KEY 派生用户笔记加密密钥,必须一次生成、永久复用:
echo "base64:$(openssl rand -base64 32)"
# base64:vB6IesmBqpSCj5ruR1H0PjcSecgiBVNtdBjCNdRaeI0=
4.2 编写 docker-compose.yml
项目目录 /opt/data/quzuo/tmp/plainpad/stack/,文件内容(实测采用版):
name: plainpad-1.2.0-stack
services:
plainpad:
image: alextselegidis/plainpad:latest
container_name: plainpad-1.2.0-app
depends_on:
- mysql
ports:
- '8139:80'
environment:
- APP_KEY=base64:vB6IesmBqpSCj5ruR1H0PjcSecgiBVNtdBjCNdRaeI0=
- BASE_URL=http://localhost:8139
- DEBUG_MODE=FALSE
- DB_HOST=mysql
- DB_NAME=plainpad
- DB_USERNAME=root
- DB_PASSWORD=secret
networks:
- plainpad-net
mysql:
image: mysql:8.0
container_name: plainpad-1.2.0-db
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=plainpad
volumes:
- ./data/mysql:/var/lib/mysql
networks:
- plainpad-net
networks:
plainpad-net:
driver: bridge
ipam:
config:
- subnet: 10.89.58.0/24
逐项说明:
name:显式项目名——多组件并行时防止 down 误删他栈(版本号的点号会被 compose 归一化,见十一、11.2);BASE_URL决定应用生成链接的前缀,生产应写真实域名(如https://notes.example.com);DB_PASSWORD=secret与 MySQL 服务MYSQL_ROOT_PASSWORD=secret对应,官方文档示例值,生产请换强口令并保持两处一致;./data/mysqlbind mount 保证数据库落宿主目录,便于备份与迁移。
4.3 启动
cd /opt/data/quzuo/tmp/plainpad/stack
mkdir -p data/mysql
docker compose up -d
实测回显:
Network plainpad-120-stack_plainpad-net Creating
Network plainpad-120-stack_plainpad-net Created
Container plainpad-1.2.0-db Creating
Container plainpad-1.2.0-db Created
Container plainpad-1.2.0-app Creating
Container plainpad-1.2.0-app Created
Container plainpad-1.2.0-db Starting
Container plainpad-1.2.0-db Started
Container plainpad-1.2.0-app Starting
Container plainpad-1.2.0-app Started
4.4 自动安装过程(无需手工跑安装向导)
plainpad 服务的入口脚本在启动时自动完成:复制 .env.example → .env、按环境变量改写数据库配置、等待 MySQL 就绪(最长 60 秒)、执行迁移与播种、最后拉起 Apache。首启日志实测(节选):
SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, Host: mysql, Port: 3306, Database: plainpad, ...)
INFO Preparing database.
Creating migration table ...................................... 17.80ms DONE
INFO Running migrations.
2019_11_19_081328_create_users_table .......................... 17.23ms DONE
2019_11_19_081344_create_notes_table .......................... 47.02ms DONE
2019_11_19_085128_create_settings_table ....................... 13.84ms DONE
2020_02_21_100738_create_sessions_table ....................... 17.01ms DONE
2024_01_01_000000_create_password_resets_table ................ 27.17ms DONE
2026_10_19_000000_add_lookup_indexes .......................... 38.64ms DONE
INFO Seeding database.
Database\Seeders\UsersSeeder ....................................... RUNNING
Login with the default admin user (email: admin@example.org & password: 12345678).
Database\Seeders\UsersSeeder .................................... 66 ms DONE
Database\Seeders\SettingsSeeder .................................... RUNNING
Database\Seeders\SettingsSeeder ................................ 22 ms DONE
AH00163: Apache/2.4.69 (Debian) PHP/8.5.12 configured -- resuming normal operations
⚠️ 注意:开头的
Connection refused是 MySQL 尚未就绪时的正常重试记录(入口脚本每 2 秒轮询 PDO 连接,最长等 60 秒),随后自动成功,不是故障。首启请等约 30 秒再验证。
4.5 验证与首登
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
plainpad-1.2.0-app alextselegidis/plainpad:latest "docker-entrypoint.sh" plainpad 25 seconds ago Up 25 seconds 0.0.0.0:8139->80/tcp, [::]:8139->80/tcp
plainpad-1.2.0-db mysql:8.0 "docker-entrypoint.s…" mysql 25 seconds ago Up 25 seconds 3306/tcp, 33060/tcp
curl -s -o /dev/null -w 'HTTP %{http_code} size=%{size_download}\n' http://localhost:8139/
# HTTP 200 size=898
返回的应用外壳 <title>Plainpad</title>,SPA 资源实测全部 200:
curl -s -o /dev/null -w '%{http_code} %{size_download}B\n' http://localhost:8139/static/js/main.e1786878.js
# 200 732345B
首登验证(网页登录框背后就是这条 API 请求,实测回显):
curl -s -X POST http://localhost:8139/api.php/v1/sessions \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.org","password":"12345678"}'
# {"user_id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","expires_at":"2026-11-02 12:20:47","token":"FE5Is0r75P5jZoalOwHKpWi0NlAsAJwNGRWsRqqFNcwtwD9kWCLQk5RDEPxS"}
⚠️ 注意:默认管理员为
admin@example.org / 12345678(官方播种值),登录后立即在 Profile 改邮箱与密码。本实验另建了演示账号root / q1w2e3r4(邮箱 root@example.org,见 《Plainpad 1.2.0 API 使用详解》 篇实测)。
同时可确认安装向导已按设计关闭——.env 存在后 setup.php 返回 404:
curl -s -o /dev/null -w 'setup.php HTTP %{http_code}\n' http://localhost:8139/setup.php
# setup.php HTTP 404
五、数据持久化说明
| 数据 | 位置 | 重建(up -d 换镜像)后 |
|---|---|---|
| 笔记/用户/设置 | MySQL,bind mount ./data/mysql | 保留 |
| APP_KEY | compose 环境变量(必须固定) | 保留(前提是你复用了同一个 key) |
.env、storage/(日志、file 驱动的会话与缓存) | plainpad 服务文件系统内 | 重新生成,在线会话失效需重登 |
⚠️ 注意:官方文档明确提醒——不要在应用内 Settings→Update 升级:更新写入服务文件系统,重建后即丢失且数据库结构未跟进。正确姿势是「拉新镜像 → 重建 →
php artisan refresh」(见 10.4)。
六、环境变量表
plainpad 服务支持的官方环境变量(来源:官方 Docker 文档 + 镜像 ENV 实查):
| 变量 | 默认值(镜像内) | 作用 | 映射到 .env |
|---|---|---|---|
APP_KEY | 空(空则自动生成) | 加密密钥派生,必须固定 | APP_KEY |
BASE_URL | http://localhost | 应用访问前缀 | APP_URL |
DEBUG_MODE | FALSE | 调试开关,仅排障时 TRUE | APP_DEBUG |
DB_HOST | db | 数据库主机 | DB_HOST |
DB_NAME | plainpad | 库名 | DB_DATABASE |
DB_USERNAME | root | 用户名 | DB_USERNAME |
DB_PASSWORD | secret | 口令 | DB_PASSWORD |
⚠️ 注意:镜像 ENV 里还有一批
MAIL_SMTP_*/MAIL_FROM_*默认值,但入口脚本并不处理它们——官方文档明确「Mail is not configured through environment variables」,邮件请在应用内 Settings→Mail 配置(存数据库 settings 表,详见 《Plainpad 1.2.0 配置参数详解》 篇)。LANGUAGE=english为 Apache 基础镜像残留变量,与界面语言无关(界面语言是用户偏好 + 应用默认 locale)。
七、配置参数说明
compose 层之外,Plainpad 的可调面集中在三处:.env 键(15 个,含 APP_REPOSITORY 自动更新源)、应用内 Settings(default_locale 与 mail_*)、每用户偏好(locale/view/line/sort/theme/encrypt)。逐项含义、默认值与生效验证见本栏 《Plainpad 1.2.0 配置参数详解》 篇(C1)。
八、配置文件挂载
官方 compose 示例不挂载应用文件——.env 由入口脚本每次启动在文件系统内重建,笔记数据全在 MySQL。若希望自定义 .env(如改 APP_REPOSITORY 指向内网更新源),可将整个应用目录或 .env 以 volume 覆盖挂载,但要注意:
- 覆盖挂载
.env后入口脚本仍会按环境变量 sed 改写其中 5 个键(APP_URL/APP_DEBUG/DB_*),以环境变量为准; - 不挂载时升级镜像即升级应用;挂载应用目录则升级需自行同步文件,与官方「拉新镜像」升级路径冲突。
结论:常规部署不建议挂载应用配置,用环境变量 + 应用内 Settings 即可覆盖全部日常需求。
九、等价 docker run
compose 两服务的等价手工启动(供无 compose 环境参考;生产建议用 compose 管理网络与持久化):
docker run -d --name plainpad-1.2.0-db \
-e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=plainpad \
-v /opt/data/quzuo/tmp/plainpad/stack/data/mysql:/var/lib/mysql \
--network plainpad-net mysql:8.0
docker run -d --name plainpad-1.2.0-app \
-p 8139:80 \
-e APP_KEY=base64:vB6IesmBqpSCj5ruR1H0PjcSecgiBVNtdBjCNdRaeI0= \
-e BASE_URL=http://localhost:8139 -e DEBUG_MODE=FALSE \
-e DB_HOST=mysql -e DB_NAME=plainpad -e DB_USERNAME=root -e DB_PASSWORD=secret \
--network plainpad-net alextselegidis/plainpad:latest
十、常用运维命令
10.1 查看服务状态与镜像
docker compose ps
docker compose images
实测回显:
CONTAINER REPOSITORY TAG PLATFORM IMAGE ID SIZE CREATED
plainpad-1.2.0-app alextselegidis/plainpad latest linux/amd64 e3503aa017ab 926MB 6 days ago
plainpad-1.2.0-db mysql 8.0 linux/amd64 7dcddc01f13b 1.09GB 6 months ago
10.2 看日志与重启
docker compose logs -f plainpad
docker compose restart plainpad
重启实测:
Container plainpad-1.2.0-app Restarting
Container plainpad-1.2.0-app Started
约 8 秒后验证恢复:
curl -s -o /dev/null -w 'after restart HTTP %{http_code}\n' http://localhost:8139/
# after restart HTTP 200
重启后数据在位验证(users 表两条账号仍在,token 取自 4.5 节登录响应):
export TOKEN=FE5Is0r75P5jZoalOwHKpWi0NlAsAJwNGRWsRqqFNcwtwD9kWCLQk5RDEPxS
curl -s http://localhost:8139/api.php/v1/users -H "Authorization: Bearer $TOKEN"
# [{"id":"7a17c8fd-b84f-4f2c-8ba3-2a484c1a0e1e","name":"Admin","email":"admin@example.org","admin":true,"locale":"en-US","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"created_at":"2026-11-02T00:20:21.000000Z","updated_at":"2026-11-02T00:20:21.000000Z"},{"id":"54ec677a-5344-4402-9189-bcd2e6470a28","name":"Test User","email":"root@example.org","admin":false,"locale":"en-US","view":"compact","line":"full","sort":"modified","theme":"light","encrypt":false,"created_at":"2026-11-02T00:21:48.000000Z","updated_at":"2026-11-02T00:21:48.000000Z"}]
10.3 备份数据库
docker compose exec -T mysql mysqldump -uroot -psecret --databases plainpad > backup_plainpad.sql
实测产物头部:
-- MySQL dump 10.13 Distrib 8.0.46, for Linux (x86_64)
--
-- Host: localhost Database: plainpad
-- ------------------------------------------------------
-- Server version 8.0.46
导出 9242 字节,含 users/notes/settings/sessions/password_resets 全表。恢复用 docker compose exec -T mysql mysql -uroot -psecret < backup_plainpad.sql。
10.4 升级
官方标准三步(应用与库结构同步跟进):
docker compose pull
docker compose up -d
docker compose exec plainpad php artisan refresh
refresh 实测退出码 0(静默执行「迁移 + 缓存刷新」,官方语义:Refresh the Plainpad installation after an update)。
10.5 收尾清理
docker compose down -v
十一、常见问题
11.1 首启日志出现 Connection refused
见 4.4:MySQL 就绪前入口脚本的轮询重试记录,等待即可,无需干预。若超 60 秒仍失败,检查 DB_HOST 是否等于数据库服务名、MySQL 是否健康(docker compose logs mysql)。
11.2 compose 项目名里的点号被吞
name: plainpad-1.2.0-stack 实测被 Compose v5.6.1 归一化为 plainpad-120-stack(点号剥除),网络实际叫 plainpad-120-stack_plainpad-net。按项目名操作(docker compose ls、跨目录 -p)时用归一化后的名字,勿拿原始 name: 硬拼资源名。
11.3 每次重启日志报 ERROR Unable to set application key
ERROR Unable to set application key. APP_KEY is already present in the environment.
无害告警:入口脚本检测到 .env 内 APP_KEY 为占位符时会调用 php artisan key:generate --force,而 Laravel 发现环境变量里已有 APP_KEY 即拒绝覆写(正是我们要求的「固定密钥」行为),随后迁移显示 Nothing to migrate. 正常继续。只要 APP_KEY 由环境变量固定提供,可忽略此 ERROR。
11.4 PHP Deprecated: PDO::MYSQL_ATTR_SSL_CA / AH00558
PHP Deprecated: Constant PDO::MYSQL_ATTR_SSL_CA is deprecated since 8.5, use Pdo\Mysql::ATTR_SSL_CA instead in /var/www/html/config/database.php on line 62
AH00558: apache2: Could not reliably determine the server's fully qualified domain name
前者是应用 config/database.php 用了 PHP 8.5 已废弃常量写法(latest 镜像基础已升到 PHP 8.5.12),仅提示不影响功能;后者是 Apache 未设全局 ServerName 的常规提示。均可忽略,或等上游修复。
11.5 官方文档说镜像是 1.1.1,实际是 1.2.0
文档滞后,以 docker run --rm --entrypoint cat alextselegidis/plainpad:latest /var/www/html/config/app.php 实查版本键为准(见「二、镜像选型说明」)。
11.6 想改邮件/语言但找不到环境变量入口
邮件不走环境变量(第六节注意项):进应用 Settings→Mail 配置;界面默认语言在 Settings 改 default_locale,用户级语言在各自 Profile 改。均可通过 API 完成(见 《Plainpad 1.2.0 API 使用详解》 篇)。
十二、参考链接
- 官方 Docker 文档:https://plainpad.org/docs/admin/docker/ (2026-11-02 实查)
- 官方安装文档:https://plainpad.org/docs/admin/installation/ (2026-11-02 实查)
- GitHub Releases:https://github.com/alextselegidis/plainpad/releases (1.2.0,changelog 2026-10-21)
- 官方镜像:https://hub.docker.com/r/alextselegidis/plainpad
- 站内同类部署参考:《Memos 0.31.0 Docker Compose 部署指南》、《Many Notes Docker Compose 部署指南》、《SilverBullet Docker Compose 部署实战》