Timezone und Locale in Docker-Containern richtig konfigurieren
AI generated
FROM
RUN
Docker · Konfiguration · Internationalisierung
Timezone und Locale in Containern richtig setzen
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.

16 Min. Lesezeit Timezone Locale Internationalisierung

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.

11. FAQ: Timezone und Locale: Das Wichtigste auf einen Blick

1Warum laufen Docker-Container standardmaessig in UTC?
Weil schlanken Basis-Images aus Groessengruenden das tzdata-Paket fehlt und das Betriebssystem ohne Zeitzonendatenbank keine andere Zeitzone anwenden kann, faellt es automatisch auf UTC zurueck.
2Reicht das Setzen der TZ-Variable allein aus?
Nein, ohne installiertes tzdata-Paket kennt die Laufzeitumgebung zwar den Namen der Zeitzone, kann aber keine Offset- und Sommerzeitregeln nachschlagen, was zu einem stillen Fallback auf UTC fuehren kann.
3Wie installiere ich tzdata auf einem Alpine-Image?
Ueber den apk-Paketmanager mit apk add --no-cache tzdata, anschliessend sollte /etc/localtime per Symlink auf die gewuenschte Zeitzone unter /usr/share/zoneinfo gesetzt werden.
4Warum blockiert die tzdata-Installation manchmal den Docker-Build?
Auf Debian-basierten Images fragt das tzdata-Paket ohne DEBIAN_FRONTEND=noninteractive interaktiv nach der Zeitzone, was in einem nicht-interaktiven Build-Kontext zum Haengenbleiben fuehrt.
5Muss PHP zusaetzlich zur TZ-Variable konfiguriert werden?
Ja, PHP verwendet eine eigene date.timezone-Direktive in php.ini, die unabhaengig von der Systemzeitzone gesetzt werden sollte, um Warnungen und inkonsistentes Verhalten zu vermeiden.
6Was ist der Unterschied zwischen Timezone und Locale?
Die Timezone bestimmt den korrekten Zeitpunkt inklusive Offset zu UTC, die Locale bestimmt die sprachliche Formatierung von Datum, Uhrzeit, Waehrung und Zahlen, beide Konzepte sind unabhaengig voneinander zu konfigurieren.
7Warum funktionieren manche Locales auf Alpine nicht?
Alpine nutzt musl-libc statt glibc, und musl bringt kein vollstaendiges Locale-System mit, wodurch viele Locale-Namen schlicht nicht unterstuetzt werden, selbst wenn das Locale-Paket installiert ist.
8Wie generiere ich eine deutsche Locale auf Debian-Images?
Ueber das locales-Paket, das Aktivieren der gewuenschten Zeile in /etc/locale.gen und den anschliessenden Aufruf von locale-gen, gefolgt vom Setzen von LANG und LC_ALL als Umgebungsvariablen.
9Wie stelle ich konsistente Zeitzonen ueber mehrere Compose-Services sicher?
Am besten ueber zentrale Werte in einer .env-Datei, die per environment in jedem Service-Block referenziert werden, statt TZ und LANG in jedem Dockerfile einzeln hart zu kodieren.
10Wie pruefe ich, welche Locales in einem Container tatsaechlich verfuegbar sind?
Mit locale -a innerhalb des Containers, das alle installierten Locales auflistet. Fehlt die erwartete Locale in der Ausgabe, ist meist die Installation oder locale-gen nicht korrekt durchgelaufen.