⚙️

Devcontainer.jsonの基本的な書き方と設計について

に公開
5

はじめに

前回の記事では、Dev Containersが解決できる課題と基本的な概念について説明しました。今回は実際にdevcontainer.jsonを利用して、Dev Container環境を構築する方法について詳しく解説していきます。書き方については公式のDocumentや他の記事、AI利用等で問題なく情報を得ることができるため、本記事ではdevcontainer.jsonの基本的な構文と主要な設定項目、その仕組みについて焦点を当てることにします。

devcontainer.jsonとは

devcontainer.jsonは、Dev Container環境の設定を定義するJSONファイルです。このファイルにより、開発環境に必要なツール、VSCode拡張機能、環境変数などを宣言的に定義することができます。以前の記事で記載した通り、このファイルを作成することで開発環境の再現性が向上し、チーム全体で統一された開発環境を簡単に構築することができるようになります。

ファイルの配置場所

devcontainer.jsonは以下のいずれかの場所に配置します。

.devcontainer/devcontainer.json

または

.devcontainer.json

推奨は.devcontainer/ディレクトリ内への配置です。これにより、Dockerfileやその他の関連ファイルを同じディレクトリにまとめることができるようになります。

imageの選択

最もシンプルなdevcontainer.jsonの例としては以下のような記述になります。

{
  "name": "My Dev Container",
  "image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye"
}

この例では、Microsoftが提供するPythonイメージを使用して、基本的なDev Container環境を構築しています。
利用できるイメージの一覧については以下のリンクから確認することができます。

https://github.com/devcontainers/images/tree/main/src

これらのイメージを利用しないで独自のDockerfileを使用することも可能です。その場合は以下のように記述することになります。

{
  "name": "My Dev Container",

  "dockerfile": "Dockerfile",
  "context": ".."
}

一般的な開発においては、公式のDev Containerイメージを使用することで、必要なツールやライブラリが事前にインストールされた状態で環境を構築することができます。ただし、不要なツール等もインストールされている可能性があるため、軽量なコンテナを求める場合は、独自のDockerfileを使用して必要なツールのみをインストールすることが推奨されます。一方で、必要なツールを追加する場合は後述のfeaturesセクションを利用することで、簡単に追加することができます。

Features

例えばPythonのImageにGitHub CLIをインストールした開発環境を構築する場合、以下のようにfeaturesセクションを使用するだけでツールのインストールが可能になります。

{
"name": "Node.js & Python Environment",
"image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye",
"features": {
    "ghcr.io/devcontainers/features/github-cli:1": {}
}
}

これにより、Dev Container内でGitHub CLIが自動的にインストールされることになります。Featuresは、Dev Container内で必要なツールやライブラリを簡単にインストールできる仕組みです。
利用できるFeaturesの一覧については以下のリンクから確認することができます。
https://github.com/devcontainers/features

Featuresの仕組み

Featuresは、OCI(Open Container Initiative)に準拠したコンテナイメージの一部として提供されるコンポーネントです。Baseのイメージの拡張パーツのような形で利用され、Dev Containerの機能を拡張します。

Featuresはメタデータであるdevcontainer-feature.jsonと設定用のScriptのinstall.shで構成されています。
参考:https://github.com/devcontainers/features/tree/main/src/aws-cli

そのため、自作のFeaturesを作成することも可能ですが、Dockerfileを使用してカスタムイメージを作る方が簡単で柔軟性があるため、外部に広くあまねく公開したい機能でない限りは自作Featuresを作るケースは少ないと思います。

Dev Containersの環境においてPython pyenv相当の仮想環境は必要か

一般的な開発環境では、Pyenvなどの仮想環境管理ツールが必須とされていますが、Dev Containers環境では状況が異なります。Dev Container自体がコンテナによって隔離された環境として機能するため、追加して仮想環境を構築する必要がありません。

Pythonのバージョン管理についても、Pyenvを使用する代わりに、Dev Containerのベースイメージを変更することで簡単に切り替えることができます。例えば、Python 3.11からPython 3.13に変更したい場合は、devcontainer.jsonのimageプロパティを以下のように変更するだけです。

{
    "name": "Python 3.13 Development",
    "image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye"
}

Poetryの様なパッケージ管理ツールについてはチームの方針や好みによって必要になってくるので、必要に応じてfeaturesを利用してインストールしましょう。

Extensions

Extensionsセクションでは、Dev Container内で自動的にインストールされるVSCode拡張機能を指定することができます。チームの中でExtensionを統一したい場合、この機能はとても効果的です。また、開発環境を移行する場合にもどのExtensionを利用していたかなどを意識する必要がないため、非常に便利です。

{
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "ms-python.pylint",
        "ms-python.black-formatter",
        "ms-vscode.vscode-typescript-next",
        "esbenp.prettier-vscode",
        "bradlc.vscode-tailwindcss"
      ]
    }
  }
}

Extensionをdevcontainer.jsonに追加するときは、VSCodeのExtentionの設定からAdd to devcontainer.jsonを選択することで、簡単に追加することができます。追加されている名前はExtensionのIDで、VSCodeのExtension Marketplaceで確認することができます。

VSCodeの設定カスタマイズ

拡張機能だけでなく、VSCodeの設定も統一できます。利用する言語はFrameworkではPathを通す必要があったりするため、この機能を利用することで開発環境のセットアップの手間を大幅に削減することができます。

{
  "customizations": {
    "vscode": {
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "python.defaultInterpreterPath": "/usr/local/bin/python",
        "python.linting.enabled": true,
        "python.linting.pylintEnabled": true,
        "files.exclude": {
          "**/__pycache__": true,
          "**/.pytest_cache": true
        }
      }
    }
  }
}

Lifecycle scripts

devcontainer.jsonでは、コンテナのライフサイクルに応じて特定のコマンドを実行するためのフックを設定できます。これにより、環境構築やサービスの起動などを自動化できます。

このあたりは公式のドキュメントが最も情報量が多いのでそちらを参照してください。

https://containers.dev/implementors/json_reference/#lifecycle-scripts

pip install -r requirements.txtnpm installなどのコマンドを自動で実行するのが一般的な使い方になると思います。実施したいコマンドによって呼び出すLifecycleは異なると思いますが、初期設定としては基本的にはpostCreateCommandで呼び出しておけば問題はないと思います。

{
  "postCreateCommand": "pip install -r requirements.txt"
}

複数のコマンドを実行したい場合は、&&でコマンドを配列で実行する形式で実行可能です。Listでの実行はエラーとなります。
※8/20 修正。うらがみ様ご指摘ありがとうございました。

{
  "postCreateCommand":"pip install -r requirements.txt && pre-commit install"
}

初期セットアップのコマンドの数が多くなる場合は、スクリプトファイルを作成してそれを呼び出す形を検討ことも選択肢に入ります。

{
  "postCreateCommand": "./scripts/setup.sh"
}

但し、この場合はスクリプトファイルを修正した場合にDev Containerが構成ファイルの変更を検知することができないため、devcontainer.jsonに列挙して記載することが良いかもしれません。

ネットワーク自動化開発におけるdevcontainer.jsonの例

筆者はネットワーク自動化のプロジェクトに携わることが多いため、ネットワーク自動化におけるdevcontainer.jsonの例を示します。ネットワーク自動化開発においては基本的にはPythonを利用することになるため、BaseのImageはPythonにしておくのが無難です。featuresに関してはGitHub CLIやgo-taskを利用することが多いと思います。また、重要な要素として自動化開発の途中で装置にpingを打つことは多いと思いますが、pingコマンドはデフォルトではインストールされていないことが多いので、必要に応じてpostCreateCommandで追加する必要があります。

PythonのLibraryに関してはiPythonやnetmiko等が必要になると思いますが、それらはrequirements.txtに記載しておくことで、postCreateCommandで自動的にインストールされるようにしておくのが無難です。

※8/27 修正。Hiroshi Koyama様ご指摘ありがとうございました。

{
  "name": "Network Automation Environment",
  "image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye",
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {},
    "ghcr.io/devcontainers/features/go-task:1": {}
  },
  "postCreateCommand":"apt-get update && apt-get install -y iputils-ping && pip install -r requirements.txt"
}

Team内での共通のdevcontainer.jsonを作成して、GitHubのTemplate Repositoryとして公開しています。これによって、devcontainer.jsonを1から作ったことが無いメンバーでも恩恵を受けつつ、修正をしながら習熟していける様な運用をしています。

まとめ

この記事では、devcontainer.jsonの基本的な書き方と主要な設定項目について説明しました

  • Features: 開発ツールの簡単インストール
  • Extensions: VSCode拡張機能の統一
  • Lifecycle Script: 環境構築の自動化

Featuresで対応できない複雑な環境構築が必要な場合は、Dockerfileを使用したカスタムイメージの作成が必要になりますが、基本的にはimageとfeaturesを組み合わせることで、ほとんどの開発環境を簡単に構築することができます。

Dockerfileを書けないとDev Containerを使えないということはなく、devcontainer.jsonを適切に設定することで、ほとんどの開発環境を簡単に構築することが可能ですし、そこから受けられる恩恵は非常に大きいため積極的に利用することをお勧めします。

Discussion

うらがみうらがみ

以下のように書かれていますが、この例はうまく動作しないのではないでしょうか?

複数のコマンドを実行したい場合は、配列形式も使用できます。

{
  "postCreateCommand": [
    "pip install -r requirements.txt",
    "npm install",
    "pre-commit install"
  ]
}

単一の文字列の場合は/bin/shで実行され、配列の場合はシェルを使用せず直接コマンドが実行されます。DockerfileにおけるCMDENTRYPOINTと同様ですね。

記事中にリンクされている公式ドキュメントにもその仕様が書かれています。

For each command property, if the value is a single string, it will be run in /bin/sh. Use && in a string to execute multiple commands. For example, "yarn install" or "apt-get update && apt-get install -y curl". The array syntax ["yarn", "install"] will invoke the command (in this case yarn) directly without using a shell.

以上のことから、記事中で例示されているpostCreateCommandはうまく動作しないと思いますが、いかがでしょうか。

Tomoya WatanabeTomoya Watanabe

ご連絡ありがとうございます。ご指摘の通りですね。。
間違った情報を公開してしまい、申し訳ございませんでした。

指摘頂いた内容について修正させていただきました。ご指摘、ありがとうございました。
お名前記載させて頂いておりますが、不都合ありましたら削除しますのでお申し付けください。

うらがみうらがみ

ご対応ありがとうございます!
名前の記載は何も問題ありません。ご配慮ありがとうございます。

Hiroshi KoyamaHiroshi Koyama

最後の例の下記について

  "postCreateCommand": [
    "apt-get update && apt-get install -y iputils-ping",
    "pip install -r requirements.txt"
  ]

こちらも次のように && で連結する必要はないでしょうか。

  "postCreateCommand":   "apt-get update && apt-get install -y iputils-ping && pip install -r requirements.txt"
Tomoya WatanabeTomoya Watanabe

ご指摘の通りですね。。ありがとうございます。

指摘頂いた内容について修正させていただきました。ご指摘、ありがとうございました。
お名前記載させて頂いておりますが、不都合ありましたら削除しますのでお申し付けください。