はじめに
前回は「Visual Studio Code」と「Dev Containers」を使って、簡単にコンテナ上に開発環境を構築する方法を紹介しました。
今回は、Dev Containerを活用せずに、「Dockerfile」を書いて直接コンテナを構築する方法を解説します。コンテナを立ち上げる方法にはいくつかありますが、一時的に利用する場合以外ではDockerfileを書いてコード化することが一般的です。
Dockerfileとは
Dockerfileとは、独自のDockerイメージを作成するための設定ファイルです。次の例のようにイメージを指定(FROM)したり、コンテナ内で実行するコマンド(RUN)を定義したりできます。これにより、ライブラリやツールのインストールからアプリケーションの実行まで、一連の作業を自動化できます。
Dockerfile
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
Dockerfileの書き方(基本編)
まずはイメージを指定します。イメージを取得する先のコンテナレジストリはDocker Hubを使用することが一般的ですが、最近では「GitHub Container Registry」や「AWS ECR」なども使われています。
今回はDocker Hubを使います。Docker Hubにアクセスして検索バーにpythonと入力すると、Pythonの公式イメージが表示されます。ここでは、最新のPython 3.9を使うことにします。
- Dockerfileを作成します。
mkdir dockerfile-tutorial cd dockerfile-tutorial touch Dockerfile - 作成したDockerfileに、次の内容を記述します。
Dockerfile FROM python:3.9 - ファイルが作成できたら、次に
docker buildコマンドでイメージをビルドします。docker build -t my-python-app .-tオプションはイメージにタグを付けるためのものです。my-python-appという名前のイメージを作成します。 - イメージのビルドが完了したら、コンテナを起動します。
docker run -it --rm my-python-app bash-it(-i -tの省略形)オプションはコンテナを対話的に起動するためのものです。--rmオプションはコンテナを終了したら削除するためのものです。 - コンテナが起動したら、環境が起動します。 Pythonの環境が起動していることを確認できます。
Dockerfileの書き方(応用編)
もう少し複雑なDockerfileを作成してみましょう。今回は、PythonのWebフレームワークであるFastAPIを使ってAPIサーバーを立ち上げるDockerfileを作成します。FastAPIの公式サイトに例があるので、今回はこれを使用します。
なお、ファイルの中で「Poetry」というパッケージマネージャを使っていますが、ここでは本ツールの詳しい使い方は説明しません。詳しくは公式サイトを参照してください。
FROM python:3.9 as requirements-stage
WORKDIR /tmp
RUN pip install poetry
COPY ./pyproject.toml ./poetry.lock* /tmp/
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
FROM python:3.9
WORKDIR /code
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
Dockerfileを書くときには様々なテクニックが活用されますが、ここでは2つのテクニックを使っています。
マルチステージビルド
マルチステージビルドは、イメージにレイヤーを追加してイメージサイズを小さくするためのテクニックです。先ほど紹介したサンプルコード上にrequirements-stageという文字がありますが、これがマルチステージビルドの記述です。前のステージで生成された必要な成果物のみを次のステージにコピーすることで、不要なファイルを含めないようにします。
FROM python:3.9 AS requirements-stage
# 1つ目のレイヤーの処理
FROM python:3.9
# 2つ目のレイヤーの処理
Dockerキャッシュの活用
Dockerはビルド時にキャッシュを使って高速化を図ります。COPYやRUNなどの命令が変更されない限りキャッシュが使われます。
例えば、requirements.txtが変更されない限り、pip installの処理はキャッシュされます。キャッシュの世界は、ひと言では語り尽くせないほど奥が深いので、興味がある方は実際に試したり、調べたりしてみてください。
アプリケーションを動かしてみよう
Dockerfileを書いたら、次にアプリケーションを動かしてみましょう。公式サイトのドキュメントに簡単なアプリケーションの例があるので、それを使ってアプリケーションを動かしてみます。
.
├── app
│ ├── __init__.py
│ └── main.py
├── Dockerfile
└── pyproject.toml
-
app/__init__.pyを作成します。このファイルには何も書かなくても大丈夫です。 -
app/main.pyを作成します。各コードの詳しい説明は公式サイトのドキュメントを参照してください。from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} -
pyproject.tomlを作成します。このファイルはPoetryのコマンドを実行することで生成できます。今回は、次の内容をコピーして作成します。[tool.poetry] name = "dockerfile-tutorial" version = "0.1.0" description = "" authors = ["Your Name"] readme = "README.md" [tool.poetry.dependencies] python = "^3.9" fastapi = "^0.110.1" uvicorn = {extras = ["standard"], version = "^0.29.0"} [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" app/__init__.pyとapp/main.py、pyproject.tomlの3つのファイルが作成できたら準備完了です。 - Dockerfileをビルドしてコンテナを起動します。ビルドコマンドは初めに実行したコマンドと同じですが、次のコマンドを実行します。
docker build -t my-python-app . - ビルドが完了したら、次にコンテナを起動します。
docker run -it --rm -p 80:80 my-python-app-pオプションはホストとコンテナのポートをマッピングするためのものです。今回はホストの80番ポートをコンテナの80番ポートにマッピングしています。ホスト側ポートを8080番に変更したい場合は、-p 8080:80と指定します。
無事に起動できたかどうかを確認してみましょう。FastAPIには標準でSwagger UIが付属しているので、ブラウザでhttp://localhost/docsにアクセスしてみてください。次のような画面が表示されれば成功です。
おわりに
Dockerfileを書くことで、コンテナ環境の構築を自動化できます。また、Dockerfileを使うことでコードとして管理できるため、チーム開発での共有やバージョン管理がしやすくなります。
なお、Dockerfileの書き方は様々ありますが、基本的な書き方を覚えておくと、コンテナの環境構築がスムーズになります。Dockerfileを使ったコンテナ開発に慣れてきたら、次のステップとしてイメージサイズやキャッシュ活用などにチャレンジしてみてください。