rcmdnk's blog

20240217_pythonpoetrydocker_200_200

Dockerに Poetryを使って必要なPythonのパッケージをインストールして 環境を構築する方法について。

Poetry+Dockerのベストプラクティス

PoetryのDiscussionsで以下のような議論があります。

Document docker poetry best practices · python-poetry · Discussion #1879

4年くらい前に始まってまだcloseしてないもの。

何がベストか、定まってないのでdocument化もされてませんが、 見ていくと参考になるものがあります。

この辺も参考にしつつ、

  • パッケージ群の管理としてのpoetryを使用する場合
  • poetryで管理している特定のプロジェクトのpyproject.tomlを使った環境構築

の2つの場合でDockerfileなどを用意する方法について見てみます。

パッケージ管理として使う

特定のパッケージ用のpyproject.tomlとかではなくて 実際に何かしらを実行したい環境をPoetryを使って作りたい、という場合、 以下のようなファイル構成にします。

1
2
3
4
$ tree
   |-- Dockerfile
   |-- poetry.lock
   |-- pyproject.toml

pyproject.toml

pyproject.tomlは以下のような感じで。

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[tool.poetry]
name = "myenv"
version = "0.0.1"
description = ""
authors = ["rcmdnk <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
pandas = "^2.2.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

pandasは依存パッケージのあるただの例です。

requirements.txtと同じようなものですが、 poetryの場合はpoetry.lockを使って依存関係を含めたものを作ることが簡単に出来るので 細かくバージョン管理したい場合には便利です。

poetry.lock

1
poetry lock

pyproject.tomlに書かれた依存関係を含めたpoetry.lockを作ります。

requirements.txtpandasだけ書いて、 一旦空の仮想環境を作ってからpip install -r requirements.txtでインストールして pip freeze > requirements.txtで依存関係を含めたリストを作れば同じようなことになりますが、 大変なので。

Dockerfile

このpython環境をDockerで作るためにpyproject.tomlpoetry.lockのあるディレクトリに以下のようなDockerfileを用意します。

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FROM python:3.12-slim-bookworm as builder

ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1

WORKDIR /app

COPY pyproject.toml poetry.lock ./

RUN pip install poetry
RUN poetry install --no-root

#########

FROM python:3.12-slim-bookworm as runtime

ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
USER_NAME=appuser

COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV

WORKDIR /app

RUN useradd -r -u 1000 $USER_NAME
RUN chown -R $USER_NAME:$USER_NAME /app

USER $USER_NAME
  • 2段階ビルド

builderruntimeの2段階ビルドを行います。 builderpoetryを使ってパッケージをインストールし、 runtimeでその仮想環境だけを持ってきて使うことで 最終的なイメージを小さくするためです。

ただ、今回は最初のbuild段階でも特にコンパイルなどは行わないので runtimeで使うのと同じ小さいイメージを使ってますし 正直このレベルだとわざわざ分けてイメージをクリーンにしなくても良いレベルではあります。 もしくはpoetryなどインストールしたものをアンインストールしてしまえば runtimeでやるのと同じサイズを作ることが出来ます。

ただ、slimバージョンだけではライブラリが足りなかったり build時にはapt-getなどで何かしらパッケージをインストールして行う必要がある場合には それら不要なものすべてをクリーンにするのは難しいので このように2段階ビルドを行うことで簡単にクリーンなイメージを作ることが出来ます。

また、runtime側にCOPYするファイルをちょっと変えるだけ、等の場合には builderのキャッシュが残っていればbuilderステージをスキップできるので ビルド時間を短縮することが出来ます。

  • イメージについて

元のイメージは python - Official Image の最新のDebianのイメージ。

今回は特にコンパイルとかはしないので 最小限にするためにslimバージョンを使っています。

alpineもありますが、ライブラリの違いなどで一般的なLinuxに比べるとパフォーマンスが悪かったりするので 1 Python公式イメージの中から選ぶならDebian系のslimなものを選んでおくのが無難です2

  • PIPの環境設定

PIP_NO_CACHE_DIR=1はキャッシュを使わない設定。 ここではpipでは直接的にはpoetryをインストールしているだけですが、 Dockerfileの中では基本的にpipで何度も同じパッケージをインストールすることは無いのでキャッシュは意味ないので無効にしています。 ただbuild側での話なのでキャッシュを作っても最終的なイメージには影響ありません。 途中段階のイメージがちょっとだけ軽くなる程度。

PIP_DISABLE_PIP_VERSION_CHECK=1pipのバージョンチェックを無効にする設定。 チェックしてwarningを出されても無視するだけなので。 pipのバージョンアップがセンシティブな場合にはきちんとチェックするなりpip install -U pipを最初に必ず実行するなりしますが、現状そこまで必要ないと思います。

  • Poetryのインストール

ここではpip install poetrybuilderのグローバル環境に入れています。

poetryのバージョンもきちんと管理したい場合は

1
RUN pip install poetry=1.7.1

のようにバージョン指定を。

また、もしそのイメージのグローバルのPython環境を直接使うような場合はpoetryの依存パッケージで汚さないように

1
RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python

と直接いれることも出来ますが、今回はベースイメージでかつ使うのは仮想環境だけなのでよりシンプルにできるpipでグローバルに入れてます。

  • Poetryの仮想環境作り

最初の builderの方でpoetryを使ってパッケージインストールを行い、 その仮想環境部分(.venv)だけをruntime側に持ってきてその中のbinPATHを通しています。

これでクリーンな最終的なイメージを作ることが出来ます。

仮想環境の作り方としては POETRY_VIRTUALENVS_IN_PROJECT=1でカレントディレクトリ以下に仮想環境を作るようにして、 今回はもともとプロジェクトを管理しているものではなくdependenciesにあるパッケージをインストールしたいだけなので インストールは--no-rootを使って依存パッケージだけをインストールするようにしています。 srcやプロジェクト名のディレクトリなどのソースディレクトリがなければ warningが出るだけで結果的には変わりませんが、 間違って入れられてしまうものがあったりするのを防ぐためにも付けておくべきです。

Poetryで管理しているプロジェクトの環境構築

Poetryで管理しているプロジェクトをそのままDockerで使いたい場合、 poetry installで作った仮想環境はそのまま使えません。

poetry installでは--no-rootを付けずに行った場合、 プロジェクトのルートディレクトリのsrcまたはプロジェクト名のディレクトリがプロジェクト名のパッケージとしてインストールされますが、 インストールされるのはソースコードのディレクトリへのパスを書いた.pthファイルだけなので(pip install -e <package path>の状態)、 それが入った仮想環境ディレクトリだけをコピーしてもコードが見つかりません。

Dockerイメージの中でPythonを起動したらそのプロジェクトがパッケージとして認識されているような状態にするため、以下のようにします。

ファイルの構成としては以下のような状態を想定します。

1
2
3
4
5
6
7
8
9
$ tree
   |-- Dockerfile
   |-- poetry.lock
   |-- pyproject.toml
   |-- src
   |    |-- myproject
   |    |    |-- app.py
   |    |    |-- __init__.py
   |    |    |-- main.py

src

srcディレクトリにはプロジェクトのソースコードが入っているとします。

__init__.py
1
2
3
from .main import main
from .app import hello
__all__ = ["main", "hello"]
app.py
1
2
def hello():
    return "Hello, World!"
main.py
1
2
3
4
5
6
7
8
9
from .app import hello


def main():
    print(hello())


if __name__ == "__main__":
    main()

こんな感じで、Hello, World!を出力するプロジェクトを想定します。

mainの方はcliとして使うことを想定しています。

pyproject.toml

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[tool.poetry]
name = "myproject"
version = "0.0.1"
description = ""
authors = ["rcmdnk <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.11"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
hello = "myproject:main"

先ほどとnameを変えてpandasを消してますが、 加えてtool.poetry.scriptshelloというコマンドをmyproject:mainとして登録しています。

これで環境が正しくインストールされていればhelloコマンドが使えるようになります。

poetry.lock

先ほど同様poetry lockで作ります。

Dockerfile

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM python:3.12-slim-bookworm as builder

ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
VIRTUAL_ENV=/app/.venv

WORKDIR /app

RUN pip install poetry-plugin-export

COPY pyproject.toml poetry.lock src ./

RUN poetry export --without-hashes --format=requirements.txt > requirements.txt
RUN echo "./" >> requirements.txt

RUN python -m venv "$VIRTUAL_ENV"
RUN . "$VIRTUAL_ENV/bin/activate" && pip install -r requirements.txt

#########

FROM python:3.12-slim-bookworm as runtime

ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
USER_NAME=appuser

COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV

WORKDIR /app

RUN useradd -r -u 1000 $USER_NAME
RUN chown -R $USER_NAME:$USER_NAME /app

USER $USER_NAME
  • 2段階ビルド

runtime側は先ほどと全く同じです。

一方builder側はだいぶ変わっています。

  • poetry-plugin-export

まず、poetry-plugin-exportをインストールして poetry exportrequirements.txtを作ります。

poetry exportpoetryのサブコマンドで、 現在はpoetryをインストールすれば使えますが、

1
2
3
Warning: poetry-plugin-export will not be installed by default in a future version of Poetry.
In order to avoid a breaking change and make your automation forward-compatible, please install poetry-plugin-export explicitly. See https://python-poetry.org/docs/plugins/#using-plugins for details on how to install a plugin.
To disable this warning run 'poetry config warnings.export false'.

といった警告が出るようになっています。

poetry-plugin-exportをインストールすれば依存関係でpoetryもインストールされます。

  • コピーするファイル

今回はpyproject.tomlpoetry.lockに加えてsrcディレクトリもをコピーします。

  • poetry export

poetry exportrequirements.txtを作りますが、 --without-hashesでハッシュを含めないようにしています。

次に./を追加していますが、 poetryでルートディレクトリでpip install .とすれば プロジェクト自体も含めてpyproject.tomlに書かれた依存関係を含めたパッケージがインストールされます。

そのままだと各パッケージは^1.0.0など範囲を指定してあるだけのものだったりするものもあるのでpoetry.lockの内容を含めることで毎回同じ環境を作ることが出来ます。

./にはハッシュ情報を付けてない状態で requirements.txtを使ったpip installでは ハッシュを持ったものとそうでないものが混在するとエラーになるので この方法だと--without-hashesは必須です。

  • 仮想環境の作り方

今回はpoetryではなく、venvで仮想環境を作ります。 poetry/appIN_PROJECTな状態で作るのと同じ名前になるように

1
VIRTUAL_ENV=/app/.venv

と仮想環境ディレクトリを指定して作成し、 activateしてからrequirements.txtを使ってインストールします。

あとは出来た仮想環境ディレクトリだけをruntime側に持ってきて使います。

これでDockerの中でpythonを実行すればimport myprojectが出来ますし、 コマンドラインからhelloというコマンドを打てばHello, World!と出力される状態になります。

その他気になることなど

プロジェクトをそのまま使う場合、ハッシュも含めてrequirements.txtを作る

PyPIではパッケージの同じバージョンを再生成することは出来ないので ハッシュを含めずとも基本的にはバージョンの指定だけで全く同じ環境を作ることが出来ます(アーキテクチャなどが同じ環境であるならば)。

ただPyPI以外の場所からインストールすることがあったり、より明確にハッシュを含めたい場合、以下のようにすると良いかな、と。

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM python:3.12-slim-bookworm as builder

ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1

WORKDIR /app

COPY pyproject.toml poetry.lock src ./

RUN pip install poetry
RUN poetry install --no-root

RUN poetry build
RUN poetry run pip install --no-deps dist/*.whl

ここでは最初の例のようにpoetryで仮想環境を作っています。

途中まではsrcをコピーすること以外は一緒で 最後にプロジェクトをbuildしてwheelファイルを作り、 それをpip installでインストールしています。

依存パッケージは既にpoetry install --no-rootでインストールしてあるので --no-depsを付けてmyprojectに関するものだけをインストールしています。

最初のものとの互換性を考えるとこちらの方がわかりやすいのと 依存パッケージのハッシュもきちんと管理してるので良いかもしれません。

まあ、好きな方で良いかと。

Poetryの環境変数設定

上で使っているものはPOETRY_VIRTUALENVS_IN_PROJECTだけですが、 poetryを使った環境構築の例を見ると他にもいくつかよく使われているものがあります。

Poetry - Python dependency management and packaging made easy

ただ、なんとなく、コピペで、で使われているだけのものもあるみたいです3

ちょうどこれを書いている時こんな議論が出てきてました。

it is not a thing, this is cargo-culting

実際自分もPOETRY_NO_INTERACTIONをきちんと確認しようとしてドキュメントに無いな、と思いながら探してたらこれを見つけたわけですが、 実際、POETRY_NO_INTERACTIONは存在しないようです。

poetryコマンド自体には--no-interaction (-n)というオプションがありますが、これはグローバルオプションでどのサブコマンドに対しても使えるものになっています。

なので

1
poetry --no-interaction help

と、インタラクティブなものがない場合でも使えて当然同じ結果になります。

よくある例のDockerfileで使うのは基本的にpoetry installだけですが、 そもそもpoetry installでインタラクティブなものに出くわしたことが無いので意味があるかはわかりません。 (もしかしたら知らないだけであるかもしれませんが。。。)

あるもの例としてはpoetry initがあります。

なので、これをPOETRY_NO_INTERACTIONを使って試してみます。

以下のテストはpoetry 1.7.1で行っています。

何も指定しない場合は

1
2
3
4
5
$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [tmp]:

のようにインタラクティブな作業が始まります。

次に--no-interactionを付けると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ poetry init --no-interaction
$ cat pyproject.toml
[tool.poetry]
name = "tmp"
version = "0.1.0"
description = ""
authors = ["rcmdnk <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"


[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

といった感じに適当な初期値を使った状態でpyproject.tomlが作られます。

最後にPOETRY_NO_INTERACTIONを使ってみます。

1
2
3
4
5
$ POETRY_NO_INTERACTION=1 poetry init

This command will guide you through creating your pyproject.toml config.

Package name [tmp]:

と、POETRY_NO_INTERACTIONを使ってもインタラクティブな作業が始まります。

というわけで、 it is not a thing, this is cargo-culting.

PYTHONの環境変数設定

同様にPYTHONの環境変数で使われているものも不要だろうと思うものがあります。

  • PYTHONDONTWRITEBYTECODE=1

これは設定すると__pycache__というディレクトリの中に作るpycといったバイトコードを書かないようになります。

バイトコードはモジュールがimportされた時に作られるもので、 次回以降のimport時にはそれを使うことで高速になります。

Document docker poetry best practices · python-poetry · Discussion #1879

これの中でも質問されてますが、buildの段階でこれは本当に必要なのか、と。

実際にはbuild時には関係ありません。

関係ない、というか、これを指定してあってもパッケージをインストールするとバイトコードは生成されます。

仮にこれをDockerfileの中で指定するとしたら runtime側で使うのであればまだわかります。

複数ファイルからなるスクリプトをコピーして入れておいて メインのファイルから他のファイルをimportするようなことをする場合には バイトコードが生成されるので、この場合 PYTHONDONTWRITEBYTECODE=1を使ってバイトコードを書かないようにする、ということは意味があるかもしれません。 基本的にdocker runしたらそれで終わりなのでそこで作られたバイトコードは2度と使われることはないので。

ただ、上のように作った環境でやりたいことは通常は何らかインストールされたコマンドを実行したり、 1つのpythonスクリプトを実行することだと思うのでその際には いずれにしろバイトコードは生成されません。

というわけで、PYTHONDONTWRITEBYTECODE=1はきちんと作用はしますが、 少なくともbuild過程で使うことは意味がないし、 runtimeで使う場合にも意味がある場合は限られています。

でも上の議論の中では

It still generates the .pyc files but it won't write them to disk. Python will generate them on the fly every time you start your application.

A few places where I have seen it:

https://sourcery.ai/blog/python-docker/
https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/

で議論が終わってしまっていて、 確かに参考先でも同じように使ってますが、ですが、確かめてはない模様。

実際、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ python -m venv myenv
o@) $ . myenv/bin/activate
(myenv) $ export PYTHONDONTWRITEBYTECODE=1
(myenv) $ pip install pyyaml
Collecting pyyaml
  Using cached PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.1 kB)
Using cached PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (757 kB)
Installing collected packages: pyyaml
Successfully installed pyyaml-6.0.1

[notice] A new release of pip is available: 23.3.1 -> 24.0
[notice] To update, run: pip install --upgrade pip
(myenv) $ ls myenv/lib/python3.11/site-packages/yaml/
composer.py     dumper.py   events.py    nodes.py     reader.py       scanner.py     _yaml.cpython-311-x86_64-linux-gnu.so
constructor.py  emitter.py  __init__.py  parser.py    representer.py  serializer.py
cyaml.py        error.py    loader.py    __pycache__  resolver.py     tokens.py
(myenv) $ ls myenv/lib/python3.11/site-packages/yaml/__pycache__/
composer.cpython-311.pyc     emitter.cpython-311.pyc   loader.cpython-311.pyc  representer.cpython-311.pyc  tokens.cpython-311.pyc
constructor.cpython-311.pyc  error.cpython-311.pyc     nodes.cpython-311.pyc   resolver.cpython-311.pyc
cyaml.cpython-311.pyc        events.cpython-311.pyc    parser.cpython-311.pyc  scanner.cpython-311.pyc
dumper.cpython-311.pyc       __init__.cpython-311.pyc  reader.cpython-311.pyc  serializer.cpython-311.pyc

という感じに生成されます。

一方、

app.py
1
2
def hello():
    return "Hello, World!"

を作ってこの環境下でimportを試してみると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(myenv) $ ls
app.py  myenv
(myenv) $ export PYTHONDONTWRITEBYTECODE=1
(myenv) $ python -c 'from app import hello;print(hello())'
Hello, World!
(myenv) $ ls
app.py  myenv
(myenv) $ unset PYTHONDONTWRITEBYTECODE
(myenv) $ python -c 'from app import hello;print(hello())'
Hello, World!
(myenv) $ ls
app.py  myenv  __pycache__
(myenv) $ ls __pycache__/
app.cpython-311.pyc

といった感じでPYTHONDONTWRITEBYTECODE=1を使うとバイトコードの生成を制御することが出来ていることがわかります。

なのでおそらく最初は1段階のruntimeのみなDockerfileで使われている例があり、 それをそのままコピペして最初に書いただけ、が始まりで そのまま広く使われてだけだと思います。

ただあまりに多くの場所で当たり前のように使われていて、 上の議論でも質問があったにもかかわらず(しかも一回食い下がったのに)一蹴されて終わっているのでこれだけ試してみた今でもなにかあるんじゃないかと思ってしまいます。。。

  • PYTHONUNBUFFERED=1

これも良くあるもので、Pythonの標準出力をバッファリングしないようにするものです。

ただこれもbuildの段階で使うことは意味がないし、 逆に必要であればきちんとruntimeで指定しなくてはいけません。

これを指定するのはdocker runしたときに、何らかの理由でコンテナが落ちてしまった際、バッファリングされた出力が処理されないまま消えてしまうことを防ぐためです。

ただ、現在(python 3.7以降)では通常のテキストレイヤー部はバッファリングされないようになっているので、 画像を出力するなどの特殊な場合を除いては必要ないです。

-u
Force the stdout and stderr streams to be unbuffered. This option has no effect on the stdin stream.

See also PYTHONUNBUFFERED.

Changed in version 3.7: The text layer of the stdout and stderr streams now is unbuffered.

いずれにしろこれもbuildでは意味ないし、 おそらく昔作られたなにかの1段階ビルドのDockerfileのものをコピペして使われているだけだと思います。

特にこちらに関してはやりたいことは確実にruntime側での話なので ちょっと影響が出るレベルにもなってくるかと思います。 (それともなんらかbuildで設定したENVが引き継がれるとかある?少なくともいくつか試した環境のいずれでも引き継がれるようなことはありませんでしたが。)

PIPの環境変数設定

PIPの環境設定の

PIP_NO_CACHE_DIR=1はbuildステージで実際に効果はありますが、 これに関しては設定値がちょっと厄介です。

もともとこの値は0, false, off, noが設定されているとキャッシュが無効になるようになっていました。

ただ、NO_CACHE_DIRという名前がキャッシュディレクトリを作らないなので、 これをNOにするということは逆にキャッシュを作る、ということになります。

というわけで理由がわからない、という理由で、1, true, on, yesと指定するとキャッシュを無効化するようにしたいということでそれらを有効にしましたが、 後方互換性を保つために0などを設定してもキャッシュが無効になるようになっています。

より正確にはdistutils.util.strtoboolが解釈できる文字ならばキャッシュが無効になるようになっています。 (上記リストに加えてy, t, n, fでも可。)

PIP_NO_CACHE_DIR and PIP_NO_BUILD_ISOLATION behave opposite to how they read · Issue #5735 · pypa/pip

Make PIP_NO_CACHE_DIR behave as it reads, and not crash pip by cjerdonek · Pull Request #5884 · pypa/pip

なので

  • PIP_NO_CACHE_DIR=on
  • PIP_NO_CACHE_DIR=off

は正反対のように見えますが、実際にはどちらもキャッシュを無効にします。

  • PIP_NO_CACHE_DIR=0
  • PIP_NO_CACHE_DIR=1

も。

かといって、たまにある、何でも良いから変数が空文字以外でとして定義されていれば効果を発揮するというわけでもなく、 上位以外の値をいれるとstrtoboolが解釈出来ずにエラーになります。

onでもoffでも同じならそうだろう、経験ある人ほどそう予測しがちだと思いますが。

この変更はpipの19.0から入っています。

Changelog - pip documentation v19.0

これより前は上のコードの変更を見るとcallbackを使わずにstore_falseになっています。

store_false, store_trueに設定されているオプションは環境変数やconfigでの設定では 値をstrtoboolで判定し、その結果がTrueならそれぞれFalse, TrueFalseなら逆にTrue, Falseを 設定するようになっています。

pip/src/pip/_internal/cli/parser.py at 76554a48eec96273717fbf87eb6e4a12374ddf4d · pypa/pip

PIP_DISABLE_PIP_VERSION_CHECKの方はstore_trueのオプションになっていて、 1onなどを設定するとpipのバージョンチェックを無効化し、0offなど、または何も設定しなければ有効になります。

pip/src/pip/_internal/cli/cmdoptions.py at 76554a48eec96273717fbf87eb6e4a12374ddf4d · pypa/pip

OSのパッケージ管理

今回使っているDebianのイメージでは aptapt-getコマンドでパッケージを管理するシステムになっています。

今回は特に追加のパッケージをインストールしていないのでapt-getは使っていませんが、 よく使うので一応追加で。

builderではgitを使い、runtimeではcurlが必要だとする場合、

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
FROM python:3.12-slim-bookworm as builder

ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1

RUN apt-get update && apt-get install -y git

WORKDIR /app

COPY pyproject.toml poetry.lock ./

RUN pip install poetry
RUN poetry install --no-root

#########

FROM python:3.12-slim-bookworm as runtime

ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
USER_NAME=appuser

RUN apt-get update && apt-get install -y curl && apt-get clean && rm -rf /var/lib/apt/lists/*

COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV

WORKDIR /app

RUN useradd -r -u 1000 $USER_NAME
RUN chown -R $USER_NAME:$USER_NAME /app

USER $USER_NAME

こんな感じになります。

builderの方は無理にクリーンにしなくても良いということでapt-get cleanとかも実行しません。

runtime側ではapt-get cleanに加えてrm -rf /var/lib/apt/lists/*も実行しています。 これはapt-get update/var/lib/apt/listsにキャッシュが作られるのでそれを削除しています。

また、aptコマンドが導入されて以降、コマンドラインではapt-getとかではなくapt(apt getなど)を使うことが推奨されていますが、

ユーザーは インタラクティブ 用途には apt(8) コマンドを使うことが推奨されますし、シェルスクリプト中ではapt-get(8) や apt-cache(8) コマンドを使うことが推奨されます。

とのことでDockerfileなどの中ではapt-getなどを使います。

Sponsored Links
  1. performance - Why is the Alpine Docker image over 50% slower than the Ubuntu image? - Super User

  2. Pythonの公式イメージとしてはないですが、 Googleが作っている DistolessPythonイメージを使うのも1つの手。

    ただ、こちらはPythonのバージョンが固定です(今現在のpython3は3.11の模様。)

  3. 自分でもDockerfileは結構何も考えずにコピペしてよく確認してないものもあると思うので。。。特に環境変数とか、影響が見た目上すぐわからないものは反映されてなくても気づくのは難しい。。。

Sponsored Links

« GitHub Actionsでのsyntax error near unexpected token `(' 回避 Poetryで管理しているパッケージテストをGitHub ActionsでPoetry及びDockerで行う »

}