コンテナを起動するスクリプトで、Dockerホストのタイムゾーンをコンテナに引き継がせたい場合がある。たとえば、コンテナが吐くファイルに、ローカルなタイムゾーンで生成日時を書き込みたいときとか。

世界中の誰もが同じスクリプトを使いまわせるようにしたければ、リテラルでタイムゾーンを書くことはできないので、何らかの手段でDockerホストのタイムゾーンを取得し、それをコンテナに引き回す必要がある。

Linuxの場合

DockerホストがLinuxの場合は簡単だ。以下はDockerホストがUbuntuの例。

コンテナのタイムゾーンは、デフォルトではUTCになっている。以下はDebianコンテナの例。

$ docker run -it --rm debian date
Thu May  4 23:17:36 UTC 2023

環境変数TZに反応してくれるコンテナなら、起動時にTZを渡せばタイムゾーンが変わる。Dockerホスト側のタイムゾーンは、たとえばtimedatectlで取り出す。

$ timedatectl --property Timezone --value show
Asia/Tokyo
$ docker run -it --rm --env TZ=$(timedatectl --property Timezone --value show) debian date
Fri May  5 08:17:57 JST 2023

あるいは、コンテナの/etc/localtimeを目的のzoneinfoへのシンボリックリンクとして張りなおす手もある。

$ docker run -it --rm debian sh -c "ln -sf /usr/share/zoneinfo/$(timedatectl --property Timezone --value show) /etc/localtime && date"
Fri May  5 08:18:00 JST 2023

ほかに、Dockerホストの/etc/localtimeをコンテナにマウントして引き継がせる手もあるが、この場合は少し注意が必要だ。

$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 30 May  4 06:49 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo
$ docker run -it --rm -v /etc/localtime:/etc/localtime:ro debian date
Fri May  5 08:18:22 JST 2023

この方法では、コンテナの/etc/localtime/usr/share/zoneinfo/Etc/UTCへのシンボリックリンクだった場合、コンテナの/usr/share/zoneinfo/Etc/UTCの中身がDockerホストの/usr/share/zoneinfo/Asia/Tokyoの中身に置き換わってしまう(Debianコンテナはこのケースに該当する)。

$ docker run -it --rm -v /etc/localtime:/etc/localtime:ro debian
root@34202b683b78:/# ls -l /etc/localtime
lrwxrwxrwx 1 root root 27 May  2 09:00 /etc/localtime -> /usr/share/zoneinfo/Etc/UTC
root@34202b683b78:/# tail --lines 1 /usr/share/zoneinfo/Etc/UTC
JST-9

万一コンテナ内で/usr/share/zoneinfo/Etc/UTCを直接参照しているプログラムがいたら、時間がずれてしまうおそれがあるので、できれば避けた方が良いかもしれない。

Windowsの場合

問題は、DockerホストがWindowsの場合だ。

PowerShellだと、タイムゾーンはGet-TimeZoneコマンドレットが返すSystem.TimeZoneInfoIdプロパティで取れるのだが、この値はIANAが定めるAsia/Tokyoのような値ではなく、Windows独自のTokyo Standard Timeのような形式なため、そのままではLinuxのコンテナに渡せない。

PS C:\> Get-TimeZone | Select-Object -ExpandProperty Id
Tokyo Standard Time

.NET 5までは、このWindows独自のタイムゾーン名をIANAの名前に変換する公式な手段がなく、TimeZoneConverterをNuGetして使うなどの方法しかなかった。

しかし.NET 6で、System.TimeZoneInfoクラスにTryConvertWindowsIdToIanaIdメソッドが追加され、これで名前の変換ができるようになった。このあたりの事情は、「Date, Time, and Time Zone Enhancements in .NET 6」に詳しい。

TryConvertWindowsIdToIanaIdメソッドは2つの引数を取る。1つ目は入力となるWindowsのタイムゾーン名の文字列で、2つ目は出力となるIANAの名前を格納する変数への参照だ。戻り値は、そのWindowsタイムゾーン名が存在したかどうかを示すブール値になる。

これをPowerShellから呼び出す場合はこんな感じになるだろう。

PS C:\> $win = Get-TimeZone | Select-Object -ExpandProperty Id
PS C:\> $iana = ""
PS C:\> [System.TimeZoneInfo]::TryConvertWindowsIdToIanaId($win, [ref]$iana)
True
PS C:\> $iana
Asia/Tokyo

これで得られた$ianaをコンテナ起動時のTZ環境変数に設定してやれば、Windowsからでもタイムゾーンが引き継げる。

PS C:\> docker run -it --rm --env TZ=$iana debian date
Fri May  5 08:19:27 JST 2023

TZに対応していないコンテナの場合は、コマンド実行前に/etc/localtimeを設定してやればよいだろう。

PS C:\> docker run -it --rm debian sh -c "ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
Fri May  5 08:19:41 JST 2023

めでたし、めでたし。

コンテナがAlpineの場合

なお、コンテナがAlpine Linuxベースの場合、デフォルトではzoneinfoが入っていないので、先にapk add tzdataしてやる必要がある。

素のままでは、TZを渡しても、/etc/localtimeを設定しても、UTCのまま。

PS C:\> docker run -it --rm --env TZ=$iana alpine date
Thu May  4 23:19:57 UTC 2023
PS C:\> docker run -it --rm alpine sh -c "ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
Thu May  4 23:20:01 UTC 2023

apk add tzdataしてからなら、タイムゾーン指定が有効になる。

PS C:\> docker run -it --rm --env TZ=$iana alpine sh -c "apk --update-cache add tzdata && date"
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2023c-r0)
OK: 10 MiB in 16 packages
Fri May  5 08:20:17 JST 2023
PS C:\> docker run -it --rm alpine sh -c "apk --update-cache add tzdata && ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2023c-r0)
OK: 10 MiB in 16 packages
Fri May  5 08:20:23 JST 2023

※バージョンメモ

  • Docker Desktop Engine 20.10.24
  • Ubuntu 22.04.2 (on WSL 1.2.5.0)
  • PowerShell 7.3.4
  • debian:latest = debian:11.7
  • alpine:latest = alpine:3.17.3