🪄

Docker+Poetry+GitでPythonの開発環境を構築しよう

2024/12/08に公開

この記事は team411アドベントカレンダー Advent Calendar 2024 8日目の記事です。

昨日の記事はかずたつさんの「【factorio】ブループリント解析してみた」でした。一見ランダムな文字列にしか見えないブループリントを上手くデコードすると、JSON文字列として解析することができるというのは驚きですね。

はじめに

皆さん、Docker は使っていますか?

Docker とは、コンテナと呼ばれる仮想環境を利用することで簡単に環境構築ができるプラットフォームです。OS 等のローカル環境の違いを気にせず誰でも同じ環境を作れるだけでなく、作成した環境の配布や再利用もできる優れものです。

この記事では、そんな Docker を使って、Poetry という Python 向けパッケージマネージャーを利用した Python の開発環境を構築する方法を紹介します。また、作成した環境を Git で管理したいときに役立つテクニックも紹介します。

注意

本記事では環境構築の具体的な手順をメインに扱い、Docker の詳しい説明や導入方法については割愛します。気になる方は以下の参考記事を読んでみてください。

参考記事

また、本記事を書くにあたり使用した筆者のPC環境は以下の通りです。

  • OS: Windows 11 Home
  • Docker Desktop: v4.35.1 (173168)
  • Windows PowerShell: v5.1.22621.4391
  • Git: v2.44.0.windows.1

1. Docker の設定ファイルを作ろう

では早速、環境構築について説明します。

まずは、自分のローカルPCの適当な場所に開発用のディレクトリを作りましょう。そして、Docker を使うために必要な設定ファイルをそのディレクトリの直下に配置します。

docker_poetry_test/
   ├─ Dockerfile
   └─ docker-compose.yml

必要なファイルは次の2つです。

ファイル名 説明
Dockerfile Dockerイメージの作成手順を指定するファイル
docker-compose.yml 作成するコンテナの初期状態を設定するファイル

Dockerfile

Dockerfile は、Docker コンテナを作るのに必要な Docker イメージの作成手順を書くファイルです。

Dockerfile
# PythonのDockerイメージをダウンロード
FROM python:3.12

# Pythonの出力表示をDocker用に設定
ENV PYTHONUNBUFFERED=1

# 作業ディレクトリを指定
WORKDIR /src

# pipを使ってpoetryをインストール
RUN pip install poetry

# poetryの設定ファイルが存在するなら、コピーしてパッケージをインストール
COPY pyproject.toml* poetry.lock* ./
RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi

(1) 既存の Docker イメージの利用

# PythonのDockerイメージをダウンロード
FROM python:3.12

「はじめに」でDocker は作成した環境を配布することができると言いましたが、ここでは Docker 公式が配布している、Python環境のプリセットを用意してくれる Docker イメージ (https://hub.docker.com/_/python) を FROM ~ でダウンロードしています。既に他の人が作ってくれている Docker イメージを利用すれば、追加の設定やカスタマイズをするだけで済み、環境構築がさらに簡単になります。

python:3.12 のように : に続けてバージョンを書くことで、Python の使いたいバージョンを指定することができます。数字だけのバージョンの他に、3.13-bullseye3.13-bookworm など細かい指定もできます。詳しく知りたい方は以下の記事を読んでみましょう。

https://zenn.dev/ken3pei/articles/1abbf7d974cf5d

(2) 環境変数の設定

# pythonの出力表示をDocker用に設定
ENV PYTHONUNBUFFERED=1

ENV ~ で仮想環境内で使う環境変数を設定することができます。ここでは PYTHONUNBUFFERED という Python の標準出力を Docker 用に調整する機能をオンにしています。

(3) 作業ディレクトリの設定

# 作業ディレクトリを指定
WORKDIR /src

仮想環境内では Linux が動いていて、ディレクトリ構造も Linux 用のものになっています。WORKDIR [ディレクトリパス] で、仮想環境内のどのディレクトリに環境を構築するか指定できます。

(4) Poetry のインストール

# pipを使ってpoetryをインストール
RUN pip install poetry

RUN ~ で Linux コマンドを実行して、Docker イメージに反映させることができます。

(1) で Python 環境のプリセットをダウンロードしたので、初めから pip を使うことができます。ここでは pip install poetry で Poetry をインストールしています。

(5) Poetry 設定ファイルのコピー・パッケージのインストール

# poetryの設定ファイルが存在するなら、コピーしてパッケージをインストール
COPY pyproject.toml* poetry.lock* ./
RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi

COPY [コピー元] [コピー先] で、ローカルにあるファイルを仮想環境内にコピーできます。ここでは Poetry を使うのに必要な設定ファイル pyproject.tomlpoetry.lock が存在する場合、それらを作業ディレクトリ (./ = /src) にコピーしています。
なお、コピー元のファイル名の末尾に * (ワイルドカード) をつけないと、コピー元のファイルが存在しない場合にエラーが出てしまうので注意しましょう。

次に、if [ -f pyproject.toml ];pyproject.toml が存在するか判定し、存在する場合は poetry install --no-root で Poetry を用いて各種パッケージをインストールしています。
このコードは初めて環境構築するときには実行されませんが、環境構築が済んだ後、その環境を配布・再利用したいときに必要となります。

docker-compose.yml

docker-compose.yml は、複数のコンテナを使ったアプリケーションを扱える Docker Compose というツールの設定ファイルです。このファイルに各コンテナの初期状態を YAML 形式で指定することで、Docker Compose を用いて Docker イメージのビルドやコンテナの起動・停止がより簡潔におこなえるようになります。

コンテナが1つだけの場合でも Docker Compose は便利なので、今回は Dockerfile と一緒に docker-compose.yml も用意します。

docker-compose.yml
services:
  demo-app:
    build: ./
    volumes:
      - ./:/src
    tty: true

services という階層の下に、使用するサービス (コンテナ) の名前 (demo-app) を指定します。さらにその下の階層に、コンテナの初期設定を書きます。

  • build: Docker Compose が参照する Dockerfile の場所を指定します。
  • volumes: ローカルからコンテナ内に同期(マウント)させたいファイルを [マウント元]:[マウント先] で指定します。ここでは、ローカルの docker-compose.yml があるディレクトリ (./ = docker_poetry_test) の中身を丸ごとコンテナ内の /src ディレクトリ内にマウントしています。
    マウントを使えばコンテナ内でローカルのファイルを利用でき、またコンテナ内でファイルに変更があれば、それをローカルのファイルに反映させることができます。
  • tty: true: 実行中のプロセスがない場合にもコンテナを起動したままにしておくときに設定します。

2. Docker イメージをビルドしよう

Dockerの設定ファイルができたので、次は Docker イメージをビルドしてみましょう。

Docker Desktop が起動していることを確認したら (忘れないで!) 、以下のコマンドを実行するだけで Docker Compose が設定ファイルを読み込んで Docker イメージを作ってくれます。ビルドには少し時間がかかるので待ちましょう。

docker compose build

実行すると、以下のようなログが表示されるはずです。(環境によって一部の内容が変わることがあります)
しばらくして再びプロンプトが表示されたらビルドは完了です。

PS C:\...\docker_poetry_test> docker compose build
[+] Building 0.0s (0/1)                                             docker:desktop-linux
[+] Building 14.2s (11/11) FINISHED                                 docker:desktop-linux
 => [demo-app internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 496B                                                0.0s
 => [demo-app internal] load metadata for docker.io/library/python:3.12             1.7s
 => [demo-app internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                                     0.0s
 => [demo-app 1/5] FROM docker.io/library/python:3.12@sha256:2fc3687451585d73c0662  0.0s
 => [demo-app internal] load build context                                          0.0s
 => => transferring context: 2B                                                     0.0s
 => CACHED [demo-app 2/5] WORKDIR /src                                              0.0s
 => [demo-app 3/5] RUN pip install poetry                                          11.6s
 => [demo-app 4/5] COPY pyproject.toml* poetry.lock* ./                             0.0s
 => [demo-app 5/5] RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi  0.3s
 => [demo-app] exporting to image                                                   0.5s
 => => exporting layers                                                             0.4s
 => => writing image sha256:075e3b27ea5c51878c46bcf644c68bb6da4a44dd1b81e1e64d541d  0.0s 
 => => naming to docker.io/library/docker_poetry_test-demo-app                      0.0s 
 => [demo-app] resolving provenance for metadata file
PS C:\...\docker_poetry_test> 

なお、一度 Docker イメージをビルドした後に Dockerfile などを変更した場合は、再度ビルドをおこなう必要があります。その場合は、以下のコマンドを実行してください。--no-cache オプションを外すと、Docker がキャッシュを参照してしまい変更が反映されないことがあります。

docker compose build --no-cache

3. Docker コンテナを起動しよう

Docker イメージをビルドできたので、いよいよ Docker コンテナを起動してみましょう。

コンテナの起動も簡単におこなえます。以下のコマンドを実行しましょう。

docker compose up

実行すると以下のようなログが表示されます。

PS C:\...\docker_poetry_test> docker compose up  
[+] Running 2/0
 ✔ Network docker_poetry_test_default       Created                               0.0s 
 ✔ Container docker_poetry_test-demo-app-1  Created                               0.0s 
Attaching to demo-app-1
demo-app-1  | Python 3.12.8 (main, Dec  4 2024, 20:39:47) [GCC 12.2.0] on linux
demo-app-1  | Type "help", "copyright", "credits" or "license" for more information.

この状態でログが止まっていれば成功です。

コンテナを停止させるには、Ctrl+C キーを押します。以下のようなログが表示され、少し待つと停止が完了します。

Gracefully stopping... (press Ctrl+C again to force)
[+] Stopping 1/1
 ✔ Container docker_poetry_test-demo-app-1  Stopped                              10.3s 
canceled
PS C:\...\docker_poetry_test> 

起動したコンテナへのアクセス

次に、起動したコンテナにアクセスしてみましょう。

今度は docker compose up -d を実行すると、コンテナがバックグラウンドで起動します。
(docker compose up を実行した後に新しいターミナルを開いても構いません)

PS C:\...\docker_poetry_test> docker compose up -d
[+] Running 1/1
 ✔ Container docker_poetry_test-demo-app-1  Started                               0.3s 
PS C:\...\docker_poetry_test> 

プロンプトが表示されたら、以下のコマンドを実行しましょう。すると、コンテナ内で Linux のターミナル (bash) を使うことができます。

docker compose exec demo-app bash

試しにこのターミナル上で色んなコマンドを実行してみましょう。もちろん Python を使うこともできます。ターミナルを終了するには exit を実行します。

PS C:\...\docker_poetry_test> docker compose exec demo-app bash
root@260fa8165c60:/src# pwd
/src
root@260fa8165c60:/src# ls -a
.  ..  .gitignore  Dockerfile  docker-compose.yml
root@260fa8165c60:/src# python
Python 3.12.8 (main, Dec  4 2024, 20:39:47) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 3 * 137
411
>>> exit()
root@260fa8165c60:/src# exit
exit
PS C:\...\docker_poetry_test> 

なお、docker compose exec demo-app ~ の末尾 ~ を変えれば、代わりにそのコマンドを直接実行することもできます。

PS C:\...\docker_poetry_test> docker compose exec demo-app python 
Python 3.12.8 (main, Dec  4 2024, 20:39:47) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

バックグラウンドでコンテナを起動した場合、そのコンテナを停止するには以下のコマンドを実行します。

docker compose stop

Ctrl+C キーで停止したときと同様に、少し待ってからコンテナが停止します。

PS C:\...\docker_poetry_test> docker compose stop
[+] Stopping 1/1
 ✔ Container docker_poetry_test-demo-app-1  Stopped                              10.4s 
PS C:\...\docker_poetry_test> 

4. Poetry を使って開発環境を構築しよう

1~3章では Docker の使い方について一通り説明しました。それでは、Docker コンテナ上で Poetry を使った Python の開発環境を作っていきましょう。

Poetry 環境のセットアップ

まずは docker compose up でコンテナを起動して、docker compose exec demo-app bash でコンテナ内のターミナルにアクセスします。

次に、コンテナ内のターミナルで以下のコマンドを実行します。

poetry init --name=demo-app -n

実行すると、Python プロジェクトの管理に使われる設定ファイルである pyproject.toml を Poetry が自動で作ってくれます。作成された pyproject.toml の中身はこのようになっています。

pyproject.toml
[tool.poetry]
name = "demo-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

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


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
実行ログ
PS C:\...\docker_poetry_test> docker compose up -d
[+] Running 1/1
 ✔ Container docker_poetry_test-demo-app-1  Started                               0.4s 
PS C:\...\docker_poetry_test> docker compose exec demo-app bash
root@260fa8165c60:/src# poetry init --name=demo-app -n
root@260fa8165c60:/src# 

ちなみに、-n (= --no-interaction) オプションを外すと Poetry が対話形式でバージョンなどの詳細情報を訊いてきます。設定を変えたい部分は都度入力して、デフォルトの設定でよい場合は何も入力せず Enter キーを押しましょう。(Author のみ n を入力する必要があります)

Poetry を使ったパッケージの追加・利用・削除

pyproject.toml ができたら、次は Poetry で好きな Python パッケージをインストールしましょう。ここでは python-ulid というID生成パッケージをインストールすることにします。

パッケージを追加するには、コンテナ内のターミナルで次のコマンドを実行します。
他のパッケージを追加したい場合は、python-ulid の部分をそのパッケージ名に書き換えましょう。

poetry add python-ulid
実行ログ
root@260fa8165c60:/src# poetry add python-ulid
Creating virtualenv demo-app-VsnhxLU2-py3.12 in /root/.cache/pypoetry/virtualenvs
Using version ^3.0.0 for python-ulid

Updating dependencies
Resolving dependencies... (0.2s)

Package operations: 1 install, 0 updates, 0 removals

  - Installing python-ulid (3.0.0)

Writing lock file
root@260fa8165c60:/src# 

すると、poetry.lock というパッケージのバージョンや依存関係を管理するための Poetry 用設定ファイルが自動的に作られます。また、追加したパッケージの情報が pyproject.toml に書き込まれます。

pyproject.toml (diff)
[tool.poetry]
name = "demo-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
+ python-ulid = "^3.0.0"


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

これでパッケージを追加できたので、パッケージが使えるか試してみましょう。

Poetry で追加したパッケージは、Poetry を経由することで使うことができます。
コンテナ内のターミナルで以下のコマンドを実行すると、Poetry を経由して Python インタプリタを起動します。

poetry run python

Python インタプリタを起動できたら、from ulid import ULID でパッケージをインポートして、ULID() でインポートした関数を呼び出します。すると、以下のようにインポートした関数を使うことができました。

root@260fa8165c60:/src# poetry run python
Python 3.12.8 (main, Dec  4 2024, 20:39:47) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from ulid import ULID
>>> ULID()
ULID(01JEGB536PXNGE1TP659V5J1D4)
>>> exit()
root@260fa8165c60:/src# 

このように、Poetry を使ってパッケージをインストールすると、poetry run ~ でインストールしたパッケージを使った処理をおこなえるようになります。


ついでにパッケージの削除についても触れておきます。

追加したパッケージを削除するには以下のコマンドを実行します。こちらも他のパッケージを削除したい場合は、python-ulid の部分をそのパッケージ名に書き換えてください。

poetry remove python-ulid
実行ログ
root@260fa8165c60:/src# poetry remove python-ulid
Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 0 installs, 0 updates, 1 removal

  - Removing python-ulid (3.0.0)

Writing lock file
root@260fa8165c60:/src# 

以上で Docker + Poetry を使った Python 環境構築は完了です!!!
自分の好きなパッケージを追加して便利に使いましょう。

最後に、この時点でのローカルのディレクトリ構造を載せておきます。

docker_poetry_test/
   ├─ Dockerfile
   ├─ docker-compose.yml
   ├─ poetry.lock
   └─ pyproject.toml

5. Git で開発環境を管理しよう

せっかく環境構築をしたので、作成した環境を Git を使ってリポジトリとして管理してみましょう。なお、この章では Docker コンテナを起動する必要はありません。

まずは (ローカルのターミナル上で) 以下のコマンドを実行すると、ディレクトリが新しい Git リポジトリとしてセットアップされます。

git init

この時点で開発用ディレクトリを確認すると、Git リポジトリの管理に使われる .git/ ディレクトリが追加されています。(VSCode ではデフォルトで非表示になっています)

docker_poetry_test/
   ├─ .git/
   │     └─ ...
   ├─ Dockerfile
   ├─ docker-compose.yml
   ├─ poetry.lock
   └─ pyproject.toml

次に、Git で管理しないファイルを指定するための .gitignore というファイルを作ります。

Python を使った開発の途中では、__pycache__ などの Git で管理する必要のない (管理しようとすると余計な変更履歴が生まれてしまう) ディレクトリやファイルが生成されることがあります。.gitignore にそれらを加えることで、必要なディレクトリやファイルだけを適切に管理することができます。

といっても、どのファイルを管理するべきで、どのファイルを管理するべきでないかを、1から考えるのは難しいですよね。そこで、gitignore.io というそれぞれの開発環境に合った .gitignore を自動生成してくれるサイトを使ってみましょう。

前述のリンク先に飛んだら、中央の検索窓に python と入力して候補から「Python」を選択し、右側の「作成する」ボタンを押します。すると、Python の開発環境向けの .gitignore がテキスト形式で表示されます。(https://www.toptal.com/developers/gitignore/api/python)
開発用ディレクトリに空の .gitignore を作って、これを丸ごとコピー&ペーストしましょう。

.gitignore (一部)
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

...(中略)

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/python

最後に、ここまでの変更をまとめて Git に登録します。以下のコマンドを続けて実行して、変更内容をコミットしましょう。

git add .
git commit -m "good first commit"

これで、作成した開発環境を Git で管理できるようになりました!
最終的な開発用ディレクトリの構成も載せておきます。

docker_poetry_test/
   ├─ .git/
   │     └─ ...
   ├─ .gitignore
   ├─ Dockerfile
   ├─ docker-compose.yml
   ├─ poetry.lock
   └─ pyproject.toml

おわりに

以上、Docker + Poetry で Python の開発環境を構築し、Git で開発環境を管理する方法について紹介しました。Docker を使うことでローカル環境よりも自由で便利な開発環境を構築することができ、開発の幅が広がると思います。皆さんも是非 Docker を使った環境構築にチャレンジしてみましょう。

この記事で作成した開発環境を GitHub にアップロードしたので、興味のある方は以下のリンクからダウンロードして使ってみてください。使う際は docker compose build してから docker compose up しましょう。パッケージを追加する場合は、poetry init --name=demo-app -n を飛ばして poetry add ~ を実行してください。

https://github.com/luuguas/docker_poetry_test

なお、この記事の続編として、FastAPI の導入についても記事を書く予定です。


明日の記事は Kanaru さんの「【Web標準】CSSとCanvasの狭間としてのSVG」です。SVG は PNG や JPEG のような画像形式の1つですが、PNG や JPEG とは違った魅力があるようです。

参考記事

本記事を書くにあたり、本文に載せたもの以外に追加で参考にした記事やサイトです。こちらも気になる方は読んでみてください。

https://qiita.com/gon0821/items/77369def082745d19c38

https://qiita.com/yuta-ushijima/items/d3d98177e1b28f736f04

https://qiita.com/tegnike/items/bcdcee0320e11a928d46

https://zenn.dev/sh0nk/books/537bb028709ab9

Discussion