空飛ぶ気まぐれ雑記帳

主に趣味とかプログラミングについて扱います。

【Windows】DockerでPythonアプリケーションをビルドする方法

背景

年に一度しか更新されず、前置きが長いポエミーな本ブログで未だによく見られている記事にGUIアプリを作る話があったので、折角ならもうちょっとモダンに何とかできないかと。 思い至ったので、実際に作ってみました。

ところで、私は普段WindowsでAnacondaを使ってPythonを使っているのですが、Anaconda+PyInstallerという組み合わせは非常にまずい組み合わせであります。 何がまずいかと言えば、ファイルサイズです。 Anacondaは依存関係が異常に複雑で、特に依存ライブラリのないプログラムであっても、PyInstallerを使ってExeを作ると200MB近いファイルサイズになってしまいます。 それにPySideや何やら入ってしまうと、どんどんファイルサイズが大きくなってしまいます。

それは何とも残念な話ですので、通常のPythonにPyInstallerをインストールしてビルドすれば、ファイルサイズの増大を回避できますが、いくらvenvをインストールしてやったとしても、それは何かと面倒なことがあります。 まず、新規でアプリケーションを作る度にvenvで環境を作るというのも良いと思いますが、個人的にいつもrequirements.txtの書き忘れが問題になります。 Linuxならpipenvを使えばそれも回避できますが、Windowsでpipenvはまともな動きをしてくれないので、それもダメです。

はてさてどうしたものかとなりますが、いい感じにやる方法にDockerがあります。 Dockerの説明はもう良いでしょと思うので割愛しますが、Dockerを使えば使い捨てのビルド環境が作れるのでいい感じにやれるなと思って今回の記事のようなものを作りました。

Windows container on Windows

Windows環境でWindow Containerを動かすのってどうやんの?って思ったので調べたのですが、タスクトレイのDockerを右クリックして出てくるメニューから切り替えられるそうです。 非常に簡単でありますね。ちなみに、WindowsコンテナとLinuxコンテナは同時に使えない問題があるそうですが、まあそれはそれということでしょうか。 f:id:elda27:20200623210106p:plain

ちなみにですが、Windowsコンテナのライセンスってどうなってんの?ってなことも気になって調べましたが、開発用途なら特に使用に制約は無いようです(ないよね?) docs.microsoft.com

Dockerfile

適当なアプリケーションをビルドする用のDockerfileは下記のとおりです。 pythonソースコードと同じディレクトリにファイルを保存している前提で、以下のようなDockerfileを書きました。

FROM winamd64/python:3.8.3-windowsservercore-1809 

# /tmpにソースコードを展開して必要なファイルをインストール
# 事前にrequirements.txtをコピーして必要なライブラリをインストールして
# キャッシュしておく。
WORKDIR /tmp
COPY requirements.txt /tmp
RUN pip install -r requirements.txt
RUN pip install pyinstaller

# それ以外のソースコードをホストからコピーしてpyinstallerを実行する。
COPY . /tmp
RUN pyinstaller Main.spec
WORKDIR /app
RUN cp -r /tmp/dist/* .
RUN Remove-Item -Path /tmp -Recurse -Force
RUN mkdir /mount
ENTRYPOINT cp -r -Force * /mount

ちなみに使っているpyinstallerのspecファイルは以下の通りです。 依存ライブラリとしてeelを使うアプリケーションを考えているので、ライブラリのインストールディレクトリからdataファイルに追加するようにしています。

# -*- mode: python -*-

import eel
from pathlib import Path

path_to_eel_js = Path(eel.__file__).parent / 'eel.js'

block_cipher = None

a = Analysis(
  ['./app/__main__.py'],
  pathex=[],
  binaries=None,
  datas=[
    (str(path_to_eel_js), './eel/')
  ],
  hiddenimports=[
    'pkg_resources.py2_warn',
    'eel',
    'bottle_websocket'
  ],
  hookspath=None,
  runtime_hooks=None,
  excludes=None,
  cipher=block_cipher
)
pyz = PYZ(
  a.pure, a.zipped_data,
  cipher=block_cipher
)
exe = EXE(
  pyz,
  a.scripts,
  a.binaries,
  a.zipfiles,
  a.datas,
  name='ClipDraw',
  debug=False,
  strip=None,
  upx=True,
  console=True, 
  #icon='mainIcon.ico'
)
app = BUNDLE(
 exe,
 name='app.exe',
 #icon='mainIcon.ico'
)

これだけで勝手にexeファイルができます。

コンテナの使い方

実際にはDockerファイル内でビルドしたパッケージをローカルにダウンロードする必要があるので、以下のコマンドでホストにコピーする必要があります。

docker run --rm -it -v ${pwd}\build:C:\mount python-build-env:latest

ちなみに上記はpowershellで実行するケースでたぶんcmdだと下記のようになります。

docker run --rm -it -v %cd%\build:C:\mount python-build-env:latest

これでWindows環境で動くPythonアプリケーションが出来上がるはずです。 LinuxMacでも基となるOSをそろえてやれば(例えばLinuxならPython:3.8-busterとか)同様の手順でアプリケーションを浮くることができるはずです。