The most surprising thing about setting the timezone inside a Docker container is that it’s often done the wrong way, leading to subtle bugs that are hard to track down.

Let’s see it in action. Imagine a simple Python application that logs the current time.

import datetime
import os

print(f"Current time (UTC): {datetime.datetime.now(datetime.timezone.utc)}")
print(f"Current time (local): {datetime.datetime.now()}")
print(f"Timezone from environment: {os.environ.get('TZ')}")

If we build a Docker image with this and run it without setting the timezone, we’ll see something like this:

Current time (UTC): 2023-10-27 10:30:00.123456+00:00
Current time (local): 2023-10-27 03:30:00.123456
Timezone from environment: None

The local time is clearly wrong. It’s defaulting to UTC or some arbitrary value.

The problem this solves is ensuring that timestamps within your containerized applications are consistent and accurate, especially when dealing with logs, scheduled jobs, or any time-sensitive operations. Without proper timezone configuration, you can have discrepancies between the host system’s time and the container’s time, leading to incorrect data interpretation and operational issues.

Internally, Linux systems determine the local timezone primarily through two mechanisms:

  1. The /etc/localtime file: This is a symbolic link to a timezone data file located in /usr/share/zoneinfo/. For example, in a UTC system, /etc/localtime might point to /usr/share/zoneinfo/UTC.
  2. The TZ environment variable: This variable can override the system’s default timezone. If set, applications that respect this variable will use it to determine the local time.

The most robust and common way to set the timezone is by copying the correct timezone data file to /etc/localtime within the container.

Here’s a Dockerfile that sets the timezone to "America/New_York":

FROM ubuntu:latest

RUN apt-get update && apt-get install -y tzdata \
    && ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime \
    && dpkg-reconfigure --frontend noninteractive tzdata

COPY your_app.py /app/your_app.py
WORKDIR /app

CMD ["python", "your_app.py"]

Let’s break down the RUN command:

  • apt-get update && apt-get install -y tzdata: This installs the tzdata package, which contains the timezone information files.
  • ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime: This is the core step. It creates a symbolic link named /etc/localtime that points to the timezone file for "America/New_York". The -f flag ensures that if /etc/localtime already exists, it will be overwritten.
  • dpkg-reconfigure --frontend noninteractive tzdata: While the symbolic link is usually enough, this command ensures that any lingering configuration prompts are handled non-interactively, finalizing the timezone setup.

When you build and run this image:

docker build -t timezone-app .
docker run timezone-app

You’ll see:

Current time (UTC): 2023-10-27 10:30:00.123456+00:00
Current time (local): 2023-10-27 06:30:00.123456
Timezone from environment: None

Notice how the "local" time is now correctly displayed as America/New_York (EDT, which is UTC-4 during daylight saving time).

Another common approach, though sometimes less reliable for all applications, is to set the TZ environment variable.

FROM ubuntu:latest

RUN apt-get update && apt-get install -y tzdata

ENV TZ=America/New_York
RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && dpkg-reconfigure --frontend noninteractive tzdata

COPY your_app.py /app/your_app.py
WORKDIR /app

CMD ["python", "your_app.py"]

This Dockerfile also works, and you’ll get the same output. The ENV TZ=America/New_York line sets the environment variable, and then subsequent RUN commands use it.

However, some older or less sophisticated applications might only check /etc/localtime and ignore the TZ variable. Therefore, setting both or prioritizing the /etc/localtime symlink is generally preferred for maximum compatibility.

A common pitfall is to only set the TZ environment variable without ensuring /etc/localtime is also correctly configured, or by not installing tzdata at all.

For example, if you only did this:

FROM alpine:latest

ENV TZ=America/New_York

COPY your_app.py /app/your_app.py
WORKDIR /app

CMD ["python", "your_app.py"]

And your Python app was compiled against a standard C library that doesn’t rely on the TZ env var but on system files, you might still get incorrect local times. Alpine Linux is particularly minimal and might not have tzdata installed by default, and its /etc/localtime might not be set up correctly if you don’t explicitly manage it. Running docker run alpine:latest date will likely show UTC.

The correct way to handle this in Alpine is:

FROM alpine:latest

RUN apk update && apk add --no-cache tzdata \
    && cp /usr/share/zoneinfo/America/New_York /etc/localtime \
    && echo "America/New_York" > /etc/timezone

COPY your_app.py /app/your_app.py
WORKDIR /app

CMD ["python", "your_app.py"]

Here, apk add --no-cache tzdata installs the timezone data. cp /usr/share/zoneinfo/America/New_York /etc/localtime copies the file directly (Alpine often doesn’t use symlinks for /etc/localtime in the same way Debian-based images do). echo "America/New_York" > /etc/timezone is also a common practice on some systems to explicitly set the timezone file name, which tzdata might use.

The most subtle issue is when your application relies on the host’s timezone. Docker containers, by default, are unaware of the host’s timezone settings. If you need your container to exactly mirror the host’s timezone, you can mount the host’s /etc/localtime and /etc/timezone files into the container.

docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro your_image

The :ro flag ensures these files are mounted read-only, preventing the container from accidentally modifying the host’s timezone configuration. This is often the simplest solution if you just need consistency and don’t want to hardcode a timezone in your Dockerfile.

When you’ve correctly set the timezone, the next problem you’ll likely encounter is ensuring that log rotation and other time-based system events within the container also respect this new timezone.

Want structured learning?

Take the full Docker course →