🎉

VSCode Dev ContainersでPython環境を構築する際のベストプラクティス

2023/12/07に公開

はじめに

VSCodeのDev Containers機能を用いて、Pythonプロジェクトの開発環境を構築しました。
そこで得られた知見を共有します。

実現する構成

  • Python
  • Poetry
  • セッションストア(Redis)
  • データベース(Microsoft SQL Server)

完成形

devcontainer.json

{
  "name": "devcontainer-python",
  "dockerComposeFile": ["docker-compose.yml"],
  "service": "server",
  "runServices": ["db", "server"],
  "mounts": [
    {
      "type": "volume",
      "source": "poetry-cache",
      "target": "/home/vscode/.cache/pypoetry"
    },
    {
      "type": "volume",
      "source": "venv-cache",
      "target": "${containerWorkspaceFolder}/.venv"
    }
  ],
  "workspaceFolder": "/workspace",
  "features": {
    "ghcr.io/jlaundry/devcontainer-features/mssql-odbc-driver": {
      "version": 18
    },
    "ghcr.io/itsmechlark/features/redis-server": {
      "version": "latest"
    },
    "ghcr.io/devcontainers-contrib/features/poetry": {
      "version": "latest"
    }
  },
  "postAttachCommand": "sudo chown -R vscode /home/vscode/.cache/pypoetry ${containerWorkspaceFolder}/.venv && poetry config virtualenvs.in-project true && poetry install",
  "remoteUser": "vscode",
  "customizations": {
    "vscode": {
      "settings": {
        "python.defaultInterpreterPath": "/workspace/.venv/bin/python"
      },
      "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "streetsidesoftware.code-spell-checker",
        "charliermarsh.ruff",
        "tamasfe.even-better-toml",
        "ms-azuretools.vscode-docker"
      ]
    }
  }
}

docker-compose.yml

version: "3"

services:
  server:
    hostname: server
    image: "mcr.microsoft.com/devcontainers/python:3.11-bullseye"
    command: /bin/sh -c "while sleep 1000; do :; done"
    user: vscode
    ports:
      - "8000:8000"
    volumes:
      - ..:/workspace:cached

  db:
    hostname: db
    image: "mcr.microsoft.com/mssql/server:2022-latest"
    env_file:
      - ../.env
    expose:
      - "1433"
    ports:
      - "1433:1433"

Dev Containersのfeaturesを極力使う

Dockerfileをこねこねせずとも、以下のことはDev ContainersのFeaturesを使えばできてしまいます。

  • データベースドライバ(SQLServerならODBC)のインストール
  • Redisサーバーの用意
  • Poetryのインストール

利用可能なfeaturesは以下のリポジトリから探すとよいです。

ライブラリはリポジトリ直下の.venvにいれる

「プロジェクトに参加する開発者全員の開発環境を一定に揃える」ために Devcontainer でさらにバージョン固定を行うことは明らかな過剰剛性であり,センスがありません.当たり前ですが,コンテナは多くのリソースを使用します.特にこの過剰剛性によってイメージサイズが大きくなることは避けることができません(中略)
さらに,概してライブラリというのは変更や追加されることなど開発用のツールほど少なくはありません.ライブラリの変更が行われるたびにコンテナのビルドが行われては開発どころではないでしょう.

この記事に則り、Dev Containerにアタッチ後、poetryが.venvにライブラリをインストールするよう設定しました。

.venvはDockerボリュームに退避する

ホストとコンテナで.venvを共有すると、PythonのIntelliSenseが非常に遅くなります。
これは.venvをDockerボリュームに切り出すことで対策できます。

また、docker-compose.ymlを使わずとも、devcontainer.jsonに以下のような設定を加えるだけで実現できます。
ただし、マウントされたボリュームのオーナーはrootになるので、sudo chown -R vscode .venvvscodeユーザーに変更する必要があります。

   "mounts": [
  {
   "type": "volume",
   "target": "${containerWorkspaceFolder}/.venv"
  }
 ],
  "postAttachCommand": "sudo chown -R vscode ${containerWorkspaceFolder}/.venv && poetry config virtualenvs.in-project true && poetry install",

Poetryのキャッシュもボリュームに退避する(オプション)

コンテナをリビルドするとPoetryがキャッシュしていたPythonライブラリのキャッシュも消えてしまいます。
Dev Containerの設定を作成する際は頻繁にコンテナのリビルドを行うと思いますが、その度にライブラリのダウンロードが発生するため、検証のサイクルが遅くなります。

これを回避するには、poetryのキャッシュディレクトリもボリュームに切り出します。

  "mounts": [
    {
      "type": "volume",
      "source": "poetry-cache",
      "target": "/home/vscode/.cache/pypoetry"
    },
    {
      "type": "volume",
      "source": "venv-cache",
      "target": "${containerWorkspaceFolder}/.venv"
    }
  ],
  "postAttachCommand": "sudo chown -R vscode /home/vscode/.cache/pypoetry ${containerWorkspaceFolder}/.venv && poetry config virtualenvs.in-project true && poetry install",

rootユーザーは使わない

保存を行ったコンテナにおいて, VSCode がコンテナの root ユーザーでファイルの保存を行った場合, root として保存され,上書きされたファイルのアクセス権限はホストマシンにも適用されます.

また、ユーザー名はvscodeとすることをおすすめします。

VSCode公式のコンテナイメージを使う

公式が提供するコンテナイメージを使えば、vscodeユーザーの作成などでDockerfileをこねこねする必要はありません。

利用可能なコンテナイメージは以下のリポジトリから探すとよいです。

デフォルトで入ってほしい拡張機能をdevcontainer.jsonにいれる

デフォルトで以下の拡張機能を入れることをおすすめします

  • ms-python.python: 言わずもがなVSCode公式のPythonサポート
  • ms-python.vscode-pylance: MS渾身のPython型チェッカー
  • streetsidesoftware.code-spell-checker: スペルチェックでタイポを防ぐ
  • charliermarsh.ruff: Rustで書かれたちょっぱやのPythonフォーマッター・リンター
  • tamasfe.even-better-toml: pyproject.tomlのシンタックスハイライト

また、devcontainer.json"customizations"."extensions"に記述した拡張はコンテナビルド時に自動で入ります。
一方.vscode/extensions.jsonに記述した拡張は自動では入りませんが、
"extensions.ignoreRecommendations": falseでインストール催促のポップアップを出すことはできます。

環境変数はリポジトリ直下の.envに集約する

環境変数に限らず設定の類はいつも気づけばカオスになります。
The Twelve-Factor Appというアプリケーション開発の11原則にある通り、設定はすべて環境変数で渡せるようにしておくのがよいです。

また.env.gitignoreした上で、example.envに雛形を用意しておくとよいです。(シークレットは書かないこと!)

M1対応のためにDockerfileにplatformを指定する必要はない

platformにx86やarmを指定することでプルするコンテナイメージのアーキテクチャを指定できますが、デフォルトでApple Silicon Macならarmが指定されるので、わざわざ書く必要はありません。

Dev Containers固有の設定以外は.vscode配下に記述する

Dev Containersを使わない場合にも対応できるよう、Dev Container固有の設定以外は.vscode/settings.jsonに記述しましょう。

以下のように、フォーマッターの設定などは.vscode/配下に記述し、逆にPythonのパスなどはdevcontainer.jsonに記述しましょう。

.vscode/settings.json
{
  "cSpell.words": [ // スペルチェッカーの追加辞書
    "devcontainer"
  ],
  "files.exclude": {
    // 余計なファイルを表示しないようにする
    "**/__pycache__": true,
    "**/.mypy_cache": true,
    "**/.pytest_cache": true
  },
  "python.testing.pytestEnabled": true,
  "python.testing.unittestEnabled": false,
  "[python]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.fixAll": true,
      "source.organizeImports": true
    },
    "editor.defaultFormatter": "charliermarsh.ruff"
  },
  "extensions.ignoreRecommendations": false // 推奨のエクステンションを必ず表示する
}

結局は公式ドキュメントが一番頼りになる

公式は正義

参考

以下の記事はとても参考になりました!ありがとうございます!

Discussion