Docker 部署后文章发布时间变成 UTC 的排查与修复

Docker 部署后文章发布时间变成 UTC 的排查与修复
最近在 Docker 部署 YESCMS,并使用 PostgreSQL 数据库时,发现后台发布文章后的时间不对:页面显示出来的时间像是按 UTC 来算的,比本地时间少了 8 个小时。
这个问题看起来像数据库时区问题,但真正排查下来,根因在容器运行环境。
现象
本地开发环境发布文章,时间正常。
部署到 Docker 后,发布文章的时间会偏向 UTC。例如北京时间下午发布,页面上显示出来却像是早上。
项目里的文章发布时间使用的是类似这样的写法:
create_time = DateTime.Now,
update_time = DateTime.Now,
这段代码本身没有做 UTC 转换。它取的是当前运行环境的本地时间。
根因
Docker 容器默认时区通常是 UTC。
所以问题不是 PostgreSQL 主动把时间改成了 UTC,也不是页面展示时强行转换了时区,而是应用运行在容器里时,DateTime.Now 拿到的“本地时间”本来就是 UTC。
换句话说:
DateTime.Now 取的是容器时间
容器默认是 UTC
所以写入数据库的时间就是 UTC 风格的时间
PostgreSQL 里如果字段使用 timestamp without time zone,它只是存储一个日期时间值,不会替你记住这个值到底属于哪个时区。应用写进去什么,它就保存什么。
不建议到处改业务代码
遇到这个问题,最直接的反应可能是把代码改成:
DateTime.UtcNow.AddHours(8)
这不推荐。
原因很简单:这会把“中国时区”硬编码进业务代码。以后如果部署到其它地区,或者同一个镜像给不同地区使用,就会继续产生新的时间问题。
更好的做法是让运行环境告诉应用:当前部署到底使用哪个时区。
推荐修复方式
在 docker-compose.yml 里给应用容器加上 TZ 环境变量:
services:
yescms:
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
TZ: Asia/Shanghai
这样处理后,容器内的本地时间会按 Asia/Shanghai 计算,DateTime.Now 获取到的就是北京时间。
如果以后部署到其它地区,只需要改 compose 里的 TZ:
TZ: America/Los_Angeles
不需要重新改代码,也不需要把 Docker 镜像固定成某个国家或地区的时区。
为什么不直接写进 Dockerfile
也可以在 Dockerfile 里设置:
ENV TZ=Asia/Shanghai
但这样会让镜像默认固定为中国时区。
如果这个镜像只在中国服务器上运行,问题不大;但如果以后镜像可能部署到国外服务器,固定在 Dockerfile 里就不够灵活。
所以更稳妥的做法是:
镜像保持通用
部署环境通过 docker-compose.yml 指定时区
这也是配置和镜像职责分离的做法。
修改后如何生效
修改 docker-compose.yml 后,需要重建或重启容器:
docker compose up -d --build
如果使用的是远程镜像,且没有重新构建镜像,也可以直接重建容器:
docker compose up -d
然后进入容器检查时间:
docker exec -it yescms date
如果输出的是北京时间,说明容器时区已经生效。
旧文章时间怎么办
这个修复只影响后续新写入的数据。
已经写入数据库的旧文章时间不会自动修正。因为数据库里保存的是一个普通日期时间值,系统无法可靠判断哪些记录是 UTC 写入、哪些记录本来就是正确时间。
如果确定某一批旧数据全部都是 UTC 错误时间,可以在备份数据库后,单独写 SQL 批量加 8 小时。但这个操作会直接修改历史数据,应该先确认范围,再执行。
总结
Docker 部署后文章发布时间显示成 UTC,本质上是容器默认时区导致的。
对于使用 DateTime.Now 的应用,最简单、最稳的修复方式是在部署层指定时区:
TZ: Asia/Shanghai
不要把时区补偿写死到业务代码里,也不要轻易把通用镜像固定成某一个地区的时区。把时区放在 compose 配置里,既能解决当前问题,也方便以后迁移到其它地区部署。


