Plainpad 1.2.0 Docker Compose 部署实战

官方镜像 alextselegidis/plainpad + mysql:8.0 的 Docker Compose 部署 Plainpad 1.2.0 全流程实测:固定 APP_KEY 生成、首启自动安装与迁移播种日志逐行解读、首登与验证闭环、环境变量表、备份升级运维与六条实测排障实录(2026-11-02)。

PlainpadPlainpadDocker Compose自托管PHPMySQL2026/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/plainpadPHP 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 应用。以实测为准,勿被文档版本号误导。

三、前置条件

  1. Docker 与 Compose v2+ 可用:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
  1. 目标端口空闲(本例 8139):
ss -ltn | grep ':8139'
# (无输出即空闲)
  1. 自定义网络子网不冲突(本例取 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. 拉取镜像(首次约 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/mysql bind 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_KEYcompose 环境变量(必须固定)保留(前提是你复用了同一个 key)
.env、storage/(日志、file 驱动的会话与缓存)plainpad 服务文件系统内重新生成,在线会话失效需重登

⚠️ 注意:官方文档明确提醒——不要在应用内 Settings→Update 升级:更新写入服务文件系统,重建后即丢失且数据库结构未跟进。正确姿势是「拉新镜像 → 重建 → php artisan refresh」(见 10.4)。

六、环境变量表

plainpad 服务支持的官方环境变量(来源:官方 Docker 文档 + 镜像 ENV 实查):

变量默认值(镜像内)作用映射到 .env
APP_KEY空(空则自动生成)加密密钥派生,必须固定APP_KEY
BASE_URLhttp://localhost应用访问前缀APP_URL
DEBUG_MODEFALSE调试开关,仅排障时 TRUEAPP_DEBUG
DB_HOSTdb数据库主机DB_HOST
DB_NAMEplainpad库名DB_DATABASE
DB_USERNAMEroot用户名DB_USERNAME
DB_PASSWORDsecret口令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 使用详解》 篇)。

十二、参考链接

Plainpad 1.2.0 Docker Compose 部署实战 - Quzuo