1.fhxd.de

Your container is UTC and your logs are lying about when

2026-05-21

A container inherits the host's kernel clock, which is UTC, and it does not inherit the host's timezone. A minimal image has no /etc/localtime and often no tzdata at all, so everything inside it formats timestamps as UTC.

In summer, in Berlin, that is a two hour error. It is large enough to matter and small enough that a line reading 14:32 instead of 16:32 does not look wrong. You correlate the container's log against the host's journal, the events do not line up, and you go looking for a dropped message that was never dropped.

Do not fix it by setting TZ

The reflex is to pass -e TZ=Europe/Berlin and move on. That works for the one process that reads TZ, does nothing for a static binary with no tzdata to read, and creates a worse problem: now some of your containers emit local time and some emit UTC, and half of them shift by an hour twice a year without telling you.

Fix it by making everything UTC and offset-explicit

The stable arrangement is that everything logs UTC with an explicit offset in the timestamp, and conversion to local time happens exactly once, at the point a human reads it. RFC 3339 with the Z:

2026-05-21T12:04:19Z

A timestamp with no offset is not a timestamp, it is a number that resembles one. If your log format emits 2026-05-21 12:04:19 with nothing after it, you cannot correlate it against anything without knowing which machine produced it and what that machine believed its timezone to be — and the second fact is not in the log.

Checking what a container actually thinks

docker run --rm IMAGE date -u +%FT%TZ
docker run --rm IMAGE sh -c 'readlink -f /etc/localtime 2>/dev/null || echo "no tzdata"'

If the second command prints no tzdata, then TZ in that image is decoration. The container will report UTC no matter what you set, which — given the argument above — is the outcome you wanted anyway.

The one place local time is correct

Cron. A backup scheduled for 03:00 should run at 03:00 local, including across a DST transition, because the reason it is at 03:00 is that nobody is awake. systemd timers get this right if you let them: OnCalendar=*-*-* 03:00:00 is interpreted in the system timezone, and the edge cases on the two transition nights are documented in systemd.time(7) rather than left to chance.