Docker Container Time Drift: Syncing Container Clock with Host RTC

Root Cause Diagnosis

Containers do not have their own clock. They share the host kernel’s system clock (CLOCK_REALTIME), so a container cannot drift independently on a native Linux host. If container time is wrong, either the host clock is wrong (no NTP sync, or RTC and system time disagree), or the container runs inside a VM (Docker Desktop, WSL2, Colima, Hyper-V, VirtualBox) whose guest clock stops advancing during host sleep or suspend and is not corrected on resume. Only the timezone, which comes from /etc/localtime and TZ inside the container, is per-container, so many “drift” reports are actually timezone mismatches.

Quick Fix

Compare host and container time first:

date -u; docker run --rm alpine date -u

If the two UTC outputs differ by more than a second, the host or the Docker VM clock is wrong. Fix it at that layer.

# Native Linux host: force an NTP sync
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd    # or: sudo systemctl restart chronyd

# Docker Desktop / WSL2: resync the VM clock from the Windows host
sudo hwclock -s            # inside the WSL2 distro
# or, from PowerShell:  wsl --shutdown   (then restart Docker Desktop)

If the UTC outputs match but local times differ, the problem is timezone only. Run the container with -e TZ=Europe/Berlin (substitute your zone).

Step-by-Step Resolution

1. Determine whether it is drift or a timezone difference

docker exec <container> date -u
date -u
docker exec <container> sh -c 'echo "TZ=$TZ"; readlink -f /etc/localtime'

Matching UTC with differing local output means the timezone is wrong. Differing UTC means the kernel clock is wrong.

See also  Docker Compose network external bridge host port already allocated error fix

2. Check the host clock and RTC (native Linux)

timedatectl
sudo hwclock --show --utc

In the timedatectl output, System clock synchronized: yes and NTP service: active are required. If RTC in local TZ: yes, the RTC holds local time, which commonly happens on dual-boot machines with Windows. Set it to UTC:

sudo timedatectl set-local-rtc 0 --adjust-system-clock

3. Enable a time synchronization daemon on the host

Debian/Ubuntu:

sudo timedatectl set-ntp true
sudo systemctl enable --now systemd-timesyncd
timedatectl timesync-status

RHEL/Fedora/Rocky/Alma, or hosts that need fast stepping after long offsets:

sudo dnf install -y chrony
sudo systemctl enable --now chronyd
chronyc tracking
sudo chronyc makestep

Do not run NTP clients inside containers. Without CAP_SYS_TIME they cannot set the clock, and with it they would modify the host clock for every container.

4. Write the RTC from the system clock after a correction

sudo hwclock --systohc --utc

Chrony does this automatically if /etc/chrony/chrony.conf (or /etc/chrony.conf) contains:

rtcsync

5. Set the container timezone correctly

Preferred, via environment variable (works for images that include tzdata):

docker run -e TZ=America/New_York <image>

Compose:

services:
  app:
    image: <image>
    environment:
      - TZ=America/New_York
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro

Mounting /etc/localtime works only on hosts that have it as a valid zoneinfo file or symlink. /etc/timezone is Debian-specific and does not exist on RHEL-family hosts, so omit that mount there. Minimal images (alpine, scratch, distroless) need tzdata installed for TZ to take effect:

RUN apk add --no-cache tzdata
# Debian/Ubuntu: RUN apt-get update && apt-get install -y --no-install-recommends tzdata

6. Docker Desktop (macOS/Windows) and WSL2: resync the VM clock

The Docker VM’s clock freezes during host sleep and hibernation, then resumes with a stale value.

See also  Fixing apt-get update Hash Sum Mismatch Error on Ubuntu Archive Mirrors

WSL2 (inside the distro):

sudo hwclock -s

Automate on WSL2 by adding to /etc/wsl.conf:

[boot]
systemd=true
command="hwclock -s"

Restart with wsl --shutdown from PowerShell. Docker Desktop on macOS: restart Docker Desktop to force a resync, or update to a current release, which resyncs on wake.

Colima/Lima:

colima ssh -- sudo chronyc makestep
# or restart the VM
colima stop && colima start

7. Verify

for i in 1 2 3; do date -u +%s; docker run --rm alpine date -u +%s; sleep 5; done

Timestamp pairs should differ by at most 1 second consistently across iterations.

Why This Fails for Some Users

  • Sleep and suspend on Docker Desktop, WSL2, or laptops: The VM clock halts during host sleep. Correction happens only on resume or on a manual resync. Time is wrong until then.
  • Drift measured as a timezone offset: An offset of exact whole hours or half-hours is a timezone problem, not drift. Fix with TZ in Step 5.
  • TZ set but ignored: The image lacks tzdata, or the application reads /etc/localtime directly and ignores the environment variable. Mount /etc/localtime or install tzdata.
  • Dual-boot RTC conflict: Windows stores local time in the RTC while Linux assumes UTC, producing a constant offset after each boot switch. Use set-local-rtc 0 on Linux, or set the Windows registry value RealTimeIsUniversal (DWORD 1) under HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation.
  • Container cannot change the clock: date -s or ntpdate inside a container fails with Operation not permitted. CAP_SYS_TIME is dropped by default, and granting it alters the host clock globally. Fix the host instead.
  • Firewall blocks NTP: UDP port 123 outbound is blocked, so timedatectl timesync-status shows no sync. Point chrony or timesyncd at an internal server via NTP= in /etc/systemd/timesyncd.conf or server/pool lines in the chrony config.
  • Host is a cloud VM with a failing time source: Use the provider’s local time source (for example 169.254.169.123 on AWS, metadata.google.internal on GCP) in the chrony config.
  • Hypervisor guest clock disagreement: VMware, VirtualBox, and Hyper-V integration services can step the guest clock independently of NTP. Disable host-guest time sync, or disable the competing NTP daemon, so only one authority sets the clock.
  • Time namespaces: Linux 5.6+ supports time namespaces, but they offset only CLOCK_MONOTONIC and CLOCK_BOOTTIME, not wall-clock time. They do not cause date drift, and Docker does not enable them by default.
  • Application-level misbehavior: JVM, Python, and Node processes cache the timezone at startup. Restart the process after changing TZ, and for Java also set -Duser.timezone=<zone>.
  • Timestamps in logs differ from date: The logging driver or collector (for example json-file, Fluentd, Loki) timestamps entries in UTC on the host side. Compare with docker logs --timestamps.
  • Rootless Docker and Podman: The clock is still the host’s, but the timezone mount path may be unreadable under user namespaces. Verify with docker exec <container> ls -l /etc/localtime.

Leave a Comment