Open4

Rye を Docker で使う

ピン留めされたアイテム
codehexcodehex

翻訳
https://github.com/mitsuhiko/rye/discussions/239#discussioncomment-6033855

マルチステージビルドのためのDockerfileを書いてみました。以下に一例を示します:

ARG PYTHON_BASE_IMAGE='python'

FROM ${PYTHON_BASE_IMAGE}:3.11 AS rye

# Pythonがpycファイルを書き込まないようにします。
ENV PYTHONDONTWRITEBYTECODE=1

# Pythonがstdoutとstderrをバッファリングしないようにします。これにより、
# バッファリングによりログが発生せずにアプリケーションがクラッシュする状況を避けることができます。
ENV PYTHONUNBUFFERED=1

ENV PYTHONPATH="/workspace/src:$PYTHONPATH"

# 仮想環境はryeが実行されるワーキングディレクトリで作成されるため、
# 開発環境と本番環境はそれぞれ同一のディレクトリにする必要があります。
WORKDIR /workspace

RUN \
  --mount=type=cache,target=/var/lib/apt/lists \
  --mount=type=cache,target=/var/cache/apt/archives \
  apt-get update \
  && apt-get install -y --no-install-recommends build-essential

ENV RYE_HOME="/opt/rye"
ENV PATH="$RYE_HOME/shims:$PATH"

# RYE_INSTALL_OPTIONはビルドに必要です。
# 参照: https://github.com/mitsuhiko/rye/issues/246
RUN curl -sSf https://rye-up.com/get | RYE_NO_AUTO_INSTALL=1 RYE_INSTALL_OPTION="--yes" bash

# Dockerのキャッシュを利用するために、依存関係のダウンロードを別のステップとして行います。
# バインドマウントを利用して一部のファイルをこのレイヤーにコピーすることを避けます。
RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=bind,source=requirements.lock,target=requirements.lock \
    --mount=type=bind,source=requirements-dev.lock,target=requirements-dev.lock \
    --mount=type=bind,source=.python-version,target=.python-version \
    --mount=type=bind,source=README.md,target=README.md \
    rye sync --no-dev --no-lock

RUN . .venv/bin/activate

# 開発のためのステージ。
# 開発環境はdevcontainerを想定し、環境はコンテナ内部に閉じ込められているため、
# 仮想環境を意識する必要はありません。
FROM rye AS dev


RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=bind,source=requirements.lock,target=requirements.lock \
    --mount=type=bind,source=requirements-dev.lock,target=requirements-dev.lock \
    --mount=type=bind,source=.python-version,target=.python-version

 \
    --mount=type=bind,source=README.md,target=README.md \
    rye sync --no-lock

# 本番用のステージ
FROM rye AS run

# アプリケーションが実行する非特権ユーザーを作成します。
# 参照: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser

# アプリケーションを実行するために非特権ユーザーに切り替えます。
USER appuser

COPY . .

ENTRYPOINT ["python3", "./src/main.py"]

想定しているディレクトリ構造は以下のようなものです。(これはかなり単純化したバージョンです。)

.
├── .devcontainer
│   ├── devcontainer.json
│   ├── docker-compose.yml
│   └── postCreateCommand.sh
├── src      # アプリケーションのソースコードディレクトリ
├── tests   # テストコードディレクトリ
├── Dockerfile
└── docker-compose.yml
プロジェクトルートのdocker-compose.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: run # マルチステージビルドターゲットを指定する
    ports:
      - 8080:8080
devcontainer.json
{
  "name": "rye",
  "dockerComposeFile": [
    "../docker-compose.yml",
    "docker-compose.yml"
  ],
  "service": "app",
  "workspaceFolder": "/workspace",
  "postCreateCommand": "bash .devcontainer/postCreateCommand.sh",
  "userEnvProbe": "loginInteractiveShell",
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "charliermarsh.ruff",
        "bungcip.better-toml",
        "ms-azuretools.vscode-docker"
      ]
    }
  }
}
.devcontainerディレクトリ内のdocker-compose.yml
version: "3.9"
services:
  app:
    container_name: app-dev
    build:
      args:
       - PYTHON_BASE_IMAGE=mcr.microsoft.com/vscode/devcontainers/python
      target: ${TARGET_STAGE:-dev} # devステージを使用するためにオーバーライド
    command: sleep infinity
    volumes:
      - .:/workspace:cached
codehexcodehex

考えてること

  • Pythonの仮想環境は必要ないと思っている。なぜなら、コンテナ内部に依存関係とアプリケーションコードを閉じ込めるから。
  • curl https://rye-up.com/get の行を実行するたびに、cpython@3.10がインストールされるが、これも必要ないと思っている。.python-versionのものをインストールしたい。(書き方に何か問題があるかも)