InvoicePlane 1.7.2 Docker Compose 部署指南

InvoicePlane 1.7.2 Docker Compose 从零部署实测指南:官方 Containerfile 镜像构建与选型、MariaDB 11 编排、环境变量全表(官方契约)、entrypoint 自动初始化机制、数据持久化与备份恢复、等价 docker run、升级与常见问题排障,全部命令带实测回显。

InvoicePlane发票管理自托管PHP 应用2026/10/24

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 Engine29.8.1(build 4a63305)
Docker Composev5.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.ymlphp-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。

三、前置条件

  1. Docker Engine 与 Compose v2 插件。验证(实测回显):
docker compose version
Docker Compose version v5.5.1
  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" 改为其他空闲端口即可。

  1. 加密密钥:应用必需 ENCRYPTION_KEY(用于加密存储敏感设置),官方文档要求以 openssl 生成:
openssl rand -base64 32
5Qj8mH2xLp9vRk3sT7wY1uN6cZ4aE0bD+fGhIjKlMnO=

⚠️ 上面为本机执行示例回显,你必须自行生成并使用自己的输出值;密钥生成后妥善保管,更换会导致既有加密数据无法解密。

  1. 磁盘空间:应用镜像约 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-stackCompose 项目名;生成的卷/网络/服务容器名均以此为前缀,避免与宿主机上其他栈冲突
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/mysqlMariaDB 全部数据(账套)

卷在宿主机上的实际位置可用 docker volume inspect 查看(实测):

docker volume inspect invoiceplane-172-stack_invoiceplane-db --format '{{.Mountpoint}}'
/var/lib/docker/volumes/invoiceplane-172-stack_invoiceplane-db/_data

持久化行为要点:

  1. 升级/重建不丢数据:docker compose up -d 变更配置重建服务容器时,命名卷原样保留,账套与附件不受影响(本环境实测经历多次重建,数据完好)。
  2. 二次启动不重复初始化:应用每次启动都会重跑迁移(有则执行、无则秒过),且检测到已有用户时跳过管理员创建——实测重启应用后的日志:
Running pending migrations...
Migrations completed successfully.
Users already exist — skipping default user creation.
  1. 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_PORT3306数据库端口
CI_ENVproduction设为 development 显示全部 PHP 错误
ENABLE_DEBUGfalse开启高级调试日志
SESS_SAVE_PATHPHP 默认临时目录会话文件目录;建议设为文档根之外的绝对路径
SESS_COOKIE_NAMEip_session会话 Cookie 名
SESS_TABLE_NAMEip_sessions会话入库时的表名(SESS_DRIVER=database 时用)
SESS_EXPIRATION864000会话有效期(秒,默认 10 天;0 = 关浏览器即过期)
SESS_MATCH_IPtrue会话绑定客户端 IP
SESS_REGENERATE_DESTROYtrue重建会话 ID 时销毁旧会话(防会话固定)
COOKIE_SECUREfalse仅经 HTTPS 发送 Cookie;HTTPS 部署应设 true
X_FRAME_OPTIONSSAMEORIGINX-Frame-Options 响应头值
ENABLE_X_CONTENT_TYPE_OPTIONStrue启用 nosniff 头
LEGACY_CALCULATIONfalse是否沿用 1.6.3 前的税/折扣计算口径;生成合规电子发票 XML 必须为 false
ENABLE_INVOICE_DELETIONfalse允许彻底删除发票
DISABLE_READ_ONLYfalse关闭已发送发票的只读锁定
PASSWORD_RESET_IP_MAX_ATTEMPTS5密码重置按 IP 限流的窗口内次数上限
PASSWORD_RESET_IP_WINDOW_MINUTES60IP 限流时间窗(分钟)
PASSWORD_RESET_EMAIL_MAX_ATTEMPTS3按邮箱限流次数上限
PASSWORD_RESET_EMAIL_WINDOW_HOURS1邮箱限流时间窗(小时)
PASSWORD_RESET_TOKEN_EXPIRY_MINUTES15密码重置链接有效期(分钟)
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_IMAGESfalse上传图片剥离 EXIF 元数据(GPS/时间等)
SUMEX_SETTINGSfalse启用瑞士医疗发票(Sumex)定制
SUMEX_URL—Sumex XML 后端地址(提交 XML 换取 PDF)
ENCRYPTION_CIPHERAES-256加密算法

6.3 默认管理员变量(仅首次初始化生效)

变量默认值说明
DEFAULT_LANGUAGEenglish应用语言(english/german/french/…,仅全新安装生效)
DEFAULT_ADMIN_EMAILadmin@localhost默认管理员邮箱(登录账号)
DEFAULT_ADMIN_PASSWORD随机 24 位管理员密码;不设则启动日志打印随机密码,务必第一时间查看
DEFAULT_ADMIN_NAMEadmin管理员显示名

⚠️ 只要数据库中已存在任意用户,三个 DEFAULT_ADMIN_* 变量即被跳过(实测日志 "Users already exist — skipping default user creation.")。

七、配置参数说明(entrypoint 启动机制)

理解 entrypoint.sh 的行为,就理解了这套部署的全部配置流。应用每次启动按以下顺序执行(逐段核实自官方镜像内 /usr/local/bin/entrypoint.sh):

  1. 必填校验:6 个必需变量任一缺失立即报错退出(实测输出,见 Q4)。
  2. 注入清洗(防配置注入):所有写入配置的值若含换行/回车符直接拒绝启动——防止攻击者借环境变量向 ipconfig.php 注入额外配置行(如改写 DISABLE_SETUP=false)。
  3. 生成配置文件:以环境变量值生成 /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
  1. 三个自动加固项(entrypoint 写死,无需配置):DISABLE_SETUP=true(关闭 Web 安装向导入口)、SETUP_COMPLETED=true(标记安装完成,阻断向导重入)、REMOVE_INDEXPHP=true(URL 去掉 index.php,依赖官方已启用的 mod_rewrite)。
  2. 模板目录防护:若配置了 CUSTOM_TEMPLATES_FOLDER,自动创建目录并写入 .htaccess(Deny from all)阻止 HTTP 直读模板源码。
  3. 数据库迁移:执行 php index.php setup/cli/migrate——全新库安装全部基础表,已有库只跑增量迁移(幂等,每次启动都会执行)。
  4. 创建默认管理员:执行 php index.php setup/cli/create_default_user——仅当 ip_users 表为空时按 DEFAULT_ADMIN_* 建号。
  5. 交接 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_* 建管理员)。⚠️ 账套与附件一并清空,先备份。

十二、参考链接

  1. 官方容器部署文档(环境变量契约来源):主仓库 .github/docs/CONTAINER_DEPLOYMENT.md(v1.7.2 源码内;2026-10-24 核实)
  2. 官方一体化镜像定义:主仓库 resources/docker/Containerfile 与 resources/docker/entrypoint.sh(v1.7.2 源码内)
  3. 官方主仓库:https://github.com/InvoicePlane/InvoicePlane (v1.7.2 tag;2026-10-24 查询)
  4. 官方 Wiki(安装与使用):https://wiki.invoiceplane.com/ (2026-10-24 查询)
  5. 官网下载页:https://invoiceplane.com (2026-10-24 查询)
  6. 软件定位与竞品选型:《InvoicePlane 介绍与选型指南》
  7. 配置参数体系(ipconfig 与系统设置两层逐项说明):《InvoicePlane 1.7.2 配置参数详解》
  8. 调度端点、CLI 路由与定时任务落地:《InvoicePlane 1.7.2 常用 CLI 命令与任务调度详解》