🤔

docker+poetryでpythonを使う

に公開
2

はじめに

今までpythonを使う時はdocker+pipを使ってきた。
pythonのパッケージマネージャーとして、poetryの存在を知り、興味があったので使ってみた。

この記事ではpoetryで使用するpyproject.tomlファイルを作成するために、docker-compose.yamlやpoetry newコマンドを使用してますが、正直効率が悪いと思います。
poetry newコマンドで作成されるディレクトリを再利用しようと思い、このような流れになっていますが、適宜やりやすい方法でやった方が早く作業ができると思います。

前提環境

pc: MacBookPro(2017)
os: Big Sur
Homebrew 3.4.10
Docker version 20.10.13
docker-compose version 1.29.2
Dockerで作成したコンテナ上でpoetryでPython環境を構築します。
poetryは仮想環境を構築できるメリットがありますが、dockerコンテナ上では特に意味がないと思ったので、今回は仮想環境を作らないように設定します。

インストール関連(事前準備)

Homebrewのインストール

Homebrewのページからインストール用のコマンドをコピペして、ターミナルなどで実行します。

install homebrew
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
version
$ brew -v
Homebrew 3.4.10

Docker, docker-composeのインストール

下記のコマンドにより、homebrewでDocker, docker-composeをインストールしていきます。

search docker
$ brew search docker
==> Formulae
docker                          docker-machine                    docker-squash
docker-clean                      docker-machine-completion         docker-swarm
docker-completion                 docker-machine-driver-hyperkit    docker2aci
docker-compose                  docker-machine-driver-vmware      dockerize
docker-compose-completion         docker-machine-driver-vultr       lazydocker
docker-credential-helper          docker-machine-driver-xhyve       powerman-dockerize
docker-credential-helper-ecr      docker-machine-nfs                mockery
docker-gen                        docker-machine-parallels
docker-ls                         docker-slim

==> Casks
docker            docker-edge         docker-toolbox      dockey              dozer
install Docker
$ brew install docker
install docker-compose
$ brew install docker-compose

手順やコード

手順としては下記のような流れとなります。
事前準備(poetryプロジェクト初期化) -> Dockerfile作成 -> docker-compose.yaml作成 -> build

事前準備(poetryプロジェクト初期化)

事前準備用のDockerfileとdocker-compose.yaml

Dockerfile
ARG python_image_v="python:3.10-buster"
# python3.10のイメージをダウンロード
FROM ${python_image_v}

ARG work_dir="/src/"

# コンテナにアクセスした際のデフォルトディレクトリ
WORKDIR ${work_dir}

# poetryのインストール先の指定
ENV POETRY_HOME=/opt/poetry
# poetryインストール
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python && \
# シンボリックによるpathへのpoetryコマンドの追加
    cd /usr/local/bin && \
    ln -s /opt/poetry/bin/poetry && \
# 仮想環境を作成しない設定(コンテナ前提のため,仮想環境を作らない)
    poetry config virtualenvs.create false
docker-compose.yaml
version: '3'
services:
  test-app:
    build: .
    volumes:
      - ./src:/src
#    ports:
#      - 8000:8000
    tty: true # retain container up

コンテナ上でのpoetryプロジェクト初期化

下記のdocker-composeコマンドでコンテナを作成し、コンテナにアクセスします。

$ docker-compose up -d
$ docker-compose exec test-app bash
root@a6c27148c534:/src#

poetry versionでpoetryがインストールされていることを確認したら、プロジェクトを作成します。
poetry new <project-name>でプロジェクトのディレクトリを作成してくれます。
オプションで--name <dir-name>を入れるとプロジェクトディレクトリ配下に作成されるソースコードを入れるディレクトリの名前を指定できます。
(poetry new --src <project-name> オプションでsrcディレクトリを作成できますが、ディレクトリ構成がややこしくなるので今回はやめておきます。)
対話形式でやりたい場合は、poetry initコマンドで行います。
(pyproject.tomlファイルのみ作成されます。)

poetry new
root@a6c27148c534:/src# poetry new myProject --name myFlask
Created package myflask in myProject
tree
$ tree
myProject
├── README.rst
├── myflask
   └── __init__.py
├── pyproject.toml
└── tests
    ├── __init__.py
    └── test_myflask.py

poetry add <package-name>コマンドでパッケージの追加ができます。
今回は一例として、flaskを入れてみます。

add Flask package
root@a6c27148c534:/src/myProject# poetry add Flask
Skipping virtualenv creation, as specified in config file.
Using version ^2.1.2 for Flask

Updating dependencies
Resolving dependencies... (14.6s)

Writing lock file

Package operations: 14 installs, 0 updates, 0 removals

 Installing markupsafe (2.1.1)
 Installing pyparsing (3.0.8)
 Installing attrs (21.4.0)
 Installing click (8.1.3)
 Installing itsdangerous (2.1.2)
 Installing jinja2 (3.1.2)
 Installing more-itertools (8.13.0)
 Installing packaging (21.3)
 Installing pluggy (0.13.1)
 Installing py (1.11.0)
 Installing wcwidth (0.2.5)
 Installing werkzeug (2.1.2)
 Installing flask (2.1.2)
 Installing pytest (5.4.3)

pyproject.tomlファイル内にFlaskが追加されます。

pyproject.toml
root@a6c27148c534:/src/myProject# cat pyproject.toml
[tool.poetry]
name = "myFlask"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"
Flask = "^2.1.2"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

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

Dockerfile, docker-compose.yamlの作成

Dockerfile
ARG python_image_v="python:3.10-buster"
# python3.10のイメージをダウンロード
FROM ${python_image_v}

ARG work_dir="/work/"
#ARG project_name="myProject"
#ARG src_dir="myflask"
RUN mkdir /work && mkdir /work/src && mkdir /work/tests

# コンテナにアクセスした際のデフォルトディレクトリ
WORKDIR ${work_dir}

# poetryのインストール先の指定
ENV POETRY_HOME=/opt/poetry
# poetryインストール
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python && \
# シンボリックによるpathへのpoetryコマンドの追加
    cd /usr/local/bin && \
    ln -s /opt/poetry/bin/poetry && \
# 仮想環境を作成しない設定(コンテナ前提のため,仮想環境を作らない)
    poetry config virtualenvs.create false
# 仮想環境をprojectディレクトリで作成する
#RUN poetry config virtualenvs.in-project true

# poetryの定義ファイルをコピー (./src/配下に存在すると想定)
COPY ./src/myProject/pyproject.toml* ./src/myProject/poetry.lock* ./

# poetryでライブラリをインストール (pyproject.tomlが既にある場合)
 RUN if [ -f pyproject.toml ]; then poetry install; else poetry new ${project_name} --name ${src_dir}; fi
docker-compose.yaml
version: '3'
services:
  test-app:
    build: .
    volumes:
      - ./src/myProject/myflask:/work/src
      - ./src/myProject/tests:/work/tests
#    ports:
#      - 8000:8000
    tty: true # retain container up

buildとflaskの確認、パッケージの追加

下記のコマンドでコンテナを起動します。

build
$ docker-compose up -d

コンテナにアクセスし、対話形式でimport flaskを実施するとインポートできます。

flaskの確認
$ docker-compose exec test-app bash
root@ab019553223e:/work# python
Python 3.10.4 (main, Apr 20 2022, 18:32:02) [GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import flask
>>>

パッケージを追加または削除する場合は下記のコマンドpoetry add(remove) <package-name>で実施可能です。
(今回はpyproject.tomlファイルがあるディレクトリで行いましたが、違うディレクトリでも可能?仮想環境を作っていないため?仮想環境を作っている場合はそのディレクトリでないとダメ?試していないため未確認です。)

add(remove) package
root@ab019553223e:/work# ls
poetry.lock  pyproject.toml  src  tests
root@ab019553223e:/work# cat pyproject.toml
[tool.poetry]
name = "myFlask"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"
Flask = "^2.1.2"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

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

root@ab019553223e:/work# poetry add selenium
Skipping virtualenv creation, as specified in config file.
Using version ^4.1.5 for selenium

Updating dependencies
Resolving dependencies... (0.3s)

Writing lock file

Package operations: 17 installs, 0 updates, 0 removals

 Installing pycparser (2.21)
 Installing cffi (1.15.0)
 Installing async-generator (1.10)
 Installing cryptography (37.0.2)
 Installing h11 (0.13.0)
 Installing idna (3.3)
 Installing outcome (1.1.0)
 Installing sniffio (1.2.0)
 Installing sortedcontainers (2.4.0)
 Installing certifi (2021.10.8)
 Installing pyopenssl (22.0.0)
 Installing pysocks (1.7.1)
 Installing trio (0.20.0)
 Installing wsproto (1.1.0)
 Installing trio-websocket (0.9.2)
 Installing urllib3 (1.26.9)
 Installing selenium (4.1.5)
  
root@ab019553223e:/work# poetry remove selenium
Skipping virtualenv creation, as specified in config file.
Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Package operations: 0 installs, 0 updates, 17 removals

 Removing async-generator (1.10)
 Removing certifi (2021.10.8)
 Removing cffi (1.15.0)
 Removing cryptography (37.0.2)
 Removing h11 (0.13.0)
 Removing idna (3.3)
 Removing outcome (1.1.0)
 Removing pycparser (2.21)
 Removing pyopenssl (22.0.0)
 Removing pysocks (1.7.1)
 Removing selenium (4.1.5)
 Removing sniffio (1.2.0)
 Removing sortedcontainers (2.4.0)
 Removing trio (0.20.0)
 Removing trio-websocket (0.9.2)
 Removing urllib3 (1.26.9)
 Removing wsproto (1.1.0)

コンテナを停止する場合は下記のコマンドでできます。オプションで--rmi allを付けるとdocker-composeコマンドで作成されたコンテナとイメージなどを削除してくれます。

down
$ docker-compose down (--rmi all)

その他

poetryでのversion指定

poetryでパッケージをインストールする際にバージョン指定が可能ですが、今回は指定していません。
詳しくはpoetryドキュメントを見てください。

docker内でのpoetry操作の自動化について

今回はpoetryによるpoetry installコマンドをpyproject.tomlがある前提でDockerfileやdocker-compose.yamlなどを書いた。しかし、poetry new or initなどの操作も自動化したい。他の記事もpyproject.tomlを前提に書いているのが多いため、現在お勉強中です。
下記のサイトが参考になるかも。
Poetry と Docker を併用する試み

docker-composeでのvolumeのタイミング

RUN -> volume -> CMDらしい?
なので、マウントするvolume上で何かしらのコマンドを実行したい場合は、CMDの方が良い?

(# 最後に)

参考

Discussion