
容器里的时间和宿主机不一致,常见表现是应用日志比实际时间早几个小时、定时任务提前执行、JWT 或签名链接刚生成就过期。Docker 容器通常共享宿主机的内核时钟,但“显示出来的时间”还会受到时区数据、TZ 环境变量、应用自己的时区设置以及日志采集器的影响。排查时要把系统时钟、时区和应用格式化分开看。
先在宿主机和容器内各看一次
先记录宿主机的时间、时区和 UTC 时间:
date '+%F %T %Z %z'
timedatectl
date -u '+%F %T UTC'
再查看容器:
docker exec app date '+%F %T %Z %z'
docker exec app date -u '+%F %T UTC'
如果宿主机和容器的 UTC 时间相同,只是本地时间相差八小时左右,问题通常在时区显示;如果 UTC 也不一致,就要检查宿主机的时间同步、容器运行环境或应用取时方式。
docker inspect 可以确认容器是否设置了时区变量:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' app | grep -E '^TZ='
变量名应为大写 TZ。如果容器没有 date,可以先看基础镜像是否包含 tzdata,再用应用自身的诊断接口或临时调试容器核对 UTC 时间。
宿主机时钟不准,容器改时区也没有用
容器不会替宿主机校准系统时钟。先在宿主机检查同步状态:
timedatectl show -p NTPSynchronized -p TimeUSec -p Timezone
systemctl status systemd-timesyncd --no-pager
使用 chrony 的系统可以执行:
chronyc tracking
chronyc sources -v
如果宿主机的 UTC 时间已经错误,先修复宿主机的 NTP 或 chrony 配置,再检查容器。不要把“设置 TZ=Asia/Shanghai”当成校准时钟的办法,它只改变时区解释和格式化结果。
云服务器、虚拟机和 Docker Desktop 还可能有额外的时间同步层。宿主机显示正确而虚拟机内核时间异常时,应在虚拟机层处理;容器内部用 date -s 修改时间通常会失败,也不应该把它当作长期方案。
Compose 中统一设置显示时区
如果应用只需要显示中国标准时间,可以在 Compose 中显式设置 TZ:
services:
app:
image: example/app:latest
environment:
TZ: Asia/Shanghai
这要求镜像中的运行时能够识别该时区,并且通常需要安装 tzdata。Alpine、Debian slim 等精简镜像可能没有完整的时区数据库。可以在容器内检查:
docker exec app sh -c 'echo "$TZ"; ls -l /etc/localtime; date'
更稳妥的做法是构建镜像时安装时区数据,而不是每次启动后临时修改容器:
FROM alpine:3.22
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
对于 Debian 或 Ubuntu 基础镜像,则按镜像发行版安装对应的 tzdata 包。生产环境应固定基础镜像版本,避免同一个 TZ 配置在不同镜像中得到不同结果。
挂载 /etc/localtime 的取舍
有些部署会把宿主机的时区文件挂载到容器:
services:
app:
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
这个方式能让部分程序读取宿主机时区,但不同发行版的 /etc/timezone 文件并不一定存在,Windows 或 Docker Desktop 环境也不适合照搬。挂载宿主机文件还会把主机配置带入镜像,迁移到另一台机器时可能出现行为变化。团队需要在“镜像内安装并固定 tzdata”和“挂载宿主机时区文件”之间选一种策略,避免两套配置互相覆盖。
应用日志的时间可能来自另一层
容器里的 date 正常,不代表日志时间就正常。常见情况有三种:
- 应用使用 UTC 生成时间,再由日志平台按本地时区展示。
- 应用运行时单独设置了时区,例如 PHP 的
date.timezone、Java 的-Duser.timezone或 Node.js 依赖TZ。 - Docker 日志驱动记录的是容器输出时间,采集器又重新解析或转换了一次。
把同一时刻的三份结果放在一起比较:容器 date -u、应用健康检查接口返回的时间、日志平台显示的时间。若第一份和第二份一致而第三份不同,应检查采集器的解析规则、字段时区和展示设置,不要继续改容器镜像。
定时任务和数据库要单独验收
Cron、应用队列和数据库连接都可能有自己的时区配置。容器内执行:
docker exec app date -u
docker exec app printenv TZ
docker exec app sh -c 'cat /etc/os-release 2>/dev/null | head'
然后用一个短周期测试任务记录 UTC 和本地时间,确认任务触发时间与预期一致。数据库也要查看会话时区,例如 MySQL 可以检查:
SELECT UTC_TIMESTAMP(), NOW(), @@global.time_zone, @@session.time_zone;
数据库返回的 NOW() 与应用日志时间不一致时,先明确业务约定使用 UTC 还是本地时间,再统一连接参数和展示层。订单、审计日志、过期时间等数据建议存储为 UTC,前端或报表层再转换为用户时区。
一个可复用的排查顺序
先比较宿主机和容器的 UTC 时间,确认内核时钟是否一致;再检查 TZ、/etc/localtime 和镜像里的 tzdata;随后核对应用运行时、日志驱动和采集平台的时区处理;最后单独测试 Cron、队列和数据库会话时区。这样可以区分“系统时钟错误”“本地时区显示错误”和“日志平台二次转换”三类问题。




