【2025年最新】開発環境をコンテナ化するDev Containersの機能をまとめる

## これは何?

https://qiita.com/sigma_devsecops/items/623eefcb95edc26a965a

前記事では，AIエージェントを企業で導入するにあたって，コンテナを使ってAIエージェントを隔離することでセキュリティを担保庵を検討した。

今回の記事では，実際にDev Containersを使って開発環境をコンテナ化するにあたってのベストプラクティスをまとめた。

## Dev Containersとは

https://code.visualstudio.com/docs/devcontainers/containers

 Dev Containersを使うと，Docker Container内でVS Codeを開くことができ，コンテナ内のソースコードを直接編集できる
- Dev Containers起動時に設定ファイルに記載のあるVS CodeのExtensions等も一括でインストールできる --> チームで開発環境を統一しやすい
- Dockerfileやcompose.yamlを汚さずにツールのインストールや設定変更ができる
- [GitHub Codespaces](https://github.com/features/codespaces)を使用する際にDev Containeersを選択することで，環境構築を自動化できる。

### Dev Containersをとりあえず試したい方

[サンプルリポジトリ](https://github.com/RyosukeDTomita/devcontainer-test)を更新したのでこちらを参照されたし。

### AIエージェントをDev Containers内で使用するメリット

前記事を見てもらったほうが良さそうだが，一旦箇条書きしておく。

- コンテナ外のリソースを意図せずに破壊される心配がない
- コンテナのほうがクラウドやVDIと違い，ローカルで実行できるのでネットワーク遅延の影響を受けにくい
- Dev Containersは開発環境をコンテナ化する上である程度枯れている。

### AIエージェントが使用しやすいDev Containersの設定とは?

AIエージェント≒人間がストレスを感じにくいということを意識して，開発環境をコンテナ化したい。

基本的には今まで言われていたことと変わらない気はするが以下の3つの観点で本記事は記載する。

- [モチベーション①: コンテナとローカルの差異を減らしたい](#コンテナとローカルの差異を減らす)
- [モチベーション②: なるべく再ビルドしたくない](#サービスをコンテナビルドなしで再起動可能にする)
- [モチベーション③: ビルド時間を短くしたい](#ビルド時間を短くするには)

---

## Dev Containersを使う前に

### Multi Stage build使ってDev Containers用の環境を作る

基本的にデプロイされるコンテナに不必要なものは含めたくないので，Multi Stage buildを使ってDev Containers用の環境を作ることが推奨される。

#### Dockerfileでtargetを指定

- **compose.yamlを使わず**，Dockerfile使う場合にはdevcontainer.jsonでtargetの指定ができる。

  ```Dockerfile
  FROM mcr.microsoft.com/devcontainers/  typescript-node:22-bookworm AS devcontainer # target名を指定する
  ```

- .devcontainer/devcontainer.jsonでtargetを指定する

  > A string that specifies a Docker image build target that should be passed when building a Dockerfile. Defaults to not set. For example: "build": { "target": "development" } [^1]

  ```json
  {
    "name": "dev-container-test",
    "build": {
      "dockerfile": "Dockerfile",
      "target": "devcontainer"
    },
  }
  ```

#### compose.yamlを使ってtargetを指定

compose.yamlを使う場合には`target`をcompose.yaml側で指定するためにDev Containers用に`.devcontainer/compose.yaml`を作成する必要がある。[^2]，[^3]

```yaml
# compose.yaml
services:
  react-app:
    build:
      target: devcontainer // targetを指定
      context: ./
      dockerfile: Dockerfile
    image: react-img-devcontainer:latest
    container_name: react-container-devcontainer
```

```Dockerfile
FROM mcr.microsoft.com/devcontainers/typescript-node:22-bookworm AS devcontainer # target名を指定する
```

:::note info
compose.yamlの`services`名をdevcontainer.jsonの`service`に指定する。

```json
{
  "name": "dev-container-test", // 任意の名前
  "dockerComposeFile": [
    "../compose.yaml",
    "compose.yaml"
  ],
  "service": "react-app", // compose.yamlのサービス名
}
```
:::

---

## コンテナとローカルの差異を減らす

### Dev Containers公式イメージを使う

公式イメージを使うことで，通常のコンテナをベースにするよりもある程度開発で使用されるツールがインストールされている。

[Dev Containers公式イメージ](https://github.com/devcontainers/images/tree/main/src)がいくつかの言語ではサポートされている。

:::note info
例えば，[base-debian](https://github.com/devcontainers/images/blob/main/src/base-debian/.devcontainer/Dockerfile)は[builderpack-deps](https://hub.docker.com/_/buildpack-deps)をベースにして作成されている。
builderpack-depsとは，ビルド時に必要なパッケージをまとめたイメージ。
> A collection of common build dependencies used for installing various modules, e.g., gems.[^4]
:::

---

### dotfilesを使い，設定ファイルをローカルと共通化する

:::note info
dotfilesは設定ファイルやスクリプト等をまとめたリポジトリのこと。
e.g. .bashrc, .gitconfig, .vimrc, etc.
:::

**VSCodeの個人用のsettings.json**に以下を参考に記載することでdotfilesをDev Containersに持ち込むことができる。[^5]

:::note info
.gitconfigは何もしなくてもDev ContainersのHOMEディレクトリにコピーされる。
:::

```json
  // Dev Containersでdotfilesを使う。
  "dotfiles.repository": "https://github.com/RyosukeDTomita/dotfiles.git",  // fixme
  "dotfiles.targetPath": "~/dotfiles", // fixme
  "dotfiles.installCommand": "install.sh", // fixme ~/dotfilesのrepository topからみたスクリプトのパスを指定する。
```

:::note info
`dotfiles.installCommand`には~/dotfilesのrepository topから見たスクリプトのパスを指定する。
そのため，dotfilesのGitHub Repositoryにinstall.shは配置が必要。[自分のdotfilesのinstall.sh](https://github.com/RyosukeDTomita/dotfiles/blob/main/install.sh)
:::

:::note info
`dotfiles.installCommand`を指定しない場合にはデフォルトのファイル名として以下が存在すればdotfilesのinstallが実行されるとVS Codeの設定画面に記載がある。

![default_install_script.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/4be2a2d2-ade9-4127-8c29-f09fa72d410d.png)
:::

#### dotfiles TIPS

Dev Containerの環境では，`REMOTE_CONTAINERS=true`が環境変数に定義されており，これを使ってDev Containerかどうかを判別できるためローカル用とDev Containers用で処理を分けることができる。[dotfilesのinstall scriptでREMOTE_CONTAINERS=trueで分岐する例](https://qiita.com/sigma_devsecops/items/c40031fc05eeb0811410)

---

### VS Code ExtensionsをDev Containersに持ち込む

devcontainer.jsonに記載することで，コンテナビルド時に自動でVS CodeのExtensionsをインストールでき，コンテナビルド時に毎回Extensionsを手動でインストールしなくて良くなる。

#### Extensionsを追加する方法

Extension IDを.devcontainer/devcontainer.jsonに記載する。
![extensionid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/2ba4178d-241b-4df0-8a4f-6d5a2d70731a.png)


```json
  "extensions": [
    "formulahendry.auto-rename-tag",
  ],
```

#### DevContainerに自分だけが使用するExtensionsを持ち込む

- **個人用のsettings.json**に記載することで，リポジトリの設定を変更せずにExtensionsを追加できる。

```json
  // Dev containersに個人的に使うExtensionsを入れる。
  "dev.containers.defaultExtensions": [
    "vscodevim.vim"
  ],
```

#### Extensionsの設定をどこに記載するか

- devcontainer.jsonと.vscode/settings.json両方に記載ができる。
- 基本はリポジトリ配下の.vscode/settings.json配下に設定を記載するで良いと思っている。


### Dockerfileを汚さずにDev Containersにツールをインストールする

[features一覧](https://containers.dev/features)から使用可能なツールを探すことができる。

devcontainer.jsonにfeaturesを記載することで，ツールをインストールすることができる。

```json

  "features": {
    "ghcr.io/devcontainers/features/aws-cli:1": {},
    "ghcr.io/guiyomh/features/vim:0": {},
    "ghcr.io/dhoeric/features/hadolint:1": {}
  },
```

features一覧に記載がないツールに関しては

`postCreateCommand`にinstallコマンドを記載する。

### おまけ: コンテナのportを開放する設定について

devcontainer.jsonでportを開放する方法は3つある。

- `addPort`は非推奨。`forwardPorts`を使う方が良い。
  > In most cases, we recommend using the new forwardPorts property. [^6]
- `forwardPorts`を使うことで，コンテナのポートをローカルにフォワードすることができる。
- `portAttributes`を使うことで，portに関連する追加属性を設定することができる。

:::note info
2025年3月に動作検証したところ，ローカルのVS CodeではportAttributeのみでportフォワーディングが可能だった。
しかし，[GitHub Codespaces](https://github.com/features/codespaces)を使う場合には`portAttributes`のみではportフォワーディングがされなかった。

`portAttributes`のみ
![portForward_only.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/03543526-62de-451e-8c8e-c8bab185cf43.png)

`forwardPorts`も追加すると自動的にportフォワーディングされる
![forwardPorts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/a2576e6a-90f4-47ff-8bc4-1d2c36e32542.png)


実際に公式ドキュメントには`forwardPorts`のみしか記載がなく[^7]，`forwardPorts`と`portAttributes`を併用することが推奨される。
:::

```json
{
  "name": "dev-container-test", // 任意の値
  "dockerComposeFile": [
    "../compose.yaml",
    "compose.yaml"
  ],
  "service": "react-app", // compose.yamlのサービス名
  "workspaceFolder": "/app",
  "forwardPorts" : [5173, 5173],
  "portsAttributes": {
    "5173:5173": {
      "label": "Vite application",
      "protocol": "http",
      "onAutoForward": "notify"
    }
  },
  "postStartCommand": "cd react-app/ && yarn dev --host 0.0.0.0",
}
```


---

## サービスをコンテナビルドなしで再起動可能にする

Dev Containersを起動しつづけるために永続コマンドが必要であるが，このサービスの再起動が必要になった場合には，コンテナの再ビルドが必要である。

```Dockerfile
CMD ["nginx", "-g", "daemon off;"]
```

しかし，これは非効率的なので，Dev Containersにはコンテナのイベントをトリガーしてコマンドを実行する機能[^8]を使い，サービスの再起動を可能にすることができる。

- `OverrideCommand`を`true`に設定することで，コンテナを永続化することができる[^1]。
- `postStartCommand`でサービスを起動することで，コンテナの永続化にサービスが使われなくなり，再起動が可能になる。

```json
{
  "name": "dev-container-test",
  "dockerComposeFile": [
    "../compose.yaml",
    "compose.yaml"
  ],
  "service": "react-app",
  "workspaceFolder": "/app",
  "overrideCommand": true, 
  "forwardPorts" : [80, 80],
  "postStartCommand": "nginx"
}
```

---

## ビルド時間を短くするには

以下の2軸でビルド時間を短くできる。

- imageサイズの縮小
- キャッシュの有効活用

https://zenn.dev/sigma_tom/books/e905f2a57b9627

---

## References

[^1]: <https://containers.dev/implementors/json_reference/#general-properties>

[^2]: <https://github.com/microsoft/vscode-remote-release/issues/7810>

[^3]: <https://stackoverflow.com/questions/78421879/devcontainer-docker-compose-best-practice>

[^4]: <https://hub.docker.com/_/buildpack-deps>

[^5]: <https://code.visualstudio.com/docs/devcontainers/containers#_personalizing-with-dotfile-repositories>

[^6]: <https://containers.dev/implementors/json_reference/#image-specific>

[^7]: <https://docs.github.com/en/codespaces/developing-in-a-codespace/forwarding-ports-in-your-codespace#automatically-forwarding-a-port>

[^8]: <https://containers.dev/implementors/json_reference/#lifecycle-scripts>