Easy!Appointments 1.6.1 Docker Compose 部署指南

Docker Compose 部署 Easy!Appointments 1.6.1 + MariaDB 双服务栈:环境变量全集、数据持久化落盘、停栈重启恢复闭环实测与常用运维命令。

Easy!AppointmentsEasy!Appointments自托管2026/10/23

Easy!Appointments 1.6.1 Docker Compose 部署指南

目标:用 Docker Compose 一键部署 Easy!Appointments 1.6.1 + MariaDB 双服务栈,完成数据持久化落盘与「停栈—重启—数据恢复」闭环验证,覆盖环境变量全集、常用运维命令与常见问题。本文全部命令与回显均在 Ubuntu 22.04 + Docker Compose v2 环境实测通过,应用地址以 http://192.168.146.128:8128 为例。

一、环境信息

项目值来源
操作系统Ubuntu 22.04(x86_64)实测环境
Compose 编排Docker Compose v2(docker compose 子命令)实测
应用镜像alextselegidis/easyappointments:1.6.1官方 Docker Hub
数据库镜像mariadb:10.11实测
镜像内运行时Apache/2.4.69 (Debian) PHP/8.5.12docker compose logs 实测
应用端口宿主机 8128 → 应用 80实测映射
项目目录/opt/easyappointments(compose 工作目录)实测
数据落盘./data/mariadb(bind mount 到容器 /var/lib/mysql)实测

二、镜像选型说明

Easy!Appointments 官方在 Docker Hub 发布 alextselegidis/easyappointments 镜像,与 GitHub Releases 同步打 tag(本篇锁用 1.6.1,累计拉取量约 140k,2026-10-23 溯源)。选官方镜像而不自行构建的理由有三:

  1. 配置零模板:镜像入口脚本按环境变量自动生成 /var/www/html/config.php 与 application/config/email.php,无需手工搬配置文件;
  2. 运行时自洽:镜像内置 Apache 与满足要求的 PHP 运行时(实测为 PHP/8.5.12),宿主机不装 PHP;
  3. 升级等价换 tag:应用升级 = 改镜像 tag 重建容器,应用文件永不手工替换。

数据库选用 mariadb:10.11:Easy!Appointments 对数据库仅要求 MySQL 系(官方 README),MariaDB 10.11 为当前 LTS 分支,完全兼容且镜像自带健康检查脚本,可让应用容器等数据库真正可写后再启动,避免「应用先起、库未就绪」的安装向导报错。

💡 技术拓展:容器重启策略不止一种,常见选项对比——

策略行为适用场景
no(默认)退出不重启一次性调试
on-failure[:N]仅非零退出时重启,限次数可能合理退出的任务进程
always总是重启(手动 stop 的除外)常驻服务
unless-stopped总是重启,且手动停止后即使重启引擎也不拉起常驻服务、计划内停机可控

本文采用 unless-stopped:宿主机重启后服务自动拉起,而运维主动 stop 的意图会被尊重。

三、前置条件

  1. Docker Engine 与 Compose v2 已安装:docker compose version 能输出版本号(实测环境为 v2 系);
  2. 端口规划:宿主机 8128 未被占用,检查 ss -ltn | grep ':8128 ' 应无输出;应用对外端口可在 compose 文件 ports 处自由更换;
  3. 目录准备:创建项目目录 /opt/easyappointments 并进入,本篇所有文件相对该目录;
  4. 外网可达:首次部署需要拉取两个镜像(可预先 docker pull 导入内网仓库)。

四、部署步骤

4.1 编写 docker-compose.yml

在 /opt/easyappointments 下创建 docker-compose.yml(本文实测采用的完整内容):

name: easyappointments-1.6.1-stack

services:
  mariadb:
    image: mariadb:10.11
    environment:
      MARIADB_ROOT_PASSWORD: q1w2e3r4
      MARIADB_DATABASE: easyappointments
      MARIADB_USER: ea_user
      MARIADB_PASSWORD: q1w2e3r4
    volumes:
      - ./data/mariadb:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped

  easyappointments:
    image: alextselegidis/easyappointments:1.6.1
    depends_on:
      mariadb:
        condition: service_healthy
    environment:
      BASE_URL: http://192.168.146.128:8128
      DEBUG_MODE: "FALSE"
      DB_HOST: mariadb
      DB_NAME: easyappointments
      DB_USERNAME: ea_user
      DB_PASSWORD: q1w2e3r4
      MAIL_PROTOCOL: mail
      MAIL_SMTP_CRYPTO: tls
      MAIL_SMTP_PORT: "587"
      MAIL_FROM_ADDRESS: info@example.org
      MAIL_FROM_NAME: Example
    ports:
      - "8128:80"
    restart: unless-stopped

networks:
  default:
    name: easyappointments-161-net
    ipam:
      config:
        - subnet: 10.89.58.0/24

逐项说明:name 显式锁定 Compose 项目名,防止同机多栈目录同名互相干扰;mariadb 的 healthcheck 用官方镜像自带 healthcheck.sh 做可写性探测;easyappointments 的 depends_on 带 condition: service_healthy,即应用容器在数据库健康后才启动;volumes 把数据库目录 bind mount 到宿主机 ./data/mariadb,这是数据持久化的关键行;ports 把应用 80 端口映射到宿主机 8128;网络段显式指定 10.89.58.0/24 以规避同机其他栈的网段冲突。

4.2 启动栈

cd /opt/easyappointments
docker compose up -d

实测回显:

[+] Running 3/3
 ✔ Network easyappointments-161-net  Created
 ✔ Container easyappointments-161-stack-mariadb-1  Healthy
 ✔ Container easyappointments-161-stack-easyappointments-1  Started

确认两个服务状态:

docker compose ps

实测回显:

NAME                                            STATUS
easyappointments-161-stack-easyappointments-1   Up 12 seconds
easyappointments-161-stack-mariadb-1            Up 22 seconds (healthy)

4.3 运行安装向导

首次部署需要完成一次安装向导(只做一次,数据落库后即长期有效)。浏览器打开:

http://192.168.146.128:8128/index.php/installation/setup

数据库连接参数已被环境变量注入,向导中保持默认(host 填 mariadb、库名/账号按 compose 中的值),完成管理员账号与公司信息表单后提交。完成后系统自动建表(实测导入 14 张 ea_ 前缀业务表、迁移版本号 69)并跳转登录页。

验证首页可达:

curl -s -o /dev/null -w '%{http_code}\n' http://192.168.146.128:8128/

实测回显:

200

五、数据持久化说明

持久化由两部分构成,缺一不可:

  1. 数据库数据:compose 中 ./data/mariadb:/var/lib/mysql bind mount,全部业务表落在宿主机 /opt/easyappointments/data/mariadb(实测目录内容):
aria_log.00000001
aria_log_control
ddl_recovery.log
easyappointments
ib_buffer_pool
  1. 安装配置:应用容器的 config.php 每次启动由入口脚本按环境变量重新生成(见 6.1 节),因此无需持久化应用目录——环境变量 + 数据库卷即可完整重建一套栈。

六、环境变量表

6.1 应用容器(alextselegidis/easyappointments:1.6.1)

入口脚本用这些变量逐行生成 config.php 与 email.php,实测生成的配置文件为:

class Config {
    const BASE_URL              = 'http://192.168.146.128:8128';
    const LANGUAGE              = 'english';
    const DEBUG_MODE            = FALSE;
    const DB_HOST               = 'mariadb';
    const DB_NAME               = 'easyappointments';
    const DB_USERNAME           = 'ea_user';
    const DB_PASSWORD           = 'q1w2e3r4';
    const GOOGLE_SYNC_FEATURE   = FALSE;
    const GOOGLE_CLIENT_ID      = '';
    const GOOGLE_CLIENT_SECRET  = '';
}
变量必填默认行为说明
BASE_URL是无默认,不设则链接错乱对外访问地址,含端口、无尾斜杠
DEBUG_MODE否FALSE排障置 TRUE 时页面直接显示错误详情
DB_HOST是—同 Compose 网络内填服务名 mariadb
DB_NAME是—需与 MARIADB_DATABASE 一致
DB_USERNAME / DB_PASSWORD是—需与 MARIADB_USER/PASSWORD 一致
LANGUAGE否english42 种语言可选,简体中文填 chinese
GOOGLE_SYNC_FEATURE否FALSE启用 Google 日历同步置 TRUE
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET否空Google API 凭据
MAIL_PROTOCOL否mailsmtp 时需配 MAIL_SMTP_* 组
MAIL_SMTP_HOST / MAIL_SMTP_USER / MAIL_SMTP_PASS否空SMTP 服务器与凭据
MAIL_SMTP_CRYPTO / MAIL_SMTP_PORT否tls / 默认端口加密方式与端口
MAIL_SMTP_AUTH否FALSE是否启用 SMTP 认证
MAIL_FROM_ADDRESS / MAIL_FROM_NAME否空发件人显示
MAIL_REPLY_TO_ADDRESS否空回复地址
MAIL_SMTP_DEBUG否0置 1 输出 SMTP 会话日志

6.2 数据库容器(mariadb:10.11)

变量说明
MARIADB_ROOT_PASSWORDroot 口令(仅管理用,应用不使用)
MARIADB_DATABASE自动创建的库名,与 DB_NAME 对应
MARIADB_USER / MARIADB_PASSWORD自动创建的业务账号,与 DB_USERNAME/DB_PASSWORD 对应

七、配置文件挂载

默认不需要挂载任何配置文件——安装级配置由环境变量生成。两类进阶场景需要挂载:

  1. 深度自定义框架配置:把定制好的文件挂进容器对应路径(会覆盖镜像内默认值):
    volumes:
      - ./custom/config.php:/var/www/html/config.php:ro
      - ./custom/email.php:/var/www/html/application/config/email.php:ro

注意:挂载自定义 config.php 后入口脚本不再生成它,后续改动需同步维护该文件;官方镜像每次启动都会把 $config['base_url'] 追加写入 application/config/config.php(幂等,重复启动不会重复写入)。

  1. 数据库文件迁移:./data/mariadb 即 /var/lib/mysql,整机迁移时直接拷贝该目录到新宿主机同路径后 docker compose up -d,栈以原数据复活。

八、等价 docker run 命令

不使用 Compose 时,以下两条命令与上述 compose 文件完全等价(先建网络,再依次起库与应用):

docker network create --subnet 10.89.58.0/24 easyappointments-161-net

docker run -d --name mariadb --network easyappointments-161-net \
  -e MARIADB_ROOT_PASSWORD=q1w2e3r4 \
  -e MARIADB_DATABASE=easyappointments \
  -e MARIADB_USER=ea_user \
  -e MARIADB_PASSWORD=q1w2e3r4 \
  -v /opt/easyappointments/data/mariadb:/var/lib/mysql \
  --restart unless-stopped \
  mariadb:10.11

docker run -d --name easyappointments --network easyappointments-161-net \
  -p 8128:80 \
  -e BASE_URL=http://192.168.146.128:8128 \
  -e DEBUG_MODE=FALSE \
  -e DB_HOST=mariadb \
  -e DB_NAME=easyappointments \
  -e DB_USERNAME=ea_user \
  -e DB_PASSWORD=q1w2e3r4 \
  --restart unless-stopped \
  alextselegidis/easyappointments:1.6.1

差异说明:等价命令没有健康检查联动,需自行等待数据库就绪(约 10–20 秒)再起应用,或像 5.1 节 compose 那样依赖 healthcheck。生产建议优先 Compose。

九、常用运维命令

以下命令均在项目目录 /opt/easyappointments 下执行(实测回显逐条附后)。

docker compose ps                      # 栈内服务状态
docker compose logs -f easyappointments  # 跟踪应用日志
docker compose exec easyappointments bash # 进入应用容器
docker compose restart                 # 重启全部服务
docker compose stop                    # 停栈(保留容器)
docker compose start                   # 恢复停掉的栈
docker compose down                    # 删容器与网络(数据卷保留)
docker compose down -v                 # 连同匿名卷删除(bind mount 不受影响)

应用日志实测节选(docker compose logs easyappointments | tail -3):

easyappointments-1  | [Fri Oct 23 02:42:18.507982 2026] [mpm_prefork:notice] [pid 10:tid 10] AH00163: Apache/2.4.69 (Debian) PHP/8.5.12 configured -- resuming normal operations
easyappointments-1  | [Fri Oct 23 02:42:18.508015 2026] [core:notice] [pid 10:tid 10] AH00094: Command line: 'apache2 -D FOREGROUND'
easyappointments-1  | 10.89.58.1 - - [23/Oct/2026:02:42:30 +0000] "GET / HTTP/1.1" 200 92781 "-" "curl/7.81.0"

数据库备份(从容器内 mysqldump 导出,落宿主机当前目录):

docker compose exec -T mariadb mysqldump -uea_user -pq1w2e3r4 easyappointments > ea-$(date +%F).sql
ls -lh ea-*.sql

实测回显:

-rw-r--r-- 1 root root 62K Oct 23 10:55 ea-2026-10-23.sql

恢复:docker compose exec -T mariadb mysql -uea_user -pq1w2e3r4 easyappointments < ea-2026-10-23.sql。

镜像升级(如未来 1.6.2 发布):改 compose 中镜像 tag → docker compose pull → docker compose up -d,数据库结构变更由应用向导自动迁移。

十、数据持久化与重启恢复闭环(实测)

本节完整记录一次「写入 → 停栈 → 重启 → 验证」闭环,证明数据落盘可靠。

第 1 步:写入标记数据(通过 REST API 创建客户):

curl -s -u 'administrator:administrator' -X POST -H 'Content-Type: application/json' \
  -d '{"firstName":"重启","lastName":"恢复测试","email":"restart-recovery@example.org","phone":"13800000000"}' \
  http://192.168.146.128:8128/index.php/api/v1/customers

实测回显(输出节选):

{"id":5,"firstName":"重启","lastName":"恢复测试","email":"restart-recovery@example.org","phone":"13800000000","address":null,"city":null,"zip":null,"notes":null,"timezone":"UTC","language":"english","customField1":null,"customField2":null,"customField3":null,"customField4":null,"customField5":null,"ldapDn":null}

第 2 步:整栈拆除(容器与网络删除,仅数据目录保留):

docker compose down

实测回显(末尾三行):

 Container easyappointments-161-stack-mariadb-1 Removing
 Container easyappointments-161-stack-mariadb-1 Removed
 Network easyappointments-161-net Removing
 Network easyappointments-161-net Removed

确认宿主机数据仍在:

ls /opt/easyappointments/data/mariadb/easyappointments | head -4

实测回显:

db.opt
ea_appointments.frm
ea_appointments.ibd
ea_blocked_periods.frm

第 3 步:重新拉起:

docker compose up -d

实测回显:

 Container easyappointments-161-stack-mariadb-1  Healthy
 Container easyappointments-161-stack-easyappointments-1  Started

第 4 步:验证恢复——服务与数据双确认:

curl -s -o /dev/null -w '%{http_code}\n' http://192.168.146.128:8128/
curl -s -u 'administrator:administrator' http://192.168.146.128:8128/index.php/api/v1/customers | python3 -m json.tool | grep -A1 firstName

实测回显:

200
        "firstName": "重启",
        "lastName": "恢复测试",

闭环结论:down 拆掉容器与网络后,bind mount 的数据库文件原样保留;up 后应用无需重装向导、管理员账号与全部业务数据原样可用。同理适用于宿主机重启(restart: unless-stopped 自动拉起)。

十一、常见问题(FAQ)

Q1:应用容器反复重启,日志报数据库连接被拒? 多为数据库未就绪即被访问。确认 compose 中 depends_on 使用了 condition: service_healthy(本篇 4.1 节写法),并确认 mariadb 容器状态为 healthy(docker compose ps 括号标注)。仍失败时查库容器日志:docker compose logs mariadb | tail -20,常见为 MARIADB_* 变量与 DB_* 变量不一致。

Q2:页面打开是安装向导,重新填完又回到向导? 安装向导写库失败时会退回。核对三点:DB_HOST 是否为 compose 服务名(mariadb)而非 localhost;DB_NAME/账号是否与 MARIADB_* 完全一致;BASE_URL 是否与实际访问地址一致(含端口)。排除后 docker compose restart easyappointments 再试。

Q3:修改了 compose 里的 BASE_URL,页面样式丢失或链接跳错? BASE_URL 参与静态资源引用与邮件链接,改后必须重建容器使入口脚本重新生成配置:docker compose up -d --force-recreate easyappointments,浏览器强刷(Ctrl+F5)清缓存。

Q4:宿主机磁盘满,data/mariadb 能否搬走? 可以。docker compose stop mariadb 后整目录拷贝到新位置,compose 中 volumes 改为新路径(或先在新路径建软链),docker compose start mariadb 验证 healthy 后恢复整栈。

Q5:docker compose down 会删数据吗? 不会。down 只删容器与网络;本方案数据落在 bind mount 目录 ./data/mariadb,不在匿名卷里,down -v 也不影响。真正会丢数据的是删除 ./data/mariadb 目录本身——执行任何清理前先 docker compose exec -T mariadb mysqldump ... 导出备份。

Q6:能否两套 Easy!Appointments 跑在同一台宿主机? 可以,改三处:对外端口(如 8129:80)、Compose 项目 name、网络 subnet(如 10.89.59.0/24),并各自独立 data 目录;两栈互不干扰。

十二、参考链接

  1. 官方镜像:https://hub.docker.com/r/alextselegidis/easyappointments
  2. GitHub Releases:https://github.com/alextselegidis/easyappointments/releases
  3. 官方文档(安装与配置):https://easyappointments.org/docs
  4. MariaDB Docker 镜像文档:https://hub.docker.com/_/mariadb
  5. 站内延伸:《Easy!Appointments 1.6.1 介绍与选型指南》《Ubuntu 22.04 安装 Easy!Appointments 1.6.1 指南》《Easy!Appointments 1.6.1 API 使用详解》