InvoicePlane 1.7.2 Docker Compose 部署指南
InvoicePlane 1.7.2 Docker Compose 从零部署实测指南:官方 Containerfile 镜像构建与选型、MariaDB 11 编排、环境变量全表(官方契约)、entrypoint 自动初始化机制、数据持久化与备份恢复、等价 docker run、升级与常见问题排障,全部命令带实测回显。
InvoicePlane 1.7.2 Docker Compose 部署指南
目标:在一台 Ubuntu 22.04 服务器上,用 Docker Compose 从零部署 InvoicePlane 1.7.2(自托管开源发票/报价/收款系统),覆盖应用镜像构建、MariaDB 数据库编排、数据持久化、环境变量配置、首次初始化与部署后逐层验证。全流程已在 Ubuntu 22.04 + Docker Compose v5.5.1 环境实测通过(应用版本与官方最新稳定版一致,2026-10-24 经官方 Releases 核实)。软件定位与选型背景见 《InvoicePlane 介绍与选型指南》。
一、环境信息
本篇实测部署所用的服务器环境与组件版本如下:
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04(x86_64,内核 6.8.0-138-generic) |
| Docker Engine | 29.8.1(build 4a63305) |
| Docker Compose | v5.5.1(v2 插件形态,docker compose 子命令) |
| 应用版本 | InvoicePlane 1.7.2(官方 git tag v1.7.2,提交 aaeea1e) |
| 数据库 | MariaDB 11(mariadb:11 官方镜像,实测服务端版本 11.8.9) |
| 应用监听 | 宿主机 8128 端口 → 应用 80 端口 |
| 部署目录 | /opt/invoiceplane(compose.yaml 所在目录) |
| 数据卷 | 3 个命名卷:uploads / storage / db |
| 初始管理员 | admin@example.com / q1w2e3r4(由 DEFAULT_ADMIN_* 环境变量注入,见第六、七节) |
架构关系:Compose 编排两个服务——app(InvoicePlane 应用,Apache + PHP 8.2 运行时)与 db(MariaDB),同挂一个自定义 bridge 网络 invoiceplane-net,app 依赖 db 健康检查通过后再启动。
二、镜像选型说明
InvoicePlane 官方的容器化发行形态有四种,选型前先分清(信息核实自官方仓库,2026-10-24):
| 形态 | 出处 | 定位 | 本篇是否采用 |
|---|---|---|---|
| 官方一体化 Containerfile | 主仓库 resources/docker/Containerfile | 生产部署形态:php:8.2-apache 基座 + composer/yarn 两段构建,内置 entrypoint.sh 自动完成"生成配置 → 数据库迁移 → 创建默认管理员" | ✅ 采用,自建应用镜像 |
| 官方开发 compose | 主仓库 docker-compose.yml | php-fpm + nginx + mariadb + phpMyAdmin 四件套,面向本地开发调试 | ❌ 面向开发,组件多、非生产形态 |
| InvoicePlane-Docker(ivpldock) | 独立仓库(Laradock 裁剪版) | workspace/php-fpm/nginx/mariadb/redis/beanstalkd 等 9 个服务的开发环境 | ❌ 明确自述"infrastructure, not an application" |
| GitHub Actions 组件镜像 | 主仓库 docker-php-fpm.yml | 按需(workflow_dispatch)构建推送 ghcr 的 php-fpm 组件镜像 | ❌ 面向贡献者的构建产物 |
💡 说明:截至 2026-10-24,官方未在 Docker Hub / ghcr 发布"开箱即用"的一体化生产镜像(官方 CONTAINER_DEPLOYMENT.md 声称有 pre-built image,但主仓库工作流中一体化镜像并无自动发布流水线,ivpldock 亦为开发栈)。因此生产部署的正确姿势是用官方 Containerfile 自建镜像——构建过程完全由官方文件定义,无第三方改动。
镜像构建(在 v1.7.2 源码根目录执行;构建含 composer 依赖安装与 yarn 前端构建,全量构建需数分钟,二次构建命中缓存秒级完成):
# 获取 v1.7.2 官方源码(含官方 Containerfile)
git clone --branch v1.7.2 --depth 1 https://github.com/InvoicePlane/InvoicePlane.git /opt/invoiceplane/src
# 以官方 Containerfile 构建应用镜像(标签可自定,与 compose 中 image 保持一致即可)
docker build -t c8n.io/mpldr/invoiceplane:latest -f resources/docker/Containerfile .
实测构建输出(缓存命中,节选尾部;标签沿用本环境构建命名,读者可换为 invoiceplane:1.7.2 等任意自定标签):
#34 exporting config sha256:35de509222aded7e62079fc913b8ee2c93233aa94f2ab7a0eff52aac65cae94e done
#34 exporting attestation manifest sha256:2ff9e68d7f854c6e9799ccdb2e708e2fe38012a9f2bc2941bd418a0b4a8cf6e1 done
#34 exporting manifest list sha256:6d30b48f4fb84529aea3e3c050e757c645b2823721b6d0a680c14c61953a8bfa done
#34 naming to c8n.io/mpldr/invoiceplane:latest done
#34 unpacking to c8n.io/mpldr/invoiceplane:latest 0.0s done
#34 DONE 0.1s
镜像体积实测:
docker images c8n.io/mpldr/invoiceplane --format '{{.Repository}}:{{.Tag}} {{.Size}}'
c8n.io/mpldr/invoiceplane:latest 1.15GB
官方 Containerfile 关键构成(逐段核实自官方文件):基座 php:8.2-apache;安装并启用 bcmath/gd/mbstring/mysqli/xml/zip 扩展与 a2enmod rewrite;以 composer check-platform-reqs 校验运行时依赖齐备;VOLUME ["/var/www/html/uploads", "/var/www/html/storage"] 声明持久化点;EXPOSE 80;入口 entrypoint.sh + 默认命令 apache2-foreground。
三、前置条件
- Docker Engine 与 Compose v2 插件。验证(实测回显):
docker compose version
Docker Compose version v5.5.1
- 端口可用:应用映射宿主机 8128,先确认未被占用(实测已监听状态即本栈自身):
ss -ltn | grep 8128
LISTEN 0 4096 0.0.0.0:8128 0.0.0.0:*
LISTEN 0 4096 [::]:8128 [::]:*
⚠️ 若输出为空则端口空闲可放行;若被其他进程占用,将 compose 中的
"8128:80"改为其他空闲端口即可。
- 加密密钥:应用必需
ENCRYPTION_KEY(用于加密存储敏感设置),官方文档要求以 openssl 生成:
openssl rand -base64 32
5Qj8mH2xLp9vRk3sT7wY1uN6cZ4aE0bD+fGhIjKlMnO=
⚠️ 上面为本机执行示例回显,你必须自行生成并使用自己的输出值;密钥生成后妥善保管,更换会导致既有加密数据无法解密。
- 磁盘空间:应用镜像约 1.15GB,另需预留数据库与附件增长空间(建议 ≥5GB 可用)。
四、部署步骤
4.1 目录规划与 compose.yaml
生产目录统一放在 /opt/invoiceplane,其中 src/ 为 v1.7.2 官方源码(含官方 Containerfile),根下放部署编排文件 compose.yaml:
/opt/invoiceplane/
├── compose.yaml # 部署编排(下文全文)
└── src/ # v1.7.2 官方源码(镜像构建上下文)
/opt/invoiceplane/compose.yaml 全文(本环境实测运行版本):
name: invoiceplane-172-stack
services:
app:
image: c8n.io/mpldr/invoiceplane:latest
restart: unless-stopped
ports:
- "8128:80"
environment:
IP_URL: http://192.168.146.128:8128
DB_HOSTNAME: db
DB_USERNAME: invoiceplane
DB_PASSWORD: invoiceplane
DB_DATABASE: invoiceplane
ENCRYPTION_KEY: dev-key-change-me-in-production-00
DEFAULT_ADMIN_EMAIL: admin@example.com
DEFAULT_ADMIN_PASSWORD: q1w2e3r4
DEFAULT_ADMIN_NAME: admin
depends_on:
db:
condition: service_healthy
networks:
- invoiceplane-net
volumes:
- invoiceplane-uploads:/var/www/html/uploads
- invoiceplane-storage:/var/www/html/storage
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: invoiceplane
MARIADB_USER: invoiceplane
MARIADB_PASSWORD: invoiceplane
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 10s
interval: 5s
timeout: 5s
retries: 5
networks:
- invoiceplane-net
volumes:
- invoiceplane-db:/var/lib/mysql
networks:
invoiceplane-net:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.89.63.0/24
volumes:
invoiceplane-uploads:
invoiceplane-storage:
invoiceplane-db:
配置逐项说明:
| 配置项 | 说明 |
|---|---|
name: invoiceplane-172-stack | Compose 项目名;生成的卷/网络/服务容器名均以此为前缀,避免与宿主机上其他栈冲突 |
image | 第二节自建的应用镜像标签 |
IP_URL | 站点对外访问地址(无尾部斜杠)。必须与用户实际访问的地址一致,否则登录跳转/公开链接会指错(实测见第十一节 Q1) |
DB_* / ENCRYPTION_KEY | 应用连接数据库与加密密钥;DB_HOSTNAME 用服务名 db 走内网 DNS |
DEFAULT_ADMIN_* | 首次启动自动创建的管理员账号(邮箱/密码/显示名);仅数据库无用户时生效 |
depends_on.condition: service_healthy | 等 MariaDB 健康检查通过再启动应用,避免首启竞态 |
restart: unless-stopped | 开机/异常自愈,人工 stop 后不自动拉起 |
子网 10.89.63.0/24 | 显式固定内网网段,规避与宿主机其他自定义网络冲突 |
⚠️
ENCRYPTION_KEY、MARIADB_ROOT_PASSWORD、DB_PASSWORD在生产环境务必换成强随机值(openssl rand -base64 32),本例为实测演示值。
4.2 启动服务
在 compose.yaml 所在目录启动(首次会按依赖顺序先起 db,健康检查通过后起 app):
cd /opt/invoiceplane && docker compose up -d
首次启动日志(实测,节选;来自应用启动输出,展示自动初始化全过程):
No existing installation detected. Running fresh install...
Base tables installed successfully.
Running pending migrations...
Migrations completed successfully.
Default admin user created.
Email: admin@example.com
即:应用启动时自动完成"建表 → 迁移 → 创建默认管理员",全程无需人工跑安装向导。随后 Apache 进入前台运行:
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 10.89.63.3. Set the 'ServerName' directive globally to suppress this message
[Fri Oct 23 19:37:45.514113 2026] [mpm_prefork:notice] [pid 1:tid 1] AH00163: Apache/2.4.69 (Debian) PHP/8.2.35 configured -- resuming normal operations
⚠️ AH00558 是 Apache 未设全局 ServerName 的常见提示,不影响功能;介意可在 compose 中加
ServerName相关覆盖或忽略。
4.3 状态确认
docker compose -p invoiceplane-172-stack ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
invoiceplane-172-stack-app-1 c8n.io/mpldr/invoiceplane "entrypoint.sh apach…" app 5 hours ago Up 5 hours 0.0.0.0:8128->80/tcp, [::]:8128->80/tcp
invoiceplane-172-stack-db-1 mariadb:11 "docker-entrypoint.s…" db 5 hours ago Up 5 hours (healthy) 3306/tcp
两个服务均 Up,db 显示 (healthy)(健康检查生效)即为正常。
4.4 HTTP 层验证
首页应 307 跳转到登录页(跳转目标正是 IP_URL 拼接的地址,实测):
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://localhost:8128/
307 -> http://192.168.146.128:8128/sessions/login
登录页可访问(实测 200):
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8128/sessions/login
200
4.5 登录验证
使用初始管理员(DEFAULT_ADMIN_EMAIL / DEFAULT_ADMIN_PASSWORD,本例 admin@example.com / q1w2e3r4)在浏览器访问 http://<服务器IP>:8128 完成登录。接口层实测(带 CSRF 令牌的表单登录):
# POST 登录表单(_ip_csrf 取自登录页隐藏域)
curl -s -o /dev/null -w "login_post: %{http_code} -> %{redirect_url}\n" \
-X POST "http://localhost:8128/sessions/login" \
--data-urlencode "_ip_csrf=29a7722a2aad2d909a5a579fac4a2445" \
--data-urlencode "btn_login=true" \
--data-urlencode "email=admin@example.com" \
--data-urlencode "password=q1w2e3r4"
login_post: 303 -> http://192.168.146.128:8128/dashboard
携带会话访问工作台返回 200(实测):
curl -s -b cookies2.txt -o dash.html -w "dashboard: %{http_code}\n" http://localhost:8128/dashboard
grep -oE '<title>[^<]*' dash.html | head -1
dashboard: 200
<title>InvoicePlane
⚠️ 登录页的
_ip_csrf是一次性 CSRF 令牌,每次会话不同,实际测试时需从当次登录页 HTML 中提取;直接浏览器登录无需关心此细节。
至此部署完成。生产环境必做两件事:①立即修改默认管理员密码;②按第十一节 Q6 启用 HTTPS 相关项。
五、数据持久化说明
InvoicePlane 应用有明确的两处"运行时可写目录"(官方 Containerfile 以 VOLUME 声明),加上数据库共三个命名卷:
| 卷名 | 容器内路径 | 内容 |
|---|---|---|
| invoiceplane-172-stack_invoiceplane-uploads | /var/www/html/uploads | 客户文件、Logo、发票附件、导入文档 |
| invoiceplane-172-stack_invoiceplane-storage | /var/www/html/storage | 会话文件、框架缓存、应用日志 |
| invoiceplane-172-stack_invoiceplane-db | /var/lib/mysql | MariaDB 全部数据(账套) |
卷在宿主机上的实际位置可用 docker volume inspect 查看(实测):
docker volume inspect invoiceplane-172-stack_invoiceplane-db --format '{{.Mountpoint}}'
/var/lib/docker/volumes/invoiceplane-172-stack_invoiceplane-db/_data
持久化行为要点:
- 升级/重建不丢数据:
docker compose up -d变更配置重建服务容器时,命名卷原样保留,账套与附件不受影响(本环境实测经历多次重建,数据完好)。 - 二次启动不重复初始化:应用每次启动都会重跑迁移(有则执行、无则秒过),且检测到已有用户时跳过管理员创建——实测重启应用后的日志:
Running pending migrations...
Migrations completed successfully.
Users already exist — skipping default user creation.
- bind mount 备选:如需把数据落到指定磁盘目录,可将三个卷改为宿主机目录挂载(如
-v /opt/invoiceplane/data/db:/var/lib/mysql),注意目录属主需匹配容器内进程用户(db 为 mysql,app 为 www-data)。
六、环境变量表
应用镜像的配置完全由环境变量驱动,启动时由 entrypoint.sh 生成 ipconfig.php,无需手工维护配置文件。以下为官方 CONTAINER_DEPLOYMENT.md 定义的完整契约(来源:官方仓库 v1.7.2,2026-10-24 核实)。
6.1 必需变量(缺一启动即失败,实测报错见第十一节 Q4)
| 变量 | 说明 |
|---|---|
IP_URL | 站点对外基础地址,无尾部斜杠,如 https://invoices.example.com |
DB_HOSTNAME | 数据库主机(Compose 内填服务名 db) |
DB_USERNAME | 数据库用户 |
DB_PASSWORD | 数据库密码 |
DB_DATABASE | 数据库名 |
ENCRYPTION_KEY | 加密密钥,openssl rand -base64 32 生成 |
6.2 可选变量(官方默认值)
| 变量 | 默认值 | 说明 |
|---|---|---|
DB_PORT | 3306 | 数据库端口 |
CI_ENV | production | 设为 development 显示全部 PHP 错误 |
ENABLE_DEBUG | false | 开启高级调试日志 |
SESS_SAVE_PATH | PHP 默认临时目录 | 会话文件目录;建议设为文档根之外的绝对路径 |
SESS_COOKIE_NAME | ip_session | 会话 Cookie 名 |
SESS_TABLE_NAME | ip_sessions | 会话入库时的表名(SESS_DRIVER=database 时用) |
SESS_EXPIRATION | 864000 | 会话有效期(秒,默认 10 天;0 = 关浏览器即过期) |
SESS_MATCH_IP | true | 会话绑定客户端 IP |
SESS_REGENERATE_DESTROY | true | 重建会话 ID 时销毁旧会话(防会话固定) |
COOKIE_SECURE | false | 仅经 HTTPS 发送 Cookie;HTTPS 部署应设 true |
X_FRAME_OPTIONS | SAMEORIGIN | X-Frame-Options 响应头值 |
ENABLE_X_CONTENT_TYPE_OPTIONS | true | 启用 nosniff 头 |
LEGACY_CALCULATION | false | 是否沿用 1.6.3 前的税/折扣计算口径;生成合规电子发票 XML 必须为 false |
ENABLE_INVOICE_DELETION | false | 允许彻底删除发票 |
DISABLE_READ_ONLY | false | 关闭已发送发票的只读锁定 |
PASSWORD_RESET_IP_MAX_ATTEMPTS | 5 | 密码重置按 IP 限流的窗口内次数上限 |
PASSWORD_RESET_IP_WINDOW_MINUTES | 60 | IP 限流时间窗(分钟) |
PASSWORD_RESET_EMAIL_MAX_ATTEMPTS | 3 | 按邮箱限流次数上限 |
PASSWORD_RESET_EMAIL_WINDOW_HOURS | 1 | 邮箱限流时间窗(小时) |
PASSWORD_RESET_TOKEN_EXPIRY_MINUTES | 15 | 密码重置链接有效期(分钟) |
CUSTOM_TEMPLATES_FOLDER | — | 自定义模板根目录绝对路径(须镜像内置结构 invoice_templates/pdf/ 等) |
CUSTOM_INVOICE_TEMPLATES_PDF | — | 自定义 PDF 发票模板白名单(逗号分隔,名称不含 .php) |
CUSTOM_INVOICE_TEMPLATES_PUBLIC | — | 自定义网页发票模板白名单 |
CUSTOM_QUOTE_TEMPLATES_PDF | — | 自定义 PDF 报价模板白名单 |
CUSTOM_QUOTE_TEMPLATES_PUBLIC | — | 自定义网页报价模板白名单 |
SEC_STRIP_EXIF_FROM_IMAGES | false | 上传图片剥离 EXIF 元数据(GPS/时间等) |
SUMEX_SETTINGS | false | 启用瑞士医疗发票(Sumex)定制 |
SUMEX_URL | — | Sumex XML 后端地址(提交 XML 换取 PDF) |
ENCRYPTION_CIPHER | AES-256 | 加密算法 |
6.3 默认管理员变量(仅首次初始化生效)
| 变量 | 默认值 | 说明 |
|---|---|---|
DEFAULT_LANGUAGE | english | 应用语言(english/german/french/…,仅全新安装生效) |
DEFAULT_ADMIN_EMAIL | admin@localhost | 默认管理员邮箱(登录账号) |
DEFAULT_ADMIN_PASSWORD | 随机 24 位 | 管理员密码;不设则启动日志打印随机密码,务必第一时间查看 |
DEFAULT_ADMIN_NAME | admin | 管理员显示名 |
⚠️ 只要数据库中已存在任意用户,三个
DEFAULT_ADMIN_*变量即被跳过(实测日志 "Users already exist — skipping default user creation.")。
七、配置参数说明(entrypoint 启动机制)
理解 entrypoint.sh 的行为,就理解了这套部署的全部配置流。应用每次启动按以下顺序执行(逐段核实自官方镜像内 /usr/local/bin/entrypoint.sh):
- 必填校验:6 个必需变量任一缺失立即报错退出(实测输出,见 Q4)。
- 注入清洗(防配置注入):所有写入配置的值若含换行/回车符直接拒绝启动——防止攻击者借环境变量向
ipconfig.php注入额外配置行(如改写DISABLE_SETUP=false)。 - 生成配置文件:以环境变量值生成
/var/www/html/ipconfig.php(本环境实测生成结果节选):
# <?php exit('No direct script access allowed'); ?>
# InvoicePlane Configuration File — generated by entrypoint.sh
IP_URL=http://192.168.146.128:8128
ENABLE_DEBUG=false
CI_ENV=production
X_FRAME_OPTIONS=SAMEORIGIN
ENABLE_X_CONTENT_TYPE_OPTIONS=true
SESS_REGENERATE_DESTROY=false
DEFAULT_LANGUAGE=english
DISABLE_SETUP=true
REMOVE_INDEXPHP=true
DB_HOSTNAME=db
DB_USERNAME=invoiceplane
DB_PASSWORD=invoiceplane
DB_DATABASE=invoiceplane
DB_PORT=3306
CUSTOM_TEMPLATES_FOLDER=/var/www/html/templates/
...
SETUP_COMPLETED=true
- 三个自动加固项(entrypoint 写死,无需配置):
DISABLE_SETUP=true(关闭 Web 安装向导入口)、SETUP_COMPLETED=true(标记安装完成,阻断向导重入)、REMOVE_INDEXPHP=true(URL 去掉 index.php,依赖官方已启用的 mod_rewrite)。 - 模板目录防护:若配置了
CUSTOM_TEMPLATES_FOLDER,自动创建目录并写入.htaccess(Deny from all)阻止 HTTP 直读模板源码。 - 数据库迁移:执行
php index.php setup/cli/migrate——全新库安装全部基础表,已有库只跑增量迁移(幂等,每次启动都会执行)。 - 创建默认管理员:执行
php index.php setup/cli/create_default_user——仅当ip_users表为空时按DEFAULT_ADMIN_*建号。 - 交接 Web 服务:
exec apache2-foreground前台运行。
结论:环境变量是配置的唯一事实源,容器每次启动都会按环境变量重写 ipconfig.php——直接改容器内该文件无效(重启即被覆盖),改配置 = 改 compose 环境变量后 docker compose up -d 重建。这也意味着升级版本时配置零迁移成本。
⚠️ 实测发现的官方细节:
SESS_REGENERATE_DESTROY在官方文档(CONTAINER_DEPLOYMENT.md)中标注默认true,但 entrypoint.sh 脚本的缺省回退值实际是false(本环境未显式设置时,生成的 ipconfig.php 中即为false)。安全敏感部署建议在 compose 中显式设为SESS_REGENERATE_DESTROY: "true",勿依赖缺省值。
八、配置文件挂载
多数配置无需挂载文件(环境变量生成),仅两类场景需要挂载:
8.1 自定义发票/报价模板(官方机制)
将宿主机模板目录挂入容器并在环境变量中登记(官方 CONTAINER_DEPLOYMENT.md 机制):
environment:
CUSTOM_TEMPLATES_FOLDER: /var/www/html/templates/
CUSTOM_INVOICE_TEMPLATES_PDF: "Corporate - Modern,Corporate - Classic"
volumes:
- /opt/invoiceplane/templates:/var/www/html/templates
配套约束(官方安全设计,逐条来自官方 AGENTS.md/文档):
- 模板目录须镜像内置结构:
invoice_templates/pdf/、invoice_templates/public/、quote_templates/pdf/、quote_templates/public/; - 模板文件为
.php(如Corporate - Modern.php),名称必须同时登记进白名单常量才会出现在界面——文件系统永不扫描(防上传恶意 .php 造成远程代码执行); - 模板名仅允许字母/数字/空格/连字符/下划线;含空格时整个值加引号;
- entrypoint 会自动在挂载目录写入
.htaccess(Deny from all)防止 HTTP 直读。
8.2 PHP 运行时微调(按需)
如需调整 PHP 参数(如上传大小上限),将自定义 ini 挂载到 PHP 配置目录即可(php:8.2-apache 官方基座的标准机制):
volumes:
- /opt/invoiceplane/php-upload.ini:/usr/local/etc/php/conf.d/zz-upload.ini
upload_max_filesize = 32M
post_max_size = 32M
⚠️ 应用自身的业务配置不要走 php.ini,一律使用第六节环境变量。
九、等价 docker run
compose.yaml 与以下 docker run 命令组完全等价(配置一一对应,便于理解或临时调试;与 4.1 的 compose 二选一即可,不要同时执行以免端口/卷名冲突):
# 1) 创建专用网络
docker network create --driver bridge --subnet 10.89.63.0/24 invoiceplane-net
# 2) 启动 MariaDB(含健康检查与数据卷)
docker run -d --name invoiceplane-db \
--network invoiceplane-net \
--restart unless-stopped \
-e MARIADB_ROOT_PASSWORD=root \
-e MARIADB_DATABASE=invoiceplane \
-e MARIADB_USER=invoiceplane \
-e MARIADB_PASSWORD=invoiceplane \
-v invoiceplane-db:/var/lib/mysql \
mariadb:11
# 3) 等 db 健康后启动应用(官方推荐用 healthcheck 判断,命令行场景可轮询 3306 就绪)
docker run -d --name invoiceplane-app \
--network invoiceplane-net \
--restart unless-stopped \
-p 8128:80 \
-e IP_URL=http://192.168.146.128:8128 \
-e DB_HOSTNAME=invoiceplane-db \
-e DB_USERNAME=invoiceplane \
-e DB_PASSWORD=invoiceplane \
-e DB_DATABASE=invoiceplane \
-e ENCRYPTION_KEY=dev-key-change-me-in-production-00 \
-e DEFAULT_ADMIN_EMAIL=admin@example.com \
-e DEFAULT_ADMIN_PASSWORD=q1w2e3r4 \
-e DEFAULT_ADMIN_NAME=admin \
-v invoiceplane-uploads:/var/www/html/uploads \
-v invoiceplane-storage:/var/www/html/storage \
c8n.io/mpldr/invoiceplane:latest
与 compose 的对应关系说明:--network ↔ networks:;-p 8128:80 ↔ ports:;-e ↔ environment:;-v 卷名:容器路径 ↔ volumes:;compose 版额外获得的 depends_on.condition: service_healthy 在 docker run 形态下需自行保证 db 先就绪(轮询健康状态后再启动应用)。本环境以 compose 方式落地并全程实测,docker run 形态为同一配置的等价表达,实际生产推荐统一用 compose 管理。
十、常用运维命令
以下命令均在 /opt/invoiceplane(compose.yaml 所在目录)执行。
10.1 日常启停与观测
docker compose ps # 服务状态(实测回显见 4.3)
docker compose logs -f app # 跟踪应用日志(含启动初始化输出)
docker compose logs app --tail 50 # 最近 50 行
docker compose restart app # 重启应用(数据卷不受影响)
docker compose stop # 停止全部服务(保留容器与卷)
docker compose up -d # 再次启动
docker compose down # 停止并删除容器与网络(保留数据卷,账套不丢)
⚠️ 需要连数据卷一起清空(彻底重置)时才用
docker compose down -v,该操作删除数据库账套,属破坏性操作。
10.2 升级版本
应用内迁移机制使升级非常直接:entrypoint 每次启动自动执行 setup/cli/migrate,新版本待执行迁移会在启动时自动完成:
cd /opt/invoiceplane/src
git fetch --tags && git checkout v1.7.3 # 以实际最新 tag 为准
docker build -t c8n.io/mpldr/invoiceplane:v1.7.3 -f resources/docker/Containerfile .
# 修改 compose.yaml 中 image 的标签后重建应用(数据库卷原样保留)
cd /opt/invoiceplane && docker compose up -d
升级后验证:docker compose logs app | grep -i migrat 应看到迁移执行输出,登录后台核实版本与数据完整。回滚 = 把 image 标签改回旧版本再 up -d(数据库降级不受官方支持,回滚前务必先做 10.3 的备份)。
10.3 备份与恢复
数据库逻辑备份(实测输出 1486 行 SQL,含全量表结构与数据):
docker exec invoiceplane-172-stack-db-1 sh -c 'mariadb-dump -uroot -proot invoiceplane' > invoiceplane-$(date +%F).sql
wc -l invoiceplane-$(date +%F).sql
1486 invoiceplane-2026-10-24.sql
备份文件头部(实测节选):
/*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.20-11.8.9-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: invoiceplane
-- ------------------------------------------------------
-- Server version 11.8.9-MariaDB-ubu2404
附件/Logo 卷备份(直接 tar 卷数据目录,实测):
tar czf uploads-$(date +%F).tgz -C /var/lib/docker/volumes/invoiceplane-172-stack_invoiceplane-uploads/_data .
ls -lh uploads-2026-10-24.tgz
-rw-r--r-- 1 root root 11K 10月 24 03:38 uploads-2026-10-24.tgz
⚠️ 若采用 bind mount 部署,直接 tar 对应宿主目录即可,路径更直观。
恢复数据库(在干净库上导入备份):
docker exec -i invoiceplane-172-stack-db-1 sh -c 'mariadb -uroot -proot invoiceplane' < invoiceplane-2026-10-24.sql
十一、常见问题(FAQ)
Q1:访问首页被 307 跳到一个不对的地址?
应用以 IP_URL 生成站内跳转与公开链接。实测将 IP_URL 设为 http://192.168.146.128:8128 时,访问 http://localhost:8128/ 返回 307 -> http://192.168.146.128:8128/sessions/login。解决:把 compose 中 IP_URL 改为用户实际访问的地址(含协议、IP/域名、端口,无尾部斜杠)后 docker compose up -d 重建。
Q2:循环发票设置了"今天"到期却没生成?
注意容器内默认 UTC 时区。实测部署中数据库 NOW() 返回 UTC 时间(如北京时间 10-24 凌晨时 UTC 仍为 10-23),而循环发票的到期判定是 recur_next_date <= date(NOW())——按北京时间设的"今天"在 UTC 下尚未到。解决:按 UTC 口径设置 next date,或为两个服务追加 TZ=Asia/Shanghai 环境变量统一时区后再核对。端点机制的完整实测见 《InvoicePlane 1.7.2 常用 CLI 命令与任务调度详解》。
Q3:登录页 404?
路径是 /sessions/login(实测 200);访问 /sessions/log-in 等变体会 404(实测)。不确定时直接访问首页,由 307 跳转带到正确登录页。
Q4:应用起不来,日志报 required?
必需环境变量缺失。实测缺 IP_URL 时启动直接失败,错误输出:
/usr/local/bin/entrypoint.sh: line 21: IP_URL: IP_URL is required (e.g. https://invoices.example.com)
按第六节 6.1 补齐全部 6 个必需变量(含 ENCRYPTION_KEY)即可。
Q5:改了容器里的 ipconfig.php,重启后配置"丢了"?
设计如此:entrypoint 每次启动按环境变量重写该文件(见第七节)。改配置的唯一正确入口是 compose 的 environment:,改完 docker compose up -d 重建。
Q6:上生产前还要做什么?
①改掉默认管理员密码(admin@example.com / q1w2e3r4 仅为演示值);②ENCRYPTION_KEY、MARIADB_ROOT_PASSWORD、DB_PASSWORD 全部换强随机值;③前面加 HTTPS 反向代理并把 COOKIE_SECURE=true、IP_URL 换成 https 地址;④确认 10.3 的备份计划(数据库 dump + uploads 卷)。
Q7:如何彻底重装?
docker compose down -v 清掉容器与三个数据卷,删除后重新 docker compose up -d 即回到全新初始化流程(会重新建表并按 DEFAULT_ADMIN_* 建管理员)。⚠️ 账套与附件一并清空,先备份。
十二、参考链接
- 官方容器部署文档(环境变量契约来源):主仓库
.github/docs/CONTAINER_DEPLOYMENT.md(v1.7.2 源码内;2026-10-24 核实) - 官方一体化镜像定义:主仓库
resources/docker/Containerfile与resources/docker/entrypoint.sh(v1.7.2 源码内) - 官方主仓库:https://github.com/InvoicePlane/InvoicePlane (v1.7.2 tag;2026-10-24 查询)
- 官方 Wiki(安装与使用):https://wiki.invoiceplane.com/ (2026-10-24 查询)
- 官网下载页:https://invoiceplane.com (2026-10-24 查询)
- 软件定位与竞品选型:《InvoicePlane 介绍与选型指南》
- 配置参数体系(ipconfig 与系统设置两层逐项说明):《InvoicePlane 1.7.2 配置参数详解》
- 调度端点、CLI 路由与定时任务落地:《InvoicePlane 1.7.2 常用 CLI 命令与任务调度详解》