【devcontainer完全ガイド】DockerとWSLで最強のイマドキ開発環境を手に入れよう!
- devcontainerをローカルで構築します
devcontainerをリポジトリに組み込めば、誰でも同じように環境再現できるようになります - devcontainerとWSLとGitの連携
- Dockerの勉強の一環にもなります
- その他devcontainerのノウハウ
はじめに
#みなさんDocker活用してますか
CI/CDでDocker Imageを使うのはもちろんですが、やはりコンテナの有用性は、環境に依存せずどこでも同じように動作することですよね。
それを活用してみんな同じ開発環境が使えたら嬉しくないですか
そんなときに使えるのがdevcontainerの仕組みです。
devcontainerを活用した機能で、GitHub Codespacesというのもあります。
リポジトリをまるごとdevcontainerで他の開発者と共有できるのでやりたいことは叶います。
ただし、筆者は以下の理由で使っていませんでした。
- ブラウザで動かす場合、操作に癖があって使いにくい。
- 環境はローカルに置いておきたい (リポジトリを直接弄っている気がしてソワソワする)。
ですが、devcontainerを理解すればCodespacesが何をやっているかが分かるようになるので、その勉強としても試してみる価値はあると思います。
GitHub Codespacesについては、こちらをチェック
GitHub CodespacesによるJavaのチーム開発環境の作り方
以下に当てはまるアナタへ役立つ記事です。
- Node.js, npx, Javaなどはどんどんバージョン増えるので入れたくない。
- VSCodeの拡張機能がたくさん増えてしまって重くなってしまうのも嫌。
- プロジェクトの新規加入メンバーに導入手順を渡して1日浪費するのは、もったいないし面倒。
(やることでプロジェクトの理解は深まると思いますが)
VSCode (WindowsにそのままインストールでOK)
+
VSCode拡張機能
- ms-ceintl.vscode-language-pack-ja: 一応、日本語化用
- ms-vscode-remote.remote-containers: devcontainerに必要
- ms-vscode-remote.remote-wsl: WSL環境に必要
なければコマンドプロンプトで以下を実行してください。
winget install Microsoft.VisualStudioCode
code --install-extension ms-ceintl.vscode-language-pack-ja
code --install-extension ms-vscode-remote.remote-containers
code --install-extension ms-vscode-remote.remote-wsl
説明する内容
#- WSLのセットアップ (できていれば飛ばしてOK)
- 作業ディレクトリの用意
- 開発環境イメージの選定と定義
- devcontainer.jsonの実装
- devcontainerの立ち上げ
- リポジトリにpushじゃあああ
WSLのセットアップ (できていれば飛ばしてOK)
#WSLのインストール
#-
WSL本体の導入
Powershell (管理者権限) で以下を実行
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux # 実行後に再起動が必要
-
Ubuntuのディストリビューションを指定してインストール
コマンドプロンプトで以下を実行 (時間かかります)
SET DISTRIBUTION=Ubuntu-22.04 WSL --install --distribution %DISTRIBUTION% REM デフォルトユーザーとパスワードの設定
ディストリビューションのアンインストールWSL --unregister Ubuntu-22.04 winget uninstall Canonical.Ubuntu.2204
WSL環境にDocker CEをインストール
#WindowsのDocker Desktopには会社の制限がありますよね。
「Dockerが使えない」と泣いていたあなたでも、WSLが使えるのであればUbuntu側にDocker CEをインストールすれば解決です。
以下をWSL環境のBashターミナルで実行して数分待てばインストール完了です。
Ctrl+Shift+P
でコマンドパレットを開くWSL: Connect to WSL
を入力しEnter- 前提の拡張機能が入っていればウィンドウが切り替わるはずです
- WSL環境でのターミナルはBashを使いましょう
sudo apt update
sudo apt install -y \
ca-certificates \
curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin &&
sudo apt clean &&
sudo rm -rf /var/lib/apt/lists/*
sudo service docker start
sudo usermod -aG docker $USER
# ターミナル再起動後にsudo無しで実行可能
上記コマンドの詳細が知りたければこちらをご参照ください Install Docker Engine on Ubuntu
- WSL環境のVSCodeにDockerの拡張機能ms-azuretools.vscode-dockerを入れておくことをオススメします。
コンテナやイメージの一覧がすぐ見られます。 - Windows環境に入れる場合、Docker Desktopがないと怒られます。
とりあえずGitもセットアップ
#git config --global user.name "[名前]"
git config --global user.email "[メールアドレス]"
ssh -T git@github.com
# 聞かれたら`yes`
作業ディレクトリの用意
#以降で作るものの完成形は、こちらのリポジトリの内容です devcontainer_sample
-
適当なフォルダをWSL環境で用意してください。
(下記の例はユーザーフォルダ直下のdevcontainer_sampleフォルダ)mkdir ~/devcontainer_sample code ~/devcontainer_sample
codeコマンドVSCodeのコマンドです。PATHを通せばcmdからでも任意のものがVSCodeで開けます。
引数でパスを指定します。- 対象がファイルであれば、カレントウィンドウで開いて編集できるようになります。
- 対象がフォルダであれば、ウィンドウを切り替えてワークフォルダが指定のパスに切り替わります。
詳細はリファレンスで。 The Visual Studio Code command-line interface
-
作成したフォルダが新規ウィンドウで開かれているので、ここで .devcontainerフォルダを作成
mkdir .devcontainer
-
作成した .devcontainerフォルダ内にファイルを作成
touch .devcontainer/{Dockerfile,devcontainer.env,compose.yaml,devcontainer.json}
-
こうなっていればOKです
~/devcontainer_sample$ tree -a . └── .devcontainer ├── Dockerfile ├── compose.yaml ├── devcontainer.env └── devcontainer.json
treeコマンドはデフォルトでは入っていませんsudo apt install tree
開発環境イメージの選定と定義
#いよいよdevcontainerのDocker Image設定ファイルを作っていきます。
VSCodeのリファレンスには、開発言語に合わせてイメージとイメージタグを選択するフローが記載されていますが、個人的にこれはオススメできません。
イマドキの開発は、複数の言語を同時に使った開発が多いと思っています。
Java用のイメージを使ってdevcontainerを使っていても、途中で別の言語のインストールをしていたら、イメージを使っている意味が薄くなってしまいます。
そのためベースイメージはベーシック, プレーンなものを選びましょう。
また、devcontainer.jsonから直接、使用するイメージを使うことができますが、docker composeを使用することをオススメします。
-
Dockerfileにベースイメージを定義
DockerfileARG TAG FROM ubuntu:${TAG}
- alpineなどの軽量イメージを使ってもなんだかんだ不都合があったりするので、私はよく普通のubuntuイメージを使っています。
- このファイルでは、引数でイメージタグを受けるようにしてあります。
- その他、基本的にどの開発環境でも必要なライブラリ等があればインストールしておいてOKです。
- 自分で探したい人はDocker Hubから見つけましょう。 dockerhub
devcontainer用のイメージMicrosoftがdevcontainer用のイメージとして提供しているものがあります。 microsoft/devcontainers
セクションのはじめにも記載したとおり、ここでJavaなど言語固有のイメージを使わないことをオススメします。また、例で記載しているubuntuのイメージよりも、mcr.microsoft.com/devcontainers/base:ubuntu-22.04が適している可能性が高いです。
私はプレーンなubuntuと違うことで不都合があったら嫌だなと思って使っていませんが、どういう違いがあるかは把握していません (無駄な拡張機能とか入れられたら嫌だし...)。 -
Docker Image用のenvファイルを定義
システムの根本的なものだけ定義し、次のcomposeファイルで使いますdevcontainer.envTZ="Asia/Tokyo" LANG="C.UTF-8"
-
docker composeにサービスを定義
ubuntuのイメージからplainというイメージを作っています。
この例は最低限の実装なのでplainイメージには何も有り難みがないです。
2行目のubuntuというサービス名を、後述のdevcontainer.jsonで使いますcompose.yamlservices: ubuntu: build: context: . dockerfile: Dockerfile args: TAG: 22.04 image: plain:22.04 hostname: ubuntu env_file: - devcontainer.env
docker composeのファイル名いつの間にか「compose.yaml」が推奨になっていたらしい Compose file reference
なんと、イメージ選定と定義は以上です。
Javaの環境などについては↓のfeaturesで。
devcontainer.jsonの実装
#これから作るのはこちら。
{
// # devcontainer.json sample
// recommend: Do not sort json
// ## To create image
"name": "mySample",
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
// ## From base image
"dockerComposeFile": ["./compose.yaml"],
"service": "ubuntu",
"runServices": [],
// ## Resources
// warning: Can not use Env
"mounts": [
{
"type": "bind",
"source": "${localWorkspaceFolder}",
"target": "${containerWorkspaceFolder}",
"consistency": "delegated"
}
],
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"username": "developer"
},
"ghcr.io/devcontainers/features/git:1": {}
},
// ## Environment
"remoteUser": "developer",
"containerEnv": {},
"remoteEnv": {},
"portsAttributes": { "80": { "label": "http", "onAutoForward": "silent" } },
// ## Container command
// warning: To use .sh you need mount
// info: key is output stage
"overrideCommand": true,
// ## IDE
"customizations": {
"vscode": {
"extensions": [],
"settings": {}
}
}
}
それぞれ区切って解説します
詳細はリファレンスへ Dev Container metadata reference
To create image
#作成するコンテナの名前 (name) とdevcontainerを開いたときのworkspaceFolderを指定します。
shutdownActionはdevcontainerを終了したときの動作で、デフォルトはstopComposeですが、気になるので明示的に書いています。
{
"name": "mySample",
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
}
From base image
#compose.yamlのパスとその中から使うサービス名を記述します。
runServicesで複数起動もできます。便利そうだけど、活用できてない。
{
"dockerComposeFile": ["./compose.yaml"],
"service": "ubuntu",
"runServices": [],
}
イメージを指定する方法は3種類あります。
それぞれのパターンによって必須パラメータが異なるので、詳細はリファレンスをご確認ください。Scenario specific properties
- devcontainer.jsonに直接イメージを指定
- compose.yamlからサービスを指定 (今回はコレ)
- Dockerfileを指定
Resources
#一番カスタマイズするところです。
{
"mounts": [
{
"type": "bind",
"source": "${localWorkspaceFolder}",
"target": "${containerWorkspaceFolder}",
"consistency": "delegated"
}
],
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"username": "developer"
},
"ghcr.io/devcontainers/features/git:1": {}
},
}
mounts
-
devcontainer内で活用するファイルやフォルダを紐付けましょう
ここで見慣れない変数が出てきました- ${localWorkspaceFolder}: VSCodeで現在開いているウィンドウのルートパスに置き換わります
- ${containerWorkspaceFolder}: workspaceFolderで指定したパスに置き換わります
その他、詳細はリファレンスへ Variables in devcontainer.json
mountの構築段階では環境変数が使えません使える変数も限られているのでちょっと不便です。
-
書くことは普段のDockerのbindやvolumeの内容と変わりません
volumeはcompose.yamlで定義していなくてもdevcontainer.jsonで記述すれば作ってくれますdockerのmountの記述Dockerを普段から活用している皆さんは、short syntaxではなくlong syntaxで書きましょう。 [1]
Docker-docs-ja
.dockerignoreをdevcontainer.jsonの階層に置いておいたりすると、mountでvolumeにしておきたいnode_modulesなどの除外ができます。
ただし、.gitignoreとは若干勝手が違うので、記述は最低限にしましょう。
features
これがdevcontainerの一番の特徴といっても過言ではありません (名前の通り)。
-
このfeaturesを指定することで特定の言語の環境構築や設定を諸々済ませてくれます。
拡張機能も一緒につけてくれるおまけ付きです
(個人的には追加で入れてくる拡張機能は自分で指定したいところですが...)。 -
存在するfeatureはここから探してください。 Features
-
featureによってはパラメータでバージョンなどを指定できます。
-
例えばJava+Node.js+AWS (Terraform) の環境を作る場合。
feature コメント ghcr.io/devcontainers/features/common-utils:2 イメージ内のユーザー作成 (UIDも指定可能)
デフォルトはvscodeユーザーが作られるのですが、なんか嫌なので使っています。ghcr.io/devcontainers/features/git:1 Gitの環境構築 (大体のイメージには標準でありますが、一応入れています) ghcr.io/devcontainers/features/java:1 Javaの環境構築 (もちろんバージョン指定可能) ghcr.io/devcontainers/features/node:1 Node.jsの環境構築 (もちろんバージョン指定可能) ghcr.io/devcontainers/features/aws-cli:1 AWS-CLIが入れられます ( ~/.aws
のbindを忘れずに)ghcr.io/devcontainers/features/terraform:1 Terraformが入れられます Columnイマドキ開発にうってつけの、Docker-in-Dockerのfeatureもあります
ぜひ開発に欲しいものがないか探してみてください
Environment
#{
"remoteUser": "developer",
"containerEnv": {},
"remoteEnv": {},
"portsAttributes": { "80": { "label": "http", "onAutoForward": "silent" } },
}
remoteUser
コンテナで作業する際のユーザー名。
containerEnv
あまり使わないことを推奨。
コンテナ固有の環境変数で、変化するとコンテナの再構築が必要。
remoteEnv
- 接続時のみに反映される環境変数。
- containerEnvよりも使いやすい。
- AWS_DEFAULT_PROFILEなどをセットして使っています。
(もちろんこの環境変数を使う場合は~/.aws
をマウントしましょうね。)
portsAttributes
属性についてはリファレンスを参照してください。 Port attributes
forwardPortsも同じような機能があり、どちらを使った方が良いかはまだ分かっていません。
Container command
#{
"overrideCommand": true,
}
- コンテナのcreate時やattach時に実行するコマンドを指定できます。
- タイミングやライフサイクルによってコマンドが6種類もあります。
- 例えばコンテナを構築した時に、node_modulesのvolumeの
chown
やnpm install
をするが、一度コンテナを作った以降でコンテナにattachする際は実行する必要が無いものなど。
- 定義できるコマンドはこちら (注意点もたくさんあります)。 Lifecycle scripts
Dockerfileやdocker composeを使ってdevcontainerを定義している場合、(今回も該当)
記載しているようにoverrideCommandをtrueにした上で定義する必要があります。
- スクリプトファイルを実行したい場合、それをバインドした時の絶対パスを書かないと実行できない。
WSL環境のパスではダメ。 - スクリプトファイルの実行は
sh $スクリプトファイルパス
。
/bin/bash
やbash
は使えませんでした。
得たノウハウ↓
- .devcontainerをリポジトリに含む場合
- 気にしなくて良い
- .devcontainerをリポジトリに含めない場合
- .devcontainer内にscriptフォルダを用意し、その中に実行したいスクリプトを実装
/workspace/repos
にリポジトリをbind/workspace/script
に.devcontainer/script
をbind- スクリプトフォルダのパスを環境変数にするのも良い
IDE
#{
"customizations": {
"vscode": {
"extensions": [],
"settings": {}
}
}
}
IDE固有の記述です。
これもリファレンスがあります。みなさんの好きなもので使えると良いですね。 Supporting tools and services
今回はVSCodeに限った紹介をします。
extensions
- devcontainer内で自動的にインストールされる拡張機能で、拡張機能のIDを指定します。
- featuresによって勝手にインストールされる拡張機能もありますが、重複は問題ありません。
VSCodeを活用する際に、拡張機能をたくさん使っている方も少なくないと思います。
そんなみなさんが、「devcontainerごとにいちいち拡張機能をリストアップできるかーい」って思うと思います (私は思いました)。
そんなあなたに朗報です。
VSCodeのsettings.jsonにdev.containers.defaultExtensions
の項目を配列型で定義しそこに拡張機能IDを書いておけば、devcontainer.jsonに書かなくても毎回使うような拡張機能はここから勝手にインストールしてくれます。
これを設定すれば、devcontainer.jsonにはそのプロジェクトで必要な最低限の拡張機能を書くだけで良くなります。
settings
- リポジトリの .vscodeに含むもよし、ここで設定するのもよし。
- ユーザーのsettings.jsonも使われますのでご安心を。
- なので私はあんまりここに書かないです
思っていたより長文になってしまってスミマセン。
ここで一息入れましょう。もうあと少しです。
ところで、devcontainerの機能にもCLIがあるんですね。知らなかったです。devcontainer CLI
vscodeのtaskやlaunch.jsonが大好きな人には嬉しい機能ですね (私とか)。
devcontainerの立ち上げ
#おめでとうございます。
ここまでで設定ファイルができあがれば、あとは以下の手順でdevcontainerを立ち上げることができます。
- WSL環境のVSCodeで、
Ctrl+Shift+P
でコマンドパレットを開く Dev Containers: Rebuild and Reopen in Container
を入力しEnter- VSCodeで開いているワークフォルダから .devcontainer/*/devcontainer.jsonを探して、構文チェックが済むとcreateが始まります (複数あると選択ポップアップが出ます)。
- create中にエラーがあってもWSL環境に戻って、エラースタックが表示されるので安心です
- devcontainer起動後のウィンドウで、VSCodeの拡張機能のインストールが終了すれば完了です
- WSL環境に戻る場合は、再度コマンドパレットを開いて、
Dev Containers: Reopen Folder in WSL
を実行します - 次回以降はコマンドパレットから、
Dev Containers: Reopen in Container
を実行します
コンテナの再構築は不要なので初回より断然、時間がかからなくなります - WSL環境のVSCodeにms-azuretools.vscode-docker拡張機能を入れておけば、コンテナやイメージの一覧が出るので嬉しいです
リポジトリにpushじゃあああ
#ここからはdevcontainerの構築とは少し違う話になります。
リポジトリが前提になります。
少し前に出た、.devcontainerフォルダをリポジトリに含む, 含まないも関係ない話です。
devcontainer環境下で開発をしていて、リポジトリの内容を更新したので、いざgit push
...というときにエラーが出ます。
「そうか、.gitconfigのbindをしていないじゃないか」
→ 違います。devcontainerは ~/.gitconfigを自動で複製してくれます。
どうやらHTTPSでgit clone
している場合は問題ないようですが、SSHキーを使用している場合はローカルのssh-agentの起動と、ssh-addが必要なようです。 Sharing git credentials
ssh-agentは起動のたびに秘密鍵の登録が必要でした。
しかしWSL環境で素直にそれをやっていると、ssh-agentがどんどん増えてしまう問題があるようです。
というわけで、そのあたりの導入をします。
Windows側のSSHインストール
#REM sshのバージョンアップ
winget install Microsoft.OpenSSH.Beta
WSL環境でsshキー生成
#KEY_NAME=ed25519
sudo apt update
sudo apt install -y \
openssh-client \
keychain \
socat \
xsel &&
sudo apt clean &&
sudo rm -rf /var/lib/apt/lists/*
# keyがなければ生成
if [ ! -f $HOME/.ssh/id_${KEY_NAME} ]; then
ssh-keygen -t ${KEY_NAME}
echo "clipboard: id_${KEY_NAME}.pub content"
cat $HOME/.ssh/id_${KEY_NAME}.pub | xsel -bi
fi
# 自動エージェント起動の設定
echo \
"if [ -z \"\$SSH_AUTH_SOCK\" ]; then
RUNNING_AGENT=\"\`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'\`\"
if [ \"\$RUNNING_AGENT\" = \"0\" ]; then
ssh-agent -s &> $HOME/.ssh/ssh-agent
fi
eval \`cat $HOME/.ssh/ssh-agent\` > /dev/null
ssh-add $HOME/.ssh/id_${KEY_NAME} 2> /dev/null
fi" \
>$HOME/.bash_profile
echo \
"/usr/bin/keychain -q --nogui $HOME/.ssh/id_${KEY_NAME}
source $HOME/.keychain/$(hostname)-sh" \
>$HOME/.bashrc
軽く説明します。
- 必要なものを
apt install
しています。 ~/.ssh/id_ed25519
がなければ生成します。- 生成された公開鍵はクリップボードに送られています。
- ssh-agentの起動とssh-addを .bash_profileに上書きします。
- 困るようであれば追記に修正してください。
- keychainというもので、devcontainerとの連携を定義し .bashrcに上書きします
- 困るようであれば追記に修正してください。
- 手動で、GitHubのSSHキー登録先にクリップボードの内容を登録してください。
https://github.com/settings/keys - あとはdevcontainer内部で、
ssh -T git@github.com
が実行できれば問題ありません。
おわりに
#お疲れ様でした。これでみんな幸せに開発環境構築できますね。
個人的にポイントだと思うのは、ベースとするDocker Imageを言語固有のものにしないことです。
記事中に書いたとおり、開発環境に複数の言語を入れることになるのであれば、言語固有のものは使わない方が良いと思います。
またお気づきかもしれませんが、今回Docker Imageが3つ登場します。
- ubuntu:22.04
- plain:22.04
- devcontainerのイメージ
compose.yamlを開発環境で共有して一カ所で管理してしまうと、同じイメージを参照して不都合があったので、開発環境ごとに複製したほうがよいです。
まだノウハウなども多くない内容だと思いますので、どんどんオレオレ環境を作っていきましょう。
「『Docker Composeのmountを1行で書くな』校歌を作詞してください」
Copilotくんの作詞↩︎Verse 1: コンテナの海を渡り 我らのコードは進む 1行のmountは避けて 安全な道を選ぶ Chorus: Docker Composeの力 我らの手に宿り 未来のシステム築く 団結の力で Verse 2: 設定ファイルを見つめ 誤りを防ぐために 1行のmountはやめて 明確な道を示す Chorus: Docker Composeの力 我らの手に宿り 未来のシステム築く 団結の力で Verse 3: チームの力を信じ 共に進むこの道 1行のmountは捨てて 新たな未来を描く Chorus: Docker Composeの力 我らの手に宿り 未来のシステム築く 団結の力で