WindowsをホストOSとしてDockerでWindowsイメージを動かす、ということをやりました。DockerのLinuxイメージについては歴史があり利用者も多くて情報がたくさん見つかるのですが、Windowsイメージについては歴史も浅く試行錯誤する部分もあり苦労しました。そんなこともあって知りえた情報を整理しておくことにします。
準備
私の環境は Windows 10 Pro
で Docker Desktop for Windows
を使用しました。使用にあたっては Hyper-VとWindowsコンテナ機能の有効化が必要になります。
Dockerを稼働するとタスクトレイにクジラのアイコンが現れます。Dockerは標準でLinuxコンテナになっているので、Windowsコンテナへ切り替えます。アイコンを右クリックして「Switch to Windows containers..
」をクリックして切り替えます。
Windowsイメージの選択
イメージ名 | Docker hubページ | 概要 |
---|---|---|
Windowsサーバコア | microsoft-windows-servercore – Official Image | Docker Hub | .NETアプリケーション向け。 |
Nanoサーバ | microsoft-windows-nanoserver – Official Image | Docker Hub | .NET Coreアプリケーション向け。 |
Windows | microsoft-windows – Official Image | Docker Hub | WindowsAPIセットを提供。 |
Windowsサーバ | microsoft-windows-server – Official Image | Docker Hub |
Windows コンテナーの基本イメージ | Microsoft Learn
コンテナ内でバッチファイルを実行する時は注意が必要。同じwindowsでも%date%
の出力がwin10(2024/01/04)とserver(Thu 01/04/2024)で異なる、といった微妙な差異があるためです。
PowerShellの利用を検討しましょう。PowerShell込のイメージもあります。私はこちらを使いました。Invoke-WebRequest
や Expand-Archive
といったコマンドが標準で使えるのでコンテナビルドが捗ります。
イメージ名 | Docker hubページ | 概要 |
---|---|---|
PowerShell | microsoft-powershell – Official Image | Docker Hub | PowerShellインストール済イメージ。tagでベースイメージを指定できる。 |
Dockerfileは下記のように書き始めます。
FROM mcr.microsoft.com/powershell:lts-nanoserver-1809
PowerShellを使う
ARGとENV
Dockerfile
内での変数的なものとしてARGとENVがあります。ARGで定義した変数はビルド時のみ環境変数として参照できます。ENVで定義した変数はビルド中だけでなくコンテナ起動後も環境変数となります。
変数を参照するときは Docker内の処理であれば ${変数名}
で。一方、RUNコマンドはPowerShellでの処理になるので環境変数と同様に ${env:変数名}
で参照します。以下サンプルです。
FROM mcr.microsoft.com/powershell:lts-nanoserver-1809
SHELL ["pwsh.exe", "-command"]
ARG name="usagi"
ENV name2="minako"
ARG msg="Moon=${name}, Venus=${name2}."
ENV msg2="Tsukino ${name}, Aino ${name2}."
RUN Write-Output ${env:msg} | Out-File -FilePath "c:\msg.txt"
RUN Write-Output ${env:msg2} | Out-File -FilePath "c:\msg.txt" -Append
実際にコンテナを起動して各変数を見てみた結果が下記です。ENVで定義した変数はコンテナ利用時に環境変数として参照できています。
PowerShell 7.2.18
Copyright (c) Microsoft Corporation.
https://aka.ms/powershell
Type 'help' to get help.
PS C:\> type .\msg.txt
Moon=usagi, Venus=minako.
Tsukino usagi, Aino minako.
PS C:\> ${env:msg}
PS C:\> ${env:msg2}
Tsukino usagi, Aino minako.
PS C:\> ${env:name}
PS C:\> ${env:name2}
minako
PS C:\>
ドライブを追加したい
単純にアプリケーションから参照する先をDドライブにするのであれば volume
コマンドを使うことで解決できますが、Dドライブにアプリケーションやデータを置いた状態をコンテナ化することはできません。
動的にドライブを追加する方法としてMS-DOS時代から存在する subst.exe
という任意フォルダをドライブとして扱えるようにするコマンドを使用します。
例えば、C:\D_DRIVE
というフォルダをDドライブにするには、subst.exe D: C:\D_DRIVE
のようにします。
このようにして作成したDドライブに対してアプリケーションやデータを置けば、実体は C:\D_DRIVE
に置かれるのでコンテナ内に収めることができます。
注意点が4つあります。
一つはベースイメージに subst.exe
が含まれていないことです。ローカルPCの C:\Windows\System32
にはあるはずなので Dockerfile
内から COPY
コマンドを使って拝借すると良いと思います。
もう一つは Dockerfile
内で RUN
コマンドで subst.exe
を使ったとして、その効果はそのRUN
コマンドの行にとどまるということです。そのためドライブ追加に関連する処理はまとめて書く必要があります。(次の RUN
コマンドでDドライブを参照するとエラーになってしまう)
RUN subst d: c:\d_drive \
&& d: \
&& mkdir app \
:
もう一つ。コンテナ起動直後はDドライブは存在しない状態です。ですのでアプリケーションを実行する前に ENTRYコマンド や CMDコマンドで実行するスクリプト内で subst.exe
によりDドライブを作成する必要があります。
最後にもう一つ。subst
で仮想ドライブを作成したとしても、プログラム側で特定の関数を用いることで元々のパスを取得することができます(例えばpythonであれば os.path.realpath
関数を使うことで実際のパスが取得可能)。
この問題は subst
以外で volume
コマンドで任意のフォルダやボリュームをDドライブとして割り当てた場合でも発生します。つまり、プログラムにドライブが存在すると見せかけることができないケースがありるということです。
PS C:\> python
Python 3.11.7 (tags/v3.11.7:fa7a6f2, Dec 4 2023, 19:24:49) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print(os.path.realpath("d:"))
C:\93E99064-82DA-44B2-85AD-7278E5B8C16F\
これは volumeによりDドライブを割り当てたときの pythonからの見え方です。
プロキシを経由する場合
企業などではプロキシ経由でDockerを使わざるをえないこともありますよね。プロキシに関する記事の中から私の環境でうまくいった方法を書いておきます。(Windows 10 Pro (22H2, 19045.2965)、Docker Desktop 4.29.0 (145265))
- Docker Desktop の
Setting>Resouces>use proxy for Windows Docker daemon
にチェックを入れる。Port欄はランダム値が設定されるので空欄で。 - 環境変数 HTTP_PROXY と HTTPS_PROXY を設定する。
例)SET HTTP_PROXT=http://user:pass@192.168.0.10:8080
- コマンドラインより、
docker info
コマンドを実行する。HTTP Proxy: http://127.0.0.1:#####
やHTTP Proxy: http://127.0.0.1:#####
という項目が表示されていればOK。 - これで docker pull ~ などが使えるようになってるはず。
ビルド中に認証ありプロキシを利用する
ビルド中にアプリケーションなどを外部サイトからダウンロードすることがあります。
認証ありプロキシを経由して playframework1.7.1
をダウンロードし、c:\app\bin
に展開するサンプルです。PowerShell は、WebからダウンロードするコマンドやZIP解凍するコマンドが標準で使えるで便利です。
FROM mcr.microsoft.com/powershell:lts-nanoserver-1809
SHELL ["pwsh.exe", "-command"]
ARG PROXY_ID=user
ARG PROXY_PASS=pass
ARG PROXY_PORT=8080
ARG PROXY_URL=192.168.0.10
ARG PROXY_HOST=http://${PROXY_URL}:${PROXY_PORT}
ARG PLAY_PATH=c:\app
# https://github.com/playframework/play1/releases/download/1.7.1/play-1.7.1.zip
ARG FILE="play-1.7.1.zip"
ARG FILE_URL="https://github.com/playframework/play1/releases/download/1.7.1/play-1.7.1.zip"
RUN $secstr = New-Object -TypeName System.Security.SecureString; \
${env:PROXY_PASS}.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}; \
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ${env:PROXY_ID}, $secstr; \
Invoke-WebRequest -Uri ${env:FILE_URL} -OutFile ${env:FILE} -Proxy ${env:PROXY_HOST} -ProxyCredential $creds;
RUN Expand-Archive -Path ${env:FILE} -DestinationPath ${env:PLAY_PATH}\bin \
&& Remove-Item -Path ${env:FILE}