Skip to content

add tzdata package#748

Open
mmichalak-swe wants to merge 6 commits intooffen:mainfrom
mmichalak-swe:Add-package-tzdata
Open

add tzdata package#748
mmichalak-swe wants to merge 6 commits intooffen:mainfrom
mmichalak-swe:Add-package-tzdata

Conversation

@mmichalak-swe
Copy link
Copy Markdown
Contributor

@mmichalak-swe mmichalak-swe commented Mar 13, 2026

Changes

  • Add tzdata package to Docker image

Reason

Installing tzdata provides the system timezone database and allows the container timezone to be configured via the TZ environment variable.

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 13, 2026

I'm a bit torn on this one, mostly because of not wanting to change things that currently work as intended. I would definitely think this is something to include in a v3, yes.

One big question in here would be: considering this would be merged and users that currently mount their TZ data as it's done right now updated without changing their config, would their setups still continue to work?

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

I just tested it by using my custom image (only adding tzdata to the existing v2.47.2 image), and commenting out the bind mounts:

environment:
  - TZ=${TZ}
image: offen/docker-volume-backup:v2.47.2-tzdata-test
volumes:
  # - /etc/timezone:/etc/timezone:ro
  # - /etc/localtime:/etc/localtime:ro
  # - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

I also tried the custom image with tzdata added, against my old configuration (without a TZ variable, and with the bind mounts):

environment:
#   - TZ=${TZ}
image: offen/docker-volume-backup:v2.47.2-tzdata-test
volumes:
  - /etc/timezone:/etc/timezone:ro
  - /etc/localtime:/etc/localtime:ro
  - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

I then tried using the custom tzdata image, with both configurations enabled together:

environment:
  - TZ=${TZ}
image: offen/docker-volume-backup:v2.47.2-tzdata-test
volumes:
  - /etc/timezone:/etc/timezone:ro
  - /etc/localtime:/etc/localtime:ro
  - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

All cases are working as expected, producing the expected time with the date command, while also creating a local backup with the correct time. Additionally, this worked while stopping and starting a container to perform the backup. I understand your hesitation, but I think this is a low-risk change with a lot of upside. To set my timezone correctly, I had to read the documentation, and then follow the guidance given in #28. That is a lot of effort for what should be a simple setting, that some users might miss!

Perhaps it is better saved for v3, but I think this functionality could be enabled without issue immediately.

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 13, 2026

To set my timezone correctly, I had to read the documentation, and then follow the guidance given in #28.

What info was missing from the docs that you could find in #28 only?

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

From #28 (comment)

/usr/share/zoneinfo:/usr/share/zoneinfo:ro

Page it's missing from: set container timezone

services:
  backup:
    image: offen/docker-volume-backup:v2
    volumes:
      - data:/backup/my-app-backup:ro
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

volumes:
  data:

I can edit and open a PR to fix

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 13, 2026

I can edit and open a PR to fix

That'd be grand 🎩

@m90 m90 mentioned this pull request Mar 16, 2026
@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

mmichalak-swe commented Mar 16, 2026

Continuing our conversation here...

Thanks! I guess I'll need a little more time to ponder #748, I hope that's ok.

No worries. I can do some more testing and report back. I was doing some research on ChatGPT and it agrees that it's a low-risk change. I think the testing I did above will cover users that upgrade to a new image that includes tzdata, while their configuration may still contain the bind mounts (the bind mounts would take precedence). Let me know if there's anything else I can help you with on this.

@le-lenn
Copy link
Copy Markdown
Contributor

le-lenn commented Mar 21, 2026

Id like to throw my hat in the ring for this addition to the codebase.

The current way of binding the OS host timezone did work for me as intended, but it feels a bit awkward as I am depending on an upstream config of my host system. Which could change. Being able to set the time zone explicitly (and also in only 1 line) would feel like a great ease of life feature.

one question that comes to mind:
What happens if both config options are set and are conflicting? Which is prioritized?

Also, if this is added, it would be great if the time zone would be logged on startup (“Timezone set to “Europe/Berlin via environment variable.”)

I also feel that the very slim increase in image size is worth it.

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

mmichalak-swe commented Mar 21, 2026

What happens if both config options are set and are conflicting? Which is prioritized?

@le-lenn great question. In short, if both are set, the timezone set by the env variable TZ (used by the tzdata package) will take precedence:

Configuration Settings Image Command Result
1 No TZ variable, bind mounts v2.47.2 $ docker exec -it volume-backup date Sat Mar 21 12:19:41 EDT 2026
2 No TZ variable, bind mounts v2.47.2-tzdata-test $ docker exec -it volume-backup date Sat Mar 21 12:20:30 EDT 2026
3 TZ variable set to something different (America/Los_Angeles) from my host's timezone (America/New_York), bind mounts v2.47.2-tzdata-test $ docker exec -it volume-backup date Sat Mar 21 09:21:04 PDT 2026

For configurations 2 and 3, I am using an image created with the following Dockerfile:

FROM offen/docker-volume-backup:v2.47.2
RUN apk add tzdata curl jq --no-cache

Configuration 1:

    environment:
      # - TZ=America/Los_Angeles
    image: offen/docker-volume-backup:v2.47.2
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

Configuration 2:

    environment:
      # - TZ=America/Los_Angeles
    image: offen/docker-volume-backup:v2.47.2-tzdata-test
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

Configuration 3:

    environment:
      - TZ=America/Los_Angeles
    image: offen/docker-volume-backup:v2.47.2-tzdata-test
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

Configuration 1 represents users today, and configuration 2 represents a new image with tzdata added. This is an important test, because it shows that the bind mounts take precedence over the addition of the tzdata package, without TZ set in the container. When users upgrade to a newer image, keep the bind mounts, but do not set TZ, their configuration will still work as it does today.

Configuration 3 shows that when TZ is set, it will override the settings from the host that are bind-mounted inside the container. I live in the America/New_York timezone, but when I set TZ to America/Los_Angeles, the timezone inside the container is superseded by what TZ is set to, even though the bind mounts from my host are still active.

I would argue that there is no need to print the current timezone upon container start, it is UTC by default. If a user modifies it from the default, they should know what they are updating it to. Worst case users can run the date command inside the container to confirm.

I like your point about the dependency created by the upstream host. The tzdata package allows for better isolation from the host, and better control over the time/timezone inside the container, regardless of the platform it is running on. It is a better design for containerization.

Let me know if there's anything I can clarify.

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 21, 2026

I guess it makes sense to do this, yes.

A few more remarks from my end:

  • should the image print a deprecation warning if the old mechanism is used? Is it even possible to know if the old mechanism is used (i.e. can we check if a file is a bind mount?)
  • can you also update the documentation in this PR?
  • I also wouldn't want to print any info on the configured timezone on startup, it's fine if users can run date

@le-lenn
Copy link
Copy Markdown
Contributor

le-lenn commented Mar 21, 2026

My line of thinking with printing the configured time zone on startup was to have a way to catch accidental misconfigurations and warn the user about it.
What happens when I misspell the time zone? (i.e “Europe/Berlinn”). Would the time zone silently be set to UTC without any warning?

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

mmichalak-swe commented Mar 22, 2026

@le-lenn the time zone defaults (without warning) to UTC if you do not provide a valid tz database time zone

@m90 we should be able to add a script that does something similar to the following:

$ docker exec -it volume-backup cat /proc/self/mountinfo | grep -E '/etc/timezone|/etc/localtime|/usr/share/zoneinfo'
544 531 252:0 /etc/timezone /etc/timezone ro,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
545 531 252:0 /usr/share/zoneinfo/America/New_York /etc/localtime ro,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
549 531 252:0 /usr/share/zoneinfo /usr/share/zoneinfo ro,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw

A possible script could look something like this:

#!/bin/sh

# List of deprecated bind-mount targets
DEPRECATED_MOUNTS="/etc/timezone /etc/localtime /usr/share/zoneinfo"
FOUND_DEPRECATED=false

for mnt in $DEPRECATED_MOUNTS; do
    # Check if the path is explicitly listed as a mount point
    if grep -q " $mnt " /proc/self/mountinfo; then
        echo "WARNING: Bind-mounting '$mnt' is DEPRECATED."
        FOUND_DEPRECATED=true
    fi
done

if [ "$FOUND_DEPRECATED" = true ]; then
    echo "-------------------------------------------------------------------"
    echo "Use the 'TZ' environment variable to set the time zone"
    echo "Please see the documentation for more details"
    echo "-------------------------------------------------------------------"
fi

# Continue to main application
exec /usr/bin/backup -foreground "$@"

This script would become the new entrypoint in the Dockerfile, and it could then call your current entrypoint. I will test this out.

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 23, 2026

The logic in the script looks good to me, but I'd prefer to have this written in Go just like everything else. Would you be capable of doing this? Happy to help out if advice is needed.

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

mmichalak-swe commented Mar 24, 2026

I am not an expert in Go, I had to get AI to help me with this one. Feel free to suggest changes or modifications you think would be helpful. I think this does a good job of capturing what we need to do, which is to detect any of the bind mounts, print a deprecation message, then hand execution off to the main binary. We could add a direct link to the page in the documentation, to the deprecation warning.

Could this possibly be included in the existing main.go file, so that there is no need to modify the container's entrypoint? That would simplify the script, and the Dockerfile would remain the same after this functionality is added.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"syscall"
)

func main() {
	deprecatedMounts := []string{"/etc/timezone", "/etc/localtime", "/usr/share/zoneinfo"}
	foundDeprecated := false

	file, err := os.Open("/proc/self/mountinfo")
	if err == nil {
		defer file.Close()
		scanner := bufio.NewScanner(file)
		
		for scanner.Scan() {
			line := scanner.Text()
			for _, mnt := range deprecatedMounts {
				if strings.Contains(line, " "+mnt+" ") {
					fmt.Printf("WARNING: Bind-mounting '%s' is DEPRECATED.\n", mnt)
					foundDeprecated = true
				}
			}
		}
	}

	if foundDeprecated {
		fmt.Println("%%%%% DEPRECATION WARNING %%%%%")
		fmt.Println("Use the 'TZ' environment variable to set the container's time zone")
		fmt.Println("Please see the documentation for more details")
		fmt.Println("%%%%% END DEPRECATION WARNING %%%%%")
	}

	binary := "/usr/bin/backup"
	args := []string{"backup", "-foreground"}

	// syscall.Exec replaces this Go process with the backup process (PID 1)
	err = syscall.Exec(binary, args, os.Environ())
	if err != nil {
		fmt.Printf("Failed to execute %s: %v\n", binary, err)
		os.Exit(1)
	}
}

@m90
Copy link
Copy Markdown
Member

m90 commented Mar 24, 2026

Looks good. Right now, deprecations warnings are printed from within the resolve method when creating a config:

// resolve is responsible for performing all implicit logic that transforms a configuration object
// into what is actually being used at runtime. E.g. environment variables are expanded or
// deprecated config options are transposed into their up to date successors. The caller is
// responsible for calling the returned reset function after usage of the config is done.
func (c *Config) resolve() (reset func() error, warnings []string, err error) {

Maybe this check can go here or we introduce a plain helper function that is called right afterwards:

unset, warnings, err := s.c.resolve()

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

My vote is for where existing deprecation warnings are already being handled/printed

@m90
Copy link
Copy Markdown
Member

m90 commented Apr 7, 2026

@mmichalak-swe Do you need any help in getting that deprecation check/notice in? In case you're just busy, that's perfectly fine, just wanted to make sure you're not awaiting any kind of feedback from my end?

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

@m90 sorry for the delayed response, I have been busy, but will be able to complete this in the next few days.

@m90
Copy link
Copy Markdown
Member

m90 commented Apr 9, 2026

No worries, please proceed as it fits your schedule and just ping me when you need help or you're done.

@mmichalak-swe
Copy link
Copy Markdown
Contributor Author

@m90 please review my latest changes when you get a chance and let me know what you think. I built the image locally and tested with the following docker-compose file:

services:
  volume-backup:
    cap_drop:
      - ALL
    container_name: volume-backup
    environment:
      - BACKUP_CRON_EXPRESSION=0 0 5 31 2 ?
      - TZ=America/New_York
    hostname: volume-backup
    image: <local test image>
    read_only: true
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    tmpfs:
      - /tmp
      - /var/lock
    user: "911:911"
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

For reference, my current local timezone is America/Los_Angeles (PDT)
Here are the logs when running the above configuration:

% docker logs volume-backup     
time=2026-04-15T02:49:32.568-04:00 level=WARN msg="Deprecated timezone bind mounts detected: /etc/timezone, /etc/localtime, /usr/share/zoneinfo. Support for these will be removed in a future version."
time=2026-04-15T02:49:32.568-04:00 level=WARN msg="`TZ=America/New_York` is set, but deprecated timezone bind mounts are still present. Remove the bind mounts after confirming timezone handling works as expected."
time=2026-04-15T02:49:32.568-04:00 level=INFO msg="Successfully scheduled backup from environment with expression 0 0 5 31 2 ?"
time=2026-04-15T02:49:32.568-04:00 level=WARN msg="Scheduled cron expression 0 0 5 31 2 ? will never run, is this intentional?"

% docker exec volume-backup date
Wed Apr 15 02:49:37 EDT 2026

When I comment out the TZ variable, the time zone in the container is still set (as expected) to my host's time zone via the bind mounts. The logs change to the following:

% docker logs volume-backup     
time=2026-04-14T23:49:51.913-07:00 level=WARN msg="Deprecated timezone bind mounts detected: /etc/timezone, /etc/localtime, /usr/share/zoneinfo. Support for these will be removed in a future version."
time=2026-04-14T23:49:51.913-07:00 level=WARN msg="Set the container timezone using the `TZ` environment variable instead."
time=2026-04-14T23:49:51.913-07:00 level=WARN msg="Refer to the documentation for migration details."
time=2026-04-14T23:49:51.913-07:00 level=INFO msg="Successfully scheduled backup from environment with expression 0 0 5 31 2 ?"
time=2026-04-14T23:49:51.913-07:00 level=WARN msg="Scheduled cron expression 0 0 5 31 2 ? will never run, is this intentional?"

% docker exec volume-backup date
Tue Apr 14 23:49:55 PDT 2026

Copy link
Copy Markdown
Member

@m90 m90 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, thank you. I left a couple of comments inline, but nothing major. Let me know what you think.

> data:
> ```

## Recommended approach (using `TZ`)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess we can move the recommended approach up? Ideally, users will only read this far and move on without even knowing the old approach still exists?

Comment thread docs/.gitignore
@@ -1,2 +1,3 @@
_site
.jekyll-cache
.DS_Store
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this? I would argue OS-specific files like this should go in a global gitignore on individual machines.

Comment thread cmd/backup/command.go
}
}

func (c *command) logStartupWarnings(config *Config) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method really needed? I'm a bit confused this is used in here, but then it's not used when printing the config. Maybe it'd be better to just loop over the warnings in place above, even if it's a slight repetition?

Comment thread cmd/backup/config.go
func (c *Config) timezoneDeprecationWarnings() []string {
mounts, err := mountedPaths("/proc/self/mountinfo")
if err != nil {
return nil
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think this means something went really wrong and we might not even want to continue at all? I.e. I think the method should return []string, error

Comment thread cmd/backup/config.go
return mounts, nil
}

func (c *Config) timezoneDeprecationWarnings() []string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can have a more descriptive name? Something like checkTimezoneConfiguration. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants