UTC-Standard verstehen und Datumsformate zuverlaessig konfigurieren
Container laufen standardmaessig in UTC ohne installierte Zeitzonendaten, was bei PHP- und Node-Anwendungen mit lokalisierten Datumsformaten schnell zu falschen Ausgaben fuehrt, wenn TZ, tzdata und Locale nicht bewusst konfiguriert werden.
Inhaltsverzeichnis
- 1. Warum Container standardmaessig UTC nutzen
- 2. Die TZ-Umgebungsvariable richtig einsetzen
- 3. Das tzdata-Paket in schlanken Images installieren
- 4. Ein vollstaendiges Dockerfile-Beispiel fuer PHP-Anwendungen
- 5. Locale-Probleme bei PHP- und Node-Anwendungen
- 6. Die PHP intl-Erweiterung und Node.js Full-ICU
- 7. Locale-Generierung in Debian-basierten Images
- 8. Konsistente Konfiguration ueber mehrere Services in Compose
- 9. Testing und Debugging von TZ- und Locale-Problemen
- 10. Zusammenfassung
- 11. FAQ
1. Warum Container standardmaessig UTC nutzen
Die meisten offiziellen Basis-Images, insbesondere schlanke Varianten wie alpine oder slim-Images auf Debian-Basis, enthalten aus Groessengruenden kein tzdata-Paket und keine /etc/localtime-Konfiguration. Ohne diese Daten faellt der Container automatisch auf UTC zurueck, weil das Betriebssystem mangels Zeitzonendatenbank keine andere Zeitzone anwenden kann, selbst wenn der Host-Rechner in einer anderen Zeitzone laeuft.
Das ist bewusst so gewaehlt, weil UTC als Referenzzeitzone keine Sommerzeitumstellung kennt und dadurch fuer serverseitige Prozesse wie Logging, Scheduling und Datenbankzeitstempel die verlaesslichste Wahl ist. Problematisch wird es erst, wenn eine Anwendung selbst lokalisierte Zeiten anzeigen soll, etwa in einer Rechnung oder einer Benutzeroberflaeche fuer Endkunden in einer bestimmten Region, ohne dass die Umrechnung explizit im Anwendungscode erfolgt.
2. Die TZ-Umgebungsvariable richtig einsetzen
Die TZ-Umgebungsvariable, etwa TZ=Europe/Berlin, signalisiert vielen Programmiersprachen und Bibliotheken, welche Zeitzone fuer Zeitfunktionen verwendet werden soll, ohne dass das Betriebssystem selbst umgestellt wird. PHP, Node.js, Python und viele weitere Laufzeiten lesen diese Variable aus und wenden sie auf Datums- und Zeitfunktionen an, sofern die zugrunde liegende Zeitzonendatenbank im Image vorhanden ist.
Genau hier liegt eine haeufige Fehlerquelle: TZ allein reicht nicht, wenn das tzdata-Paket fehlt, weil die Laufzeit dann zwar den Namen der Zeitzone kennt, aber keine Regeln fuer Offset und Sommerzeitumstellung nachschlagen kann. Das Ergebnis ist ein stiller Fallback auf UTC oder eine Fehlermeldung, je nach Sprache und Bibliothek, was in Tests oft unbemerkt bleibt, weil dort haeufig ohnehin mit UTC gearbeitet wird.
# TZ als Umgebungsvariable im Container setzen
docker run -e TZ=Europe/Berlin myapp
# Pruefen, welche Zeitzone der Container aktuell nutzt
docker exec myapp date
docker exec myapp cat /etc/timezone
3. Das tzdata-Paket in schlanken Images installieren
Damit TZ tatsaechlich wirkt, muss die Zeitzonendatenbank als Paket im Image vorhanden sein. Auf Debian- und Ubuntu-basierten Images heisst das Paket tzdata, auf Alpine-Images tzdata ebenfalls, allerdings ueber den apk-Paketmanager. Nach der Installation liegt die vollstaendige IANA-Zeitzonendatenbank unter /usr/share/zoneinfo, auf die sich die meisten Sprach-Laufzeiten beziehen, sobald TZ gesetzt ist.
Bei Debian-basierten Images ist zusaetzlich Vorsicht bei der interaktiven Installation geboten: Ohne DEBIAN_FRONTEND=noninteractive fragt das tzdata-Paket waehrend der Installation interaktiv nach der Zeitzone ab und blockiert den Build, weil in einem nicht-interaktiven Docker-Build-Kontext keine Eingabe moeglich ist. Diese Umgebungsvariable sollte deshalb vor der Installation gesetzt werden.
# Dockerfile-Ausschnitt: tzdata auf Debian-Basis korrekt installieren
FROM debian:bookworm-slim
ENV TZ=Europe/Berlin
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y tzdata \
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
&& dpkg-reconfigure -f noninteractive tzdata \
&& rm -rf /var/lib/apt/lists/*
# Dockerfile-Ausschnitt: tzdata auf Alpine-Basis
FROM node:20-alpine
ENV TZ=Europe/Berlin
RUN apk add --no-cache tzdata \
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone
4. Ein vollstaendiges Dockerfile-Beispiel fuer PHP-Anwendungen
In einer typischen PHP-Anwendung reicht das Setzen von TZ auf Betriebssystemebene allein nicht aus, weil PHP eine eigene, von der Systemzeitzone unabhaengige Konfiguration ueber php.ini kennt. Fehlt dort date.timezone, greift PHP zwar oft auf die Systemzeitzone zurueck, meldet aber je nach php.ini-Einstellung eine E_WARNING beim Aufruf von Datumsfunktionen, was in Produktionsumgebungen unnoetig Log-Rauschen erzeugt.
Ein vollstaendiges Setup kombiniert deshalb drei Ebenen: die TZ-Umgebungsvariable fuer das Betriebssystem, das tzdata-Paket fuer die zugrunde liegende Zeitzonendatenbank und eine eigene php.ini-Direktive fuer date.timezone, damit PHP unabhaengig von der Aufrufumgebung immer dieselbe Zeitzone verwendet, auch wenn TZ aus irgendeinem Grund nicht gesetzt sein sollte.
FROM php:8.4-fpm
ENV TZ=Europe/Berlin
RUN apt-get update && apt-get install -y tzdata \
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone \
&& printf '[Date]\ndate.timezone = ${TZ}\n' \
> /usr/local/etc/php/conf.d/timezone.ini \
&& rm -rf /var/lib/apt/lists/*
5. Locale-Probleme bei PHP- und Node-Anwendungen
Waehrend TZ und tzdata den Zeitpunkt korrekt verschieben, steuert die Locale, wie Datum, Uhrzeit, Waehrungsbetraege und Zahlen sprachlich formatiert werden. Eine PHP-Anwendung, die mit strftime oder IntlDateFormatter deutsche Monatsnamen wie 'August' statt 'August' in der englischen Schreibweise oder korrekt formatierte Dezimalzahlen mit Komma statt Punkt ausgeben soll, braucht dafuer eine installierte und aktivierte de_DE-Locale im Container.
Schlanke Basis-Images enthalten haeufig nur die C- oder POSIX-Locale, bei der alle Ausgaben auf ein neutrales, meist englischsprachiges Format zurueckfallen. Das fuehrt in der Praxis zu Symptomen wie englischen Wochentagsnamen in einer eigentlich deutschsprachigen Oberflaeche oder zu Warnungen, wenn eine Anwendung explizit setlocale(LC_TIME, 'de_DE.UTF-8') aufruft und die Locale im System gar nicht vorhanden ist.
6. Die PHP intl-Erweiterung und Node.js Full-ICU
Fuer robuste Internationalisierung reicht die klassische setlocale-Funktion in PHP oft nicht aus, weil ihr Verhalten stark vom jeweiligen Betriebssystem und dessen installierten Locales abhaengt. Die intl-Erweiterung auf Basis von ICU (International Components for Unicode) umgeht dieses Problem, indem sie ihre eigene, in PHP eingebettete Locale- und Zeitzonendatenbank mitbringt und damit unabhaengig von den im Container installierten System-Locales konsistente Formatierung liefert.
Aehnlich verhaelt es sich bei Node.js: Die offiziellen Node-Images werden standardmaessig mit Full-ICU ausgeliefert, sodass Intl.DateTimeFormat und verwandte APIs bereits ohne zusaetzliche Systemkonfiguration alle Locales unterstuetzen. Wer ein besonders schlankes, selbst gebautes Node-Image ohne Full-ICU verwendet, muss dagegen entweder das ICU-Datenpaket small-icu gegen full-icu austauschen oder die Daten ueber NODE_ICU_DATA explizit einbinden, sonst werden nicht-englische Locales stillschweigend ignoriert.
# PHP intl-Erweiterung installieren (Debian-Basis)
docker exec myapp php -m | grep intl
apt-get install -y php8.4-intl
# Node.js pruefen, ob Full-ICU aktiv ist
docker exec myapp node -e \
"console.log(new Intl.DateTimeFormat('de-DE', {month:'long'}).format(new Date()))"
7. Locale-Generierung in Debian-basierten Images
Auf Debian- und Ubuntu-basierten Images muessen benoetigte Locales explizit ueber das locales-Paket generiert werden, da standardmaessig nur C.UTF-8 vorhanden ist. Der Befehl locale-gen erzeugt aus der in /etc/locale.gen aktivierten Zeile die kompilierte Locale-Datei, die anschliessend ueber LANG und LC_ALL als Umgebungsvariablen fuer alle Prozesse im Container aktiv gesetzt wird.
Auf Alpine-basierten Images ist die Situation komplizierter, weil die musl-libc von Alpine im Gegensatz zu glibc kein vollstaendiges Locale-System mitbringt und viele Locale-Namen schlicht nicht unterstuetzt. Fuer Anwendungen mit ernsthaftem Lokalisierungsbedarf ist ein glibc-basiertes Image wie debian-slim oder ein spezielles Alpine-Image mit nachinstallierter glibc-Kompatibilitaetsschicht oft die praktikablere Wahl als der Versuch, Locales unter musl zu erzwingen.
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y locales \
&& sed -i '/de_DE.UTF-8/s/^# //g' /etc/locale.gen \
&& locale-gen \
&& rm -rf /var/lib/apt/lists/*
ENV LANG=de_DE.UTF-8
ENV LC_ALL=de_DE.UTF-8
8. Konsistente Konfiguration ueber mehrere Services in Compose
In einem typischen Compose-Stack mit PHP-FPM, Nginx, MySQL und einem Cron-Container ist es wichtig, TZ, LANG und LC_ALL fuer alle beteiligten Services identisch zu setzen, sonst entstehen inkonsistente Zeitstempel zwischen Anwendungslogik und Datenbank. MySQL etwa nutzt standardmaessig ebenfalls UTC, kann aber ueber eine eigene TZ-Konfiguration oder ueber CONVERT_TZ-Funktionen in Queries eine abweichende Zeitzone verwenden, was bei uneinheitlicher Konfiguration zu schwer nachvollziehbaren Zeitverschiebungen fuehrt.
Am wartungsfreundlichsten ist es, TZ, LANG und LC_ALL zentral in einer .env-Datei zu pflegen und ueber environment in jedem Service-Block des Compose-Files zu referenzieren, statt die Werte in jedem Dockerfile einzeln hart zu kodieren. Aenderungen an der Zeitzone, etwa bei einem Wechsel des Zielmarkts, sind dann an einer einzigen Stelle moeglich, ohne mehrere Images neu bauen zu muessen.
# .env
TZ=Europe/Berlin
LANG=de_DE.UTF-8
LC_ALL=de_DE.UTF-8
# docker-compose.yml
services:
php-fpm:
environment:
TZ: ${TZ}
LANG: ${LANG}
LC_ALL: ${LC_ALL}
mysql:
environment:
TZ: ${TZ}
9. Testing und Debugging von TZ- und Locale-Problemen
Ein haeufiger Fallstrick ist, dass Timezone- und Locale-Probleme in automatisierten Tests unbemerkt bleiben, weil Testumgebungen oft ohnehin mit UTC und der C-Locale arbeiten und die eigentliche Fehlerursache erst in Produktion mit realen TZ- und LANG-Werten sichtbar wird. Ein einfacher Smoke-Test, der beim Container-Start date, locale und php -i | grep date.timezone ausgibt und gegen erwartete Werte prueft, deckt solche Abweichungen schon vor dem Deployment auf.
Fuer die manuelle Fehlersuche in einem laufenden Container liefert docker exec container date einen schnellen Ueberblick ueber die effektive Systemzeit, waehrend locale -a alle im Image tatsaechlich installierten Locales auflistet. Wenn eine erwartete Locale in dieser Liste fehlt, ist meist die Installation oder locale-gen nicht korrekt gelaufen, unabhaengig davon, was in LANG oder LC_ALL gesetzt wurde.
| Base-Image | tzdata-Installation | Locale-Support | Empfehlung |
|---|---|---|---|
| debian:bookworm-slim | apt-get install tzdata | Vollstaendig ueber locales-Paket | Beste Wahl bei Lokalisierungsbedarf |
| alpine (musl) | apk add tzdata | Eingeschraenkt, viele Locales fehlen | Nur bei reinem UTC-Betrieb ohne Locale-Anforderungen |
| ubuntu:24.04 | apt-get install tzdata | Vollstaendig ueber locales-Paket | Gleichwertig zu Debian |
| distroless | Nicht nachinstallierbar | Nicht vorhanden | Nur wenn Anwendung UTC/C-Locale toleriert |
Mironsoft
Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung
Docker-Setups, die im Team und in Produktion tragfähig bleiben?
Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.
Dockerfile-Review
Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.
Security-Audit
Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.
CI/CD-Integration
Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.
10. Zusammenfassung
Timezone und Locale: Das Wichtigste auf einen Blick
Ausgangslage
Schlanke Basis-Images laufen ohne tzdata standardmaessig in UTC.
Zeitzone
TZ-Variable plus installiertes tzdata-Paket fuer korrekte Zeitverschiebung.
Locale
locale-gen und LANG/LC_ALL fuer sprachlich korrekte Datumsformate.
Konsistenz
Zentrale .env-Werte fuer alle Compose-Services vermeiden Zeitversatz.