Kimai 2.71 Docker Compose 部署实战

本文是 Kimai 部署篇,基于 2026-10-25 发布的官方最新稳定版 **2.71.0**(`kimai/kimai2:2.71.0` 镜像 + MySQL 8.3)在 Ubuntu 服务器上完成端到端部署实测:Compose 编排逐段拆解、起栈时序日志逐行核对、超级管理员自动建号、Web/CLI/API 三通道部署后核验、停启后数据持久性验证、备份,以及 4 个真实排障案例。产品认知与选型对比见 [《Kimai 2.71 介绍与选型指南》](https://www.quzuo.net/document/sK41JUhCNu0x)。

KimaiKimai工时管理PHP 应用运维自动化2026/10/27

Kimai 2.71 Docker Compose 部署实战

本文是 Kimai 部署篇,基于 2026-10-25 发布的官方最新稳定版 2.71.0(kimai/kimai2:2.71.0 镜像 + MySQL 8.3)在 Ubuntu 服务器上完成端到端部署实测:Compose 编排逐段拆解、起栈时序日志逐行核对、超级管理员自动建号、Web/CLI/API 三通道部署后核验、停启后数据持久性验证、备份,以及 4 个真实排障案例。产品认知与选型对比见 《Kimai 2.71 介绍与选型指南》。

一、部署形态与前置条件

1.1 官方部署形态

Kimai 官方在 kimai.org/documentation/docker-compose.html 给出的自托管推荐形态是「Kimai 应用 + MySQL 数据库」双服务编排:应用使用官方一体化镜像 kimai/kimai2(内含 Apache Web 服务,监听 8001 端口),数据库使用 mysql:8.3(官方样例版本),两者通过 Compose 私有网络互联。官方文档同时给出裸机(PHP 8.2+ 手工安装)与 Kubernetes 部署路径,但 Compose 是官方主推且维护最及时的形态——本文全部实测基于该形态。

官方样例的镜像标签使用 kimai/kimai2:apache(始终跟踪最新稳定版);本文为锁定版本、保证文档结果可复现,实测时使用等价的版本号标签 kimai/kimai2:2.71.0(2026-10-25 发布的当前最新稳定版,与官方「Latest (2.71.0)」样例页一致)。

1.2 服务器前置条件

项目要求本文实测环境
操作系统任意可运行 Docker 的 Linux 发行版Ubuntu 22.04(Linux 5.15,x86_64)
Docker 引擎Docker 20.10+ 与 Compose V2(docker compose 子命令)Docker 27.x,Compose V2
应用端口对外暴露 8001(可映射为宿主机任意空闲端口)宿主机 8136 → 容器内 8001
数据目录需要持久化的三个落盘点:MySQL 数据、Kimai var/data、Kimai var/plugins同目录 bind mount 方式持久化
内存官方镜像默认 PHP memory_limit=256M,2 GB 内存服务器即可流畅运行实测栈内存占用约 500 MB
数据库MySQL 8.x 或 MariaDB 10.x(连接串需带 charset=utf8mb4&serverVersion=8.3.0 类版本声明)mysql:8.3

⚠️ 网络说明:kimai/kimai2:2.71.0 与 mysql:8.3 镜像在 Docker Hub 拉取。本文实测环境下直连 Docker Hub 速度不稳定,走代理 192.168.1.62:7897 完成拉取(docker pull 已配置该代理),两镜像均可正常获取;直连可用的网络环境下无需此步。

1.3 端口与网络规划

部署前先确认宿主机端口占用情况,从 8128–8152 中选取未占用者:

ss -ltn | grep -E ':813[0-9]|:812[0-9]'

实测输出(8136 未被占用,选定):

State    Recv-Q   Send-Q   Local Address:Port   Peer Address:Process
LISTEN   0        4096     127.0.0.53%lo:53     0.0.0.0:*
LISTEN   0        511      0.0.0.0:8129         0.0.0.0:*
LISTEN   0        511      0.0.0.0:8130         0.0.0.0:*

Compose 编排会自动创建专属的 user-defined bridge 网络;如需固定网段(多栈并存防冲突),可显式指定 subnet。本文实测选定 10.89.66.0/24(先用 docker network ls 盘点已占用的 10.89.x.0/24 网段,避开在用者)。

二、Compose 编排文件详解

2.1 完整编排文件

以下为本文实测所用完整 docker-compose.yml(放在独立工作目录,如 /opt/kimai-stack/):

name: kimai-2.71.0-stack

services:
  sqldb:
    image: mysql:8.3
    container_name: kimai-2.71.0-db
    volumes:
      - ./mysql-data:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=kimai
      - MYSQL_USER=kimaiuser
      - MYSQL_PASSWORD=q1w2e3r4
      - MYSQL_ROOT_PASSWORD=q1w2e3r4
    command: --default-storage-engine innodb
    restart: unless-stopped
    healthcheck:
      test: mysqladmin -uroot -p$$MYSQL_ROOT_PASSWORD ping -h 127.0.0.1 --silent
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 30s
    networks:
      - kimai-net

  kimai:
    image: kimai/kimai2:2.71.0
    container_name: kimai-2.71.0-web
    depends_on:
      sqldb:
        condition: service_healthy
        restart: true
    volumes:
      - ./data:/opt/kimai/var/data
      - ./plugins:/opt/kimai/var/plugins
    ports:
      - "8136:8001"
    environment:
      - APP_SECRET=5f0a1c9e2b7d4a8f3e6c1d9b0a7f4e2c
      - 'TRUSTED_HOSTS=^(localhost|127\.0\.0\.1|kimai-2\.71\.0-web)$'
      - ADMINMAIL=admin@example.com
      - ADMINPASS=q1w2e3r4
      - "DATABASE_URL=mysql://kimaiuser:q1w2e3r4@kimai-2.71.0-db/kimai?charset=utf8mb4&serverVersion=8.3.0"
    restart: unless-stopped
    networks:
      - kimai-net

networks:
  kimai-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.66.0/24

💡 技术拓展:为什么官方样例的健康检查里写 $$MYSQL_ROOT_PASSWORD(双美元符) Compose 会先对 YAML 文件做一轮「变量插值」:单个 $ 会被解释为「从宿主机 .env 或 shell 环境取值」;健康检查字符串里的 $$ 转义后交给 MySQL 镜像的检查进程,由其在容器内读取 MySQL 镜像自己的 MYSQL_ROOT_PASSWORD 环境变量。写成单 $ 时,Compose 会在宿主机侧查找名为 MYSQL_ROOT_PASSWORD 的变量——通常不存在,导致健康检查拿到空密码而永远失败。这是 MySQL 官方镜像 Compose 编排中最常见的笔误。

2.2 关键配置逐项解说

数据库服务(sqldb):

  • MYSQL_DATABASE=kimai / MYSQL_USER=kimaiuser / MYSQL_PASSWORD:MySQL 镜像首启时自动建库建用户。Kimai 的连接串将引用这三项;
  • command: --default-storage-engine innodb:官方文档明确要求 InnoDB 作为默认存储引擎(Kimai 的 Doctrine 迁移依赖事务性 DDL);
  • healthcheck:用 mysqladmin ping 探活,start_period: 30s 给 MySQL 首次初始化留足时间。这个健康检查是整个编排的时序基石——下一节的 Kimai 服务以 condition: service_healthy 依赖它。

应用服务(kimai):

  • depends_on.sqldb.condition: service_healthy:必须等 MySQL 健康后才启动 Kimai,否则 Kimai 启动脚本会因数据库不可达而进入等待循环(见 7.2 节排障案例);
  • volumes 的两个 bind mount 是官方样例要求的最小持久化集合:/opt/kimai/var/data 存放上传的发票模板/导出产物/公司 logo 等,/opt/kimai/var/plugins 存放插件。MySQL 数据另由 ./mysql-data:/var/lib/mysql 持久化;
  • ports: "8136:8001":官方镜像内 Apache 监听 8001;宿主机侧换成任意空闲端口即可;
  • APP_SECRET:Symfony 会话/CSRF 加密密钥,官方要求「必须改为长随机串」(生成方式:php -r 'echo bin2hex(random_bytes(32));');
  • TRUSTED_HOSTS:正则白名单,必须包含实际访问 Kimai 用的主机名(否则请求被 Symfony 直接拒绝);本文按「真实服务器 + 本机验证」的口径写成 ^(localhost|127\.0\.0\.1|kimai-2\.71\.0-web)$,生产环境应写成自己的域名(如 ^kimai\.example\.com$);
  • ADMINMAIL / ADMINPASS:官方镜像的首启自动建号变量——首次启动安装完成后自动创建用户名 admin、邮箱为 ADMINMAIL、密码为 ADMINPASS 的超级管理员;
  • DATABASE_URL:Symfony 标准数据库连接串。注意两点:一是 serverVersion=8.3.0 必须与真实数据库版本一致(Doctrine 迁移按此生成平台相关 SQL);二是密码里若含 @ : / ? 等 URL 保留字符必须做 URL 编码。

2.3 与官方样例的差异说明

官方「Latest (2.71.0)」样例(kimai.org/documentation/docker-compose.html,2026-10-27 取证)与本文编排的一致点:同为 mysql:8.3 + kimai/kimai2 双服务、同样挂 var/data 与 var/plugins 两个卷、同样用 ADMINMAIL/ADMINPASS 自动建号、同样以 service_healthy 控制启动时序。差异点仅有两处:本文把镜像标签从浮动的 kimai/kimai2:apache 换成锁版本的 kimai/kimai2:2.71.0(可复现性更好,升级时改标签即可);本文端口映射为 8136:8001(宿主机 8001 已被占用时的常规做法)。

三、端到端部署实录

3.1 起栈

cd /opt/kimai-stack
docker compose up -d

实测回显(首次起栈,MySQL 无数据目录需初始化,耗时较长):

[+] Running 4/4
 ✔ Network kimai-2710-stack_kimai-net  Created   0.1s
 ✔ Volume "kimai-2710-stack_plugins"   Created   0.0s
 ✔ Container kimai-2.71.0-db           Healthy   32.5s
 ✔ Container kimai-2.71.0-web          Started   33.1s

💡 技术拓展:depends_on 的两种等待语义 depends_on: [sqldb] 只保证「启动顺序」(MySQL 先创建),不保证「可用性」(MySQL 完成初始化并接受连接);加 condition: service_healthy 后 Compose 才会阻塞等待健康检查通过。对「启动要 20–30 秒的初始化」的 MySQL 来说,缺了后者,Kimai 会在数据库尚未就绪时启动。 Kimai 官方镜像对这种情况有兜底(启动脚本自带「Wait for database connection」重试循环,见 3.3 节日志),但等待期间 Web 服务不对外,service_healthy 仍是必备配置。

3.2 观察启动时序

首次启动的完整时序可从 Kimai 应用日志逐行核对:

docker logs kimai-2.71.0-web 2>&1 | grep -v deprecation | head -25

实测回显(已略去无关行):

2.71.0
Wait for database connection ...
Testing DB:Connection established
Group already exists
User already exists
 Start installation ...
Database `kimai` for connection named default already exists. Skipped.
 Creating database ...

 [OK] Already at the latest version ("DoctrineMigrations\Version20261025120000")

 Rebuilding cache ...

 // Clearing the cache for the prod environment with debug false

 [OK] Cache for the "prod" environment (debug=false) was successfully cleared.

 // Warming up the cache for the prod environment with debug false

 [OK] Cache for the "prod" environment (debug=false) was successfully warmed.

逐段解读:

  1. 2.71.0:应用启动横幅(镜像版本号);
  2. Wait for database connection ... → Testing DB:Connection established:启动脚本先做数据库连通性探测,通过后才继续;
  3. Group already exists / User already exists:安装脚本同步创建 MySQL 用户/组(文件属主 www-data 需要),已存在则跳过;
  4. Creating database ... Already at the latest version:执行 bin/console kimai:install(建库幂等跳过 + 跑 Doctrine 迁移建全部 36 张表,当前迁移版本 Version20261025120000 对应 2.71.0);
  5. Rebuilding cache ...:清空并预热 Symfony 生产缓存,完成后 Web 服务才开始对外监听。

💡 技术拓展:Doctrine migration 与「建表时机」 Kimai 不使用裸 SQL 建表,而是 Doctrine ORM 的 migration 版本链:每个数据库结构变更是链上一个有序版本(如 2.71.0 的 Version20261025120000),kimai:install 沿链执行到最新。这带来两个运维特性:一是安装与升级是同一条命令(kimai:install 内部即 kimai:update 别名),二是「当前库版本」可独立查询(doctrine:migrations:status),回滚需要沿链下移而非改代码。

3.3 确认健康状态

docker compose ps

实测回显:

NAME               IMAGE                  COMMAND                  SERVICE   CREATED          STATUS
kimai-2.71.0-db    mysql:8.3              "docker-entrypoint.s…"   sqldb     45 seconds ago   Up 44 seconds (healthy)
kimai-2.71.0-web   kimai/kimai2:2.71.0    "tini -- /entrypoint…"   kimai     45 seconds ago   Up 44 seconds (healthy)

两个服务均进入 healthy 状态即部署完成。首次启动从 up 到 Web 健康实测约 40 秒(其中 MySQL 初始化约 30 秒、Kimai 安装与缓存预热约 10 秒)。

3.4 超级管理员自动建号验证

编排中 ADMINMAIL/ADMINPASS 指定的账号在首次安装时自动创建,且后续启动幂等(再次启动日志中的 User already exists 即此含义)。在数据库侧直接核对:

docker exec kimai-2.71.0-db mysql -ukimaiuser -pq1w2e3r4 kimai \
  -e "SELECT id,username,email,enabled FROM kimai2_users;"

实测回显:

id   username   email              enabled
1    admin      admin@example.com  1

若使用的是官方样例中「不传 ADMINMAIL/ADMINPASS」的编排变体,则不会自动建号,需要手工创建超级管理员(命令篇有完整实测,见 《Kimai 2.71 常用命令与运维实战》)。

四、部署后核验

4.1 Web 通道核验

浏览器打开 http://<服务器IP>:8136/,未登录状态会 302 重定向到登录页。命令行核验等价过程:

curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://127.0.0.1:8136/

实测回显:

302 -> http://127.0.0.1:8136/zh/login

登录表单提交(CSRF token 从登录页抓取):

CSRF=$(curl -s -c cookies.txt http://localhost:8136/zh/login \
  | grep -oE 'name="_csrf_token"[^>]*value="[^"]+"' | cut -d'"' -f4)
curl -s -b cookies.txt -c cookies.txt -o /dev/null \
  -X POST http://localhost:8136/zh/login_check \
  --data-urlencode "_username=admin" \
  --data-urlencode "_password=q1w2e3r4" \
  --data-urlencode "_csrf_token=$CSRF"
curl -s -b cookies.txt -o /dev/null -w '%{http_code}\n' http://localhost:8136/zh/dashboard/

实测回显:

200

⚠️ 排障前置提醒:上面两条 curl 的域名是 localhost 而非 127.0.0.1——浏览器 cookie 按「域名」隔离,登录时会话 cookie 写在 localhost 名下,后续请求换用 127.0.0.1 会拿不到会话、表现为「明明登录成功却总是跳回登录页」。这在本文实测中真实踩到(见 7.4 节案例四)。

4.2 CLI 通道核验

进入 Kimai 安装目录(Compose 部署下即应用容器内 /opt/kimai/),确认版本与运行环境:

bin/console kimai:version
bin/console about | head -8

实测回显:

Kimai 2.71.0 by Kevin Papst.
 -------------------- ---------------------------------
  Symfony
 -------------------- ---------------------------------
  Version              6.4.48
  Long-Term Support    Yes
  End of maintenance   11/2026 (in +34 days)
  End of life          11/2027 (in +399 days)
 -------------------- ---------------------------------

Compose 部署下等价的执行方式是在编排目录使用 docker compose exec kimai bin/console <命令>。CLI 工具链的全量实测(用户管理、发票、导出、备份等 19 条 kimai: 命令)见 《Kimai 2.71 常用命令与运维实战》。

4.3 API 通道核验

官方镜像内置完整 JSON API(Swagger 界面在 /api/doc)。最小核验是拿一个 API 访问令牌后打探活端点(2.71.0 起认证头为 Authorization: Bearer):

curl -s -H "Authorization: Bearer <你的访问令牌>" http://127.0.0.1:8136/api/version
curl -s -H "Authorization: Bearer <你的访问令牌>" http://127.0.0.1:8136/api/ping

实测回显:

{"version":"2.71.0","versionId":27100,"copyright":"Kimai 2.71.0 by Kevin Papst."}
{"message":"pong"}

API 令牌的创建入口在 Web 界面「个人配置 → API」页;2.71.0 移除了旧版「API 密码」机制,老文档里 X-AUTH-TOKEN 请求头在当前版本会得到 401(完整认证实测与排障见命令篇)。

五、升级与生命周期管理

5.1 官方升级流程

官方文档给出的升级三步(kimai.org/documentation/docker-compose.html「Updating Kimai」节,2026-10-27 取证):

docker compose pull    # 拉取最新镜像
docker compose down    # 停止并移除旧服务(数据在卷中不受影响)
docker compose up -d   # 以新版本重新启动

重启用的是 kimai/kimai2:apache 这类浮动标签时,pull 即会拿到新版本;若像本文一样锁定了 2.71.0,则先修改 docker-compose.yml 中的标签再执行上述三步。新版本容器启动时,3.3 节看到的安装时序会自动重跑——kimai:install 幂等地把 Doctrine 迁移沿链推进到新版对应位置,无需人工干预。

5.2 停启与数据持久性实测

对编排目录执行完整的 down → up 循环,验证三个持久化点(MySQL 数据、var/data、var/plugins)在服务移除后仍然保留:

docker compose down
docker compose up -d

实测回显:

[+] Running 3/3
 ✔ Container kimai-2.71.0-db  Removed  10.4s
 ✔ Container kimai-2.71.0-web Removed  0.5s
 ✔ Network kimai-2710-stack_kimai-net  Removed  0.1s
[+] Running 2/2
 ✔ Container kimai-2.71.0-db  Healthy  31.6s
 ✔ Container kimai-2.71.0-web Started  32.1s

重启后核验数据完整:

docker exec kimai-2.71.0-db mysql -ukimaiuser -pq1w2e3r4 kimai \
  -e "SELECT COUNT(*) AS users FROM kimai2_users; SELECT COUNT(*) AS invoices FROM kimai2_invoices;"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8136/

实测回显(down 前创建的 2 个用户与 2 张发票全部保留):

users
2
invoices
2
302

💡 技术拓展:Compose 的两个持久化坑位 其一,docker compose down 只删容器与网络,不删命名卷(volumes: 顶级段声明的卷),但会无视 bind mount 的存在与否——本文用 bind mount 把数据落在编排目录下,物理位置直观、便于用 rsync 整目录备份,代价是目录属主由镜像内进程写入(www-data),宿主机侧操作时注意权限;其二,down -v 才会连命名卷一起删除,误加 -v 会清空 MySQL 数据——这也是官方备份文档强调「先 dump 再动卷」的原因。

六、备份与恢复

6.1 数据库备份

Kimai 的全部业务数据(用户、客户、项目、活动、时间记录、发票)都在 MySQL 里;var/data 只存发票 PDF/模板等文件资产。最小备份 = 一次全库 dump:

docker exec kimai-2.71.0-db sh -c \
  'mysqldump -ukimaiuser -pq1w2e3r4 kimai | gzip > /tmp/kimai-$(date +%F).sql.gz'
docker cp kimai-2.71.0-db:/tmp/kimai-2026-10-27.sql.gz /opt/backup/

实测产物(全库 35 张表,压缩后约 9 KB——空库体积,随业务量增长):

-rw-r--r-- 1 root root 9227 Oct 27 23:25 /tmp/kimai-2026-10-27.sql.gz

恢复路径:建同版本空库 → gunzip -c 备份.sql.gz | mysql -u<用户> -p kimai → 重启应用(缓存重建由启动时序自动完成)。MySQL 数据目录(./mysql-data)整体拷贝也可作为备份手段,但跨 MySQL 大版本恢复不保证兼容,官方建议以逻辑 dump(mysqldump)为准。

6.2 文件资产备份

两个 bind mount 目录直接整目录打包:

tar -czf /opt/backup/kimai-var-data-$(date +%F).tgz -C /opt/kimai-stack data plugins

data/ 中值得关注的子目录:invoices/(CLI/Web 生成的发票 PDF 实测落盘于此,如 2026-002-Beta_zi_xun_gong_si.pdf)、exports/(导出模板产物)。数据库 dump + 这两个目录即 Kimai 的完整备份集。

七、排障实录

以下 4 个案例全部来自本文部署实测,现象与回显均为真实复现。

7.1 案例一:不配置 APP_SECRET,服务照常启动但「密钥在数据卷里」

现象:编排中删掉 APP_SECRET 环境变量后 up -d,服务健康、页面可访问——与官方文档「必填」的说法不符。

根因:查看应用启动日志,官方镜像的入口脚本有兜底逻辑:

APP_SECRET: generated a new unique secret, persisted to var/data volume

处置:镜像会生成随机密钥并持久化到 var/data 卷,让会话密钥在重启后保持稳定。这意味着「忘配 APP_SECRET」不会像老版本那样直接启动失败,但生成的密钥不受编排管理。生产部署仍应显式配置 APP_SECRET(编排文件里可审计、可迁移);var/data 卷在多实例共享时尤其要注意。

7.2 案例二:DATABASE_URL 数据库主机名写错,启动卡在等待循环

现象:把连接串主机名写成不存在的 wrong-host,容器长时间 starting,Web 端口不对外。

日志特征(docker logs 每 10 秒重复):

Wait for database connection ...
Testing DB:SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for wrong-host failed: Temporary failure in name resolution (?)
Checking DB: 10

根因:HY000/2002 是 PHP 侧「DNS 解析失败」,主机名在编排网络里不存在。入口脚本设计为无限等待而非快速失败——数据库就绪前不启动应用,这是给「依赖编排时序」留的兜底,代价是配置错误时表现为「卡住」而非报错退出。

处置:核对连接串主机名与数据库服务的 container_name/编排服务名一致;若用了自定义网络,确认两个服务在同一网络。

7.3 案例三:数据库密码错误,同样的卡住现象但错误码不同

现象:连接串密码改错(badpass),现象与案例二几乎一样——反复打印等待日志。

日志特征:

Wait for database connection ...
Testing DB:Access denied (1045)
Checking DB: 1

根因:1045 是 MySQL「认证失败」,说明网络已通、主机名解析成功,纯粹是凭据错误。注意 Access denied (1045) 也可能因用户未授权从应用侧来源主机连接出现(MySQL 用户由 kimaiuser@'%' 定义时不受影响;手工建用户限定来源时常见)。

处置:案例二(2002,解析失败)与案例三(1045,认证失败)的错误码差异正是定位「网络问题」还是「凭据问题」的抓手。凭据修正后无需重启数据库,应用侧的等待循环会在下一轮探测成功后自动继续安装。

7.4 案例四:登录成功却「永远跳回登录页」

现象:curl 抓 CSRF、POST /login_check、HTTP 302 全部正常,会话 cookie 已写入 cookie jar;但换一个主机名访问仪表盘立刻被重定向到 /login。

复现回显(同一 cookie jar,仅 URL 主机名不同):

$ curl -b cookies.txt -o /dev/null -w '%{http_code}\n' http://localhost:8136/en/dashboard/
200
$ curl -b cookies.txt -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8136/en/dashboard/
302

根因:浏览器与 curl 的 cookie 按域隔离——登录发生在 localhost 域,会话 cookie(KIMAI_SESSION)就只对 localhost 生效;127.0.0.1 是另一个域,请求不带会话 cookie,服务端视为未登录。KIMAI_REMEMBER(记住我)cookie 同理。

处置:整个访问链路统一使用同一个主机名(生产用域名,本机测试统一 localhost)。同时确认 TRUSTED_HOSTS 白名单覆盖实际使用的主机名,否则会先一步被 Symfony 以 400/403 拒绝。

7.5 排障速查表

现象关键日志/回显定位处置
服务启动即退出或启动报缺参APP_SECRET: generated ... persistedAPP_SECRET 未配置(镜像已兜底)编排显式配置长随机串
启动卡住、Web 不对外getaddrinfo for <host> failed (2002)数据库主机名解析失败核对连接串主机名与编排网络
启动卡住、Web 不对外Access denied (1045)数据库凭据/授权错误核对密码与用户来源限制
登录后跳回登录页同一 cookie 换主机名 302cookie 域不匹配统一访问主机名
页面 403/400TRUSTED_HOSTS 不含访问用主机名可信主机白名单正则补充域名后重建
升级后页面 500缓存残留旧版本缓存未清重跑 bin/console kimai:reload

八、部署核对清单

  • 编排文件含 name: 项目名(多栈并存时避免同名项目互相覆盖);
  • APP_SECRET 为长随机串;TRUSTED_HOSTS 覆盖实际访问域名;
  • DATABASE_URL 的主机名/库名/用户名/密码与 MySQL 编排一致,serverVersion 与实际版本一致;
  • depends_on 带 condition: service_healthy;MySQL 带 --default-storage-engine innodb;
  • 三个持久化点落盘:mysql-data/、data/(var/data)、plugins/(var/plugins);
  • 首启后 docker compose ps 两服务 healthy,/api/ping 与 Web 登录双通道核验通过;
  • 备份脚本覆盖 mysqldump + data/ + plugins/;
  • 升级按 pull → down → up 三步,升级后确认迁移日志推进到新版本号。

配置参数(.env 环境变量、local.yaml 覆盖、费率规则、LDAP/SAML、邮件)的逐项详解见 《Kimai 2.71 配置参数详解》。