開発コンテナ、使わんと？👀

## 始めに

皆さん、Docker好きですか。僕は大好きです。
ありもののサービスをコンテナで動作させるのは勿論のこと、最近ではコンテナの内部に開発環境を閉じ込めて開発ツールとして使用される場面も増えてきたように思います。

開発コンテナのメリットには、下記のものがあります。
・ホスト環境が汚れない
・気軽に開発環境を構築/破壊できる
・開発環境の可搬性が高まる

要するに、開発環境を構築する心理的障壁を取っ払って、誰でも同じ開発環境を構築できるのが、開発コンテナの最大の強みです。

本記事では、開発コンテナを使用するための手順や、開発コンテナを使用するうえでのTipsをまとめます。
こういうアイデアもあるよ！などあれば、コメントで教えていただけると幸いです。

https://containers.dev/

## 1. 要件を満たす

開発コンテナを使用するための要件は下記の通りです。
・ホスト環境にDockerがインストールされていること
・ホスト環境にVSCodeがインストールされていること

ここでいうホスト環境とは、Linux環境を想定しています。
また、僕は普段Linux環境をSSHで接続するため、実際にはVSCodeをWindows環境にインストールしています。
WSL環境を使用される方、Linux環境に直接ログインして使用される方は、適宜読み替えてください。

### 1.1. Dockerをインストールする

Dockerをインストールする詳細な手順は、公式サイトを参考にしてください。
Dockerをインストールする便利スクリプトが公式サイトにあるので、僕はそれを使用しました。

https://docs.docker.com/engine/install/

余談ではありますが、DockerにはRootlessモードがあります。
Dockerデーモンやコンテナを一般ユーザー(非rootユーザー)のプロセスとして動作させることで、安全性を高められるモードです。
また、ホスト環境の一般ユーザーがコンテナ内部のrootユーザーにマッピングされるため、コンテナ内部で作成されたファイルの所有者がホスト環境の一般ユーザーとなり、扱いやすいという副次的なメリットもあります。
※Rootlessモードが実装される前は、コンテナを起動するときにUID/GIDを指定したり、dockerfileで一般ユーザーを作成したり、一手間必要でした。
正直、Rootlessモードを使用しない選択肢はないと思いますが、動作が不安定である、などの声も散見されますので、考慮したうえで使用してください。
開発コンテナを使用するうえでは、必須ではありません。

https://docs.docker.com/engine/security/rootless/

### 1.2. VSCodeをインストールする

VSCodeをインストールする詳細な手順は、公式サイトを参考にしてください。
と言っても、インストーラーをダウンロードしてインストールするだけです。

https://code.visualstudio.com/

インストールしたら、設定ファイルを作成します。

VSCodeの設定ファイルの一例は、下記の通りです。
```json:settings.json
{
  "editor.fontSize": 16,
  "editor.fontFamily": "UDEV Gothic NF",
  "editor.renderWhitespace": "all",
  "files.trimTrailingWhitespace": true,
  "files.insertFinalNewline": true,
  "files.trimFinalNewlines": true,
  "security.workspace.trust.enabled": false,
  "window.confirmSaveUntitledWorkspace": false,
  "workbench.colorTheme": "GitHub Dark Default"
}
```

設定ファイルを作成したら、拡張機能をインストールします。
VSCodeにインストールする拡張機能は、下記の通りです。
・[Japanese Language Pack for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-ja)
・[Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)

インストールしたら、SSHの設定ファイルを作成します。
SSHの設定ファイルの一例は、下記の通りです。
```config:%USERPROFILE%\.ssh\config
Host <linux-host>
    HostName 192.168.xxx.xxx
    User <user>
    PasswordAuthentication no
    IdentityFile "<identity-file>"
    IdentitiesOnly yes
    Port xxx
```

ここまでで、Windows環境のVSCodeからLinux環境にSSHで接続して、開発コンテナを使用するための要件を満たせました。

## 2. 開発コンテナを定義する

開発コンテナでは、あらゆる言語の開発環境を構築することができ、その定義をJSON形式で作成します。
ここでは、Hugoでポートフォリオを作成する環境を例にして、開発コンテナを定義します。

https://github.com/zen96k/enoshima-escar

https://containers.dev/implementors/spec/#devcontainerjson

https://containers.dev/implementors/json_reference/

Linux環境で適当な作業ディレクトリを作成して、VSCodeで開きます。
開いたら、.devcontainerディレクトリを作成して、devcontainer.jsonを作成します。

ファイル構成の一例は、下記の通りです。
```bash
enoshima-escar
|-- .devcontainer
|   |-- devcontainer.env
|   |-- devcontainer.json
|   `-- post_start_command.sh
|-- .dockerignore
|-- dockerfile
```

devcontainer.jsonの一例は、下記の通りです。
```jsonc:devcontainer.json
{
  "name": "enoshima-escar",
  "image": "debian:12.6",
  "features": {
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/devcontainers/features/hugo:1": {
      "version": "0.128.2"
    }
  },
  // "build": {
  //   "dockerfile": "../dockerfile"
  // },
  "runArgs": [
    "--env-file",
    ".devcontainer/devcontainer.env",
    "--name",
    "ee-hugo",
    "--rm"
  ],
  "containerEnv": {
    "TZ": "Asia/Tokyo"
  },
  "forwardPorts": [1313],
  "customizations": {
    "vscode": {
      "extensions": [
        "eamodio.gitlens",
        "ms-azuretools.vscode-docker",
        "MS-CEINTL.vscode-language-pack-ja",
        "esbenp.prettier-vscode",
        "tamasfe.even-better-toml",
        "kennylong.kubernetes-yaml-formatter",
        "DavidAnson.vscode-markdownlint",
        "dotenv.dotenv-vscode"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.tabSize": 2,
        "editor.renderWhitespace": "all",
        "files.trimTrailingWhitespace": true,
        "files.insertFinalNewline": true,
        "files.trimFinalNewlines": true,
        "[dockerfile]": {
          "editor.defaultFormatter": "ms-azuretools.vscode-docker"
        },
        "[json]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[jsonc]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[toml]": {
          "editor.defaultFormatter": "tamasfe.even-better-toml"
        },
        "[yaml]": {
          "editor.defaultFormatter": "kennylong.kubernetes-yaml-formatter"
        },
        "[markdown]": {
          "editor.defaultFormatter": "DavidAnson.vscode-markdownlint",
          "files.trimTrailingWhitespace": false
        }
      }
    }
  },
  "postStartCommand": "bash .devcontainer/post_start_command.sh"
}
```

僕が個人的によく使用するプロパティは、下表の通りです。
| プロパティ | 型 | 説明 |
|:--|:--|:--|
| name | 文字列 | VSCodeで表示される開発コンテナの名前。docker container lsしたときに表示されるコンテナの名前ではないことに注意。|
| image | 文字列 | 開発コンテナのベースとなるイメージ。 |
| build.dockerfile | 文字列 | 開発コンテナのイメージをビルドするdockerfileのパス。 |
| features | オブジェクト | 開発コンテナに追加する機能。 |
| runArgs | 配列 | 開発コンテナを起動するときに渡すオプション。 |
| containerEnv | オブジェクト | 開発コンテナを起動するときに渡す環境変数。 |
| forwardPorts | 配列 | 開発コンテナからホスト環境にフォワードするポート番号。 |
| postStartCommand | 文字列/配列/オブジェクト | 開発コンテナの起動時に実行されるコマンド。ライフサイクルスクリプト。 |

ライフサイクルスクリプトについては、公式サイトを参考にしたいところですが、英弱なのでQiitaの記事も参考にしました。

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

https://qiita.com/1mono2/items/5bbf91f588ab9d5cd444

:::note info
**Tips: featuresを積極的に活用する**
プロジェクトが依存する機能をfeaturesとして開発コンテナに追加しましょう。
https://containers.dev/features
必要な機能がfeaturesにない場合のみ、dockerfileを作成して開発コンテナをビルドする、くらいの心構えで良いと思います。
:::
:::note info
**Tips: runArgsで環境変数やコンテナの名前を渡す**
環境変数については、containerEnvでも開発コンテナに渡すことができますが、ものによっては開発者ごとに異なる値を設定したかったり、リポジトリで管理したくなかったりすると思います。
そんな場合にrunArgsは有効です。僕はgitの設定値を環境変数で渡しています。

コンテナの名前については、好みによるところが大きいです。
複数の開発者が同じホスト環境で(同じ一般ユーザーで)開発コンテナを起動する場合は、名前が重複してしまうので指定しないのもありだと思います。
:::
:::note info
**Tips: customizationsに開発コンテナにインストールする拡張機能を記述する**
開発コンテナで使用する拡張機能はdevcontainer.jsonに記述しましょう。
devcontainer.jsonとは別に.vscode/extensions.jsonに記述する方法もありますが、devcontainer.jsonに記述することで、開発コンテナが起動するときに**自動**で**開発コンテナ内部**に拡張機能がインストールされます。
開発コンテナをリビルドする度に手動で拡張機能をインストールする必要がなく、開発コンテナに拡張機能を閉じ込めることができるので、ホスト環境もキレイに保てます。
:::
:::note info
**Tips: postStartCommandで痒いところに手を伸ばす**
これができたら便利だな、という小ネタをpostStartCommandに記述しましょう。
postStartCommandの一例(シェルスクリプト)は、下記の通りです。
```bash:post_start_command.sh
#! /usr/bin/env bash

set -euxo pipefail
SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd)

cd ${SCRIPT_DIRNAME}

rm -rf ${HOME}/.gitconfig
git config --global init.defaultBranch ${GIT_INIT_DEFAULT_BRANCH}
git config --global user.name ${GIT_USER_NAME}
git config --global user.email ${GIT_USER_EMAIL}
```
:::

ここまでで、開発コンテナを定義して、起動する準備ができました。

## 3. 開発コンテナを起動する

いよいよ開発コンテナを起動します。
VSCodeでF1キーを押下して、コマンドパレットを開いてください。
"Dev Containers: Open Folder in Container..."を選択して、.devcontainerディレクトリがある階層で"OK"を選択します。
画像のように、開発コンテナが起動すればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281292/6628d044-c293-9238-2965-f25d5336fd5a.png)

お疲れ様でした。
後はもう、今まで生きてきた中で、一番自由になって、開発してください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/V38jQMwf-nE?si=MB8NBB9nSHJQVXl9" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 終わりに

開発コンテナについて、ざっくり説明(布教)しました。
開発コンテナは、個人開発やチーム開発を問わず、強力な開発ツールになります。
開発環境を構築するのが面倒くさいと感じている人にこそ、触ってほしいです。
Love Docker, Love Dev Container.