Dockerfileを使った簡単な開発環境の構築

## はじめに

Dockerfileからコンテナを作成し、その中で簡単な開発作業をしたい場合の備忘録です。
- VSCodeの開発コンテナ機能を使用してコンテナ内で開発する環境を構築します
  - この開発用コンテナのことを「Dev Container」と呼びます
- Dev Containerを起動する方法は下記の3種類があります
  - DockerイメージからDev Containerを作成・起動
  - **DockerfileからDev Containerを作成・起動**
  - Docker Composeで複数コンテナを立ち上げ、うち1つのコンテナをDev Containerとして使用
- 本記事では下記を扱います
  - DockerfileからDev Containerを作成・起動
  - Dev Containerに拡張機能を自動インストール
  - ワークスペース設定で拡張子固有の自動フォーマット
- 参考:[Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)  

## 準備
- VSCode
  - 拡張機能「Remote Development」をインストールする必要があります
- Dockerが利用できる環境
  - Docker Desktop for Mac/Windowsなど
  - 今回はWSL2 + Docker CE環境です
- Dockerfile
  - 今回はC++をコンパイル・実行できる環境を用意します

## やってみる

開発コンテナ機能を使用してDev Containerで開発するためには設定ファイル(`devcontainer.json`)を作成する必要があります。
まずは適当なDockerfileを用意します。
```Dockerfile:Dockerfile
# ベースイメージとしてUbuntuを使用
FROM ubuntu:latest

# 必要なパッケージをインストール
RUN apt-get update && \
    apt-get install -y build-essential && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 作業ディレクトリを設定
WORKDIR /workspace

# デフォルトのコマンドを指定
CMD ["bash"]
```

次に設定ファイル(`devcontainer.json`)を作成します。
ステータスバー左端にあるリモート開発機能のアイコンをクリックし、「Add Dev Container Configuration Files...」を選択します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/24b41619-1ece-3815-b84d-25668cbeaa48.png)


選択肢が出てくるので「From 'Dockerfile'」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/06d4d234-b708-f05f-1a2f-fd1dbe192b6e.png)

追加機能のリスト(詳細は[Available Dev Container Features](https://containers.dev/features))が表示されますが、ここでは何もチェックせず「OK」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/2c14f4f3-3b15-bbb7-42db-a15f36ac979f.png)

`.devcontainer`というディレクトリとその中に`devcontainer.json`が作成されます。
(`.github`ディレクトリと`dependabot.yml`ファイルも作成されますが無視します)
この`.devcontainer/devcontainer.json`が存在するワークスペースを開くと、右下に開発コンテナ機能の確認ダイアログが現れます。
「Reopen in Container」をクリックします。

:::note info
ダイアログが出てこない場合
コマンドパレットから「Dev Containers: Reopen in Container」を実行します。
コマンドパレットは「F1」もしくは「`Ctrl` + `Shift` + `P`」で呼び出します。
(MacOSの場合、`Ctrl`は`⌘`に読み替える)
:::



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/ea04186b-e907-b9cc-a6d6-ecfb2411f153.png)

ステータスバー左端の表示が「Dev Container」となり、VSCodeの作業環境がDev Container内になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/456a7212-9b35-5b25-a213-60e15a2a6227.png)


## 問題点
これでDev Containerの作成・起動までできましたが問題があります。
現行の`devcontainer.json`では、デフォルトのユーザーは`root`になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/488d18dd-f0ef-7ed0-a6ba-612f24ed0cc7.png)


`root`ユーザーでファイルに書き込みを行うと、ファイルがローカルシステムに root ユーザーの所有として書き込まれてしまいます。
そこで、一般的に使用されるUID 1000のユーザー(非rootユーザー)でコンテナに接続するように設定します。

## `common-utils`を使って`Dev Container`の初期設定

Dev Container Featuresで使える`common-utils`を使ってDev Containerの初期設定を行います。
参考：[Available Dev Container Features](https://containers.dev/features)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/0e208224-613d-4974-e41d-fd36452c7f44.png)

`common-utils`は非ルートユーザーの設定のほか、zshのインストールなど指定することができます。
参考：[Common Utilities (common-utils)](https://github.com/devcontainers/features/tree/main/src/common-utils)

現行の`devcontainer.json`を下記のように書き換え、devuserというUID/GID : 1000の非ルートユーザーを作成・指定します。そのほか、zshのインストールなど設定しています。
```json:devcontainer.json
{
	"name": "Existing Dockerfile",
	"build": {
		"context": "..",
		"dockerfile": "../Dockerfile"
	},
    "features": {
        "ghcr.io/devcontainers/features/common-utils:2": {
            "installZsh": true,
            "configureZshAsDefaultShell": true,
            "installOhMyZsh": true,
            "installOhMyZshConfig": true,
            "upgradePackages": true,
            "username": "devuser",
            "userUid": "1000",
            "userGid": "1000",
            "nonFreePackages": false
        }
    },
	"remoteUser": "devuser"
}
```

:::note warn
Ubuntu 24.04以降のDockerイメージを使用する場合
Ubuntu 24.04のDockerイメージから、UID/GID：1000のubuntuというユーザーが追加されました。
そのためUbuntu 24.04以降を使用する場合、UID/GID:1000のユーザーをcommon-utilsで設定するとエラーが発生します。

参考：[Ubuntu 24.04 docker images now includes user ubuntu with UID/GID 1000?](https://askubuntu.com/questions/1513927/ubuntu-24-04-docker-images-now-includes-user-ubuntu-with-uid-gid-1000)
:::


Ubuntu 24.04以降のDockerイメージを使用する場合には、下記のように`remoteUser`をubuntuに指定します。

```json:devcontainer.json
{
	"name": "Existing Dockerfile",
	"build": {
		"context": "..",
		"dockerfile": "../Dockerfile"
	},
	"remoteUser": "ubuntu"
}

```

zshなどの設定をcommon-utilsを使用して設定する場合には下記のように`features`を設定します。

```json:devcontainer.json
{
	"name": "Existing Dockerfile",
	"build": {
		"context": "..",
		"dockerfile": "../Dockerfile"
	},
    "features": {
        "ghcr.io/devcontainers/features/common-utils:2": {
            "installZsh": true,
            "configureZshAsDefaultShell": true,
            "installOhMyZsh": true,
            "installOhMyZshConfig": true,
            "upgradePackages": true,
            "nonFreePackages": false
        }
    },
	"remoteUser": "ubuntu"
}
```
書き換えたらコマンドパレットから`Dev Containers: Rebuild Container`を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/91418f96-2a62-32d9-abe0-94ad3ceef254.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/69475925-7de7-8c68-4785-9c9adb07c286.png)

これで簡単な開発環境ができました。

## 拡張機能の自動インストール

`devcontainer.json`に使用したい拡張機能を記述しておくと、Dev Containerの起動時にそれらの拡張機能が自動的にインストールされます。
現在、Dev Containerの起動時には何もインストールされていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/2e0c5803-4c28-8613-6cd4-247ffa9dfbce.png)

今回は、以下の拡張機能をDev Containerに自動インストールする設定を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/07d9e518-e365-65f5-c498-c1b596261908.png)

設定には拡張機能の識別子が必要です。既にインストールされている拡張機能の識別子は以下のコマンドで確認できます。
```zsh
code --list-extensions
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/5a38d928-c4bb-d689-9821-f8b5d679b175.png)

以下のように、`customizations`セクションを追加し、先ほど確認した拡張機能を指定します。
```json:devcontainer.json
{
	"name": "Existing Dockerfile",
	"build": {
		"context": "..",
		"dockerfile": "../Dockerfile"
	},
    "features": {
        "ghcr.io/devcontainers/features/common-utils:2": {
            "installZsh": true,
            "configureZshAsDefaultShell": true,
            "installOhMyZsh": true,
            "installOhMyZshConfig": true,
            "upgradePackages": true,
            "nonFreePackages": false
        }
    },
	"customizations": {
		"vscode": {
			"extensions": 
			[
				"ms-vscode.cmake-tools",
				"ms-vscode.cpptools",
				"ms-vscode.cpptools-extension-pack",
				"ms-vscode.cpptools-themes",
				"twxs.cmake"
			]
		}
	},
	"remoteUser": "ubuntu"
}
```
書き換えたら、コマンドパレットから`Dev Containers: Rebuild Container`を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/ac85564a-bddf-60dc-06a2-ba1238d51029.png)

これにより、指定されたVS Code拡張機能がDev Containerに自動的にインストールされます。

## ワークスペース設定でファイルを自動フォーマット

VSCodeには「ユーザー設定」と「ワークスペース設定」の2つの設定があります。
ワークスペース内に`.vscode/settings.json`を作成することにより、後者の設定ができます。
詳細は[こちら](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson)やQiitaにもわかりやすい解説記事があるのでそちらを参考にしてください。
今回は下記の言語固有設定を行います。
- `C++`ファイル保存時に自動的にフォーマット

```json:settings.json
{
    "[cpp]": {
        "editor.formatOnSave": true
    }
}
```
これで保存時に自動でフォーマットが効くようになりました。

## 備考

### `dependabot.yml`
`devcontainer.json`を作成した際に`.github`ディレクトリと`dependabot.yml`ファイルも自動的に生成されました。
このファイルはGitHub Dependabotの設定ファイルです。
開発コンテナの機能に更新があるかチェックし、自動的にプルリクエストを作成して更新を提案するもののようですが、不要であれば削除しても問題ありません。

```yml:dependabot.yml
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
 - package-ecosystem: "devcontainers"
   directory: "/"
   schedule:
     interval: weekly
```

### Dev Containers: Rebuild Without Cache and Reopen in Container

キャッシュを使用せずにコンテナを再構築するコマンドです。
Dev Containerをクリーンな状態で作り直したい場合に有効です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852303/9ba4ead8-10f2-191d-f950-7bf2c22baef6.png)