Your container is UTC and your logs are lying about when
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.