Support reading timezone info from env (#748)

* add tzdata package

* updated timezone documentation

* add missing bind-mount from updated docs

* Add timezone deprecation warnings and related logging to backup commands

* fix lint CI job error

* Address PR comments: refactor timezone deprecation warning handling, update docs
This commit is contained in:
Mitchell Michalak
2026-04-20 08:10:00 -04:00
committed by GitHub
parent 0955d6fe80
commit 6a83ce4034
5 changed files with 141 additions and 10 deletions

View File

@@ -14,7 +14,7 @@ FROM alpine:3.23
WORKDIR /root
RUN apk add --no-cache ca-certificates && \
RUN apk add --no-cache ca-certificates tzdata && \
chmod a+rw /var/lock
COPY --from=builder /app/cmd/backup/backup /usr/bin/backup

View File

@@ -36,6 +36,13 @@ func (c *command) runAsCommand() error {
}
for _, config := range configurations {
warnings, warnErr := config.timezoneDeprecationWarnings()
if warnErr != nil {
return errwrap.Wrap(warnErr, "error collecting startup warnings")
}
for _, w := range warnings {
c.logger.Warn(w)
}
if err := runScript(config); err != nil {
return errwrap.Wrap(err, "error running script")
}
@@ -102,6 +109,14 @@ func (c *command) schedule(strategy configStrategy) error {
for _, cfg := range configurations {
config := cfg
warnings, warnErr := config.timezoneDeprecationWarnings()
if warnErr != nil {
return errwrap.Wrap(warnErr, "error collecting startup warnings")
}
for _, w := range warnings {
c.logger.Warn(w)
}
id, err := c.cr.AddFunc(config.BackupCronExpression, func() {
c.logger.Info(
fmt.Sprintf(

View File

@@ -4,6 +4,7 @@
package main
import (
"bufio"
"bytes"
"crypto/x509"
"encoding/pem"
@@ -97,6 +98,7 @@ type Config struct {
GoogleDriveImpersonateSubject string `split_words:"true"`
GoogleDriveEndpoint string `split_words:"true"`
GoogleDriveTokenURL string `split_words:"true"`
Timezone string `envconfig:"TZ"`
source string
additionalEnvVars map[string]string
}
@@ -322,3 +324,81 @@ func (c *Config) resolve() (reset func() error, warnings []string, err error) {
}
return
}
func mountedPaths(path string) (map[string]struct{}, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() { _ = file.Close() }()
mounts := make(map[string]struct{})
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, " - ", 2)
fields := strings.Fields(parts[0])
if len(fields) < 5 {
continue
}
mounts[fields[4]] = struct{}{}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return mounts, nil
}
func (c *Config) timezoneDeprecationWarnings() ([]string, error) {
mounts, err := mountedPaths("/proc/self/mountinfo")
if err != nil {
return nil, errwrap.Wrap(err, "error reading mount info")
}
deprecatedMounts := []string{
"/etc/timezone",
"/etc/localtime",
"/usr/share/zoneinfo",
}
var found []string
for _, mnt := range deprecatedMounts {
if _, ok := mounts[mnt]; ok {
found = append(found, mnt)
}
}
if len(found) == 0 {
return nil, nil
}
var warnings []string
// Primary deprecation message (compressed)
warnings = append(warnings,
fmt.Sprintf(
"Deprecated timezone bind mounts detected: %s. Support for these will be removed in a future version.",
strings.Join(found, ", "),
),
)
// Guidance based on TZ usage
if c.Timezone == "" {
warnings = append(warnings,
"Set the container timezone using the `TZ` environment variable instead.",
"Refer to the documentation for migration details.",
)
} else {
warnings = append(warnings,
fmt.Sprintf(
"`TZ=%s` is set, but deprecated timezone bind mounts are still present. Remove the bind mounts after confirming timezone handling works as expected.",
c.Timezone,
),
)
}
return warnings, nil
}

View File

@@ -34,6 +34,13 @@ func runPrintConfig() error {
for _, warning := range warnings {
fmt.Printf("warning:%s\n", warning)
}
timezoneWarnings, warnErr := config.timezoneDeprecationWarnings()
if warnErr != nil {
return errwrap.Wrap(warnErr, "error collecting timezone deprecation warnings")
}
for _, warning := range timezoneWarnings {
fmt.Printf("warning:%s\n", warning)
}
// insert line breaks before each field name, assuming field names start with uppercase letters
formatted := formatter.ReplaceAllString(fmt.Sprintf("%+v", *config), "\n$1")
fmt.Printf("%s\n", formatted)

View File

@@ -1,26 +1,55 @@
---
title: Set the timezone the container runs in
title: Setting the Time Zone
layout: default
parent: How Tos
nav_order: 8
---
# Set the timezone the container runs in
# Setting the Time Zone
By default a container based on this image will run in the UTC timezone.
As the image is designed to be as small as possible, additional timezone data is not included.
In case you want to run your cron rules in your local timezone (respecting DST and similar), you can mount your Docker host's `/etc/timezone`, `/etc/localtime`, and `/usr/share/zoneinfo` in read-only mode:
## Use Environment Variable `TZ`
A container started using this image will default to UTC. To modify the time zone, set the `TZ` environment variable to a valid [tz database time zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones):
```yml
services:
backup:
image: offen/docker-volume-backup:v2
image: offen/docker-volume-backup:latest
environment:
- TZ=Europe/Berlin
volumes:
- data:/backup/my-app-backup:ro
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /usr/share/zoneinfo:/usr/share/zoneinfo:ro
volumes:
data:
```
## Notes
This approach is preferred because it:
- avoids dependency on host configuration
- works consistently across environments
### Compatibility
- Bind-mounting timezone files will continue to work if `TZ` is not set.
- If `TZ` is set, it takes precedence over any bind-mounted timezone configuration.
- An invalid `TZ` value will cause the container to default to UTC.
:warning: **Deprecation Warning**
The method described below (bind-mounting files from the host) is **deprecated**. Please use the new method described above (`TZ`)
> ```yml
> services:
> backup:
> image: offen/docker-volume-backup:latest
> volumes:
> - data:/backup/my-app-backup:ro
> - /etc/timezone:/etc/timezone:ro
> - /etc/localtime:/etc/localtime:ro
> - /usr/share/zoneinfo:/usr/share/zoneinfo:ro
>
> volumes:
> data:
> ```