Devcontainerとは?
こちらの記事が参考になる
この機能を使う目的
- devcontainerはコンテナ内で環境を作成できるので、「作業者の環境にパッケージなどが依存しており、本番環境で動かない!」ということがない
- いちいち
docker run
してdocker attach
してdockerコンテナ内に入る、という作業がなくなる - Git, Githubの設定をホストマシンから引き継いで、Githubにプッシュできる
- Dockerコンテナの起動方法を記述して、開発環境に特化させた環境を記述できる。例えば、開発環境のみに必要なGitや開発依存のnpmパッケージをインストールする記述が可能
現状の運用最適解
-
環境差異をなくすため。devのための環境はできるかぎり作らない。その環境差異はdevcontainerに吸収させる。
-
onCreateCommandでgit, openssh-client、npmの開発依存packageなどのインストール。
Git, openssh-clientをインストールするのは、コンテナ内でGithubにプッシュするため。 -
postStartCommandで開発用の永続プロセス(サーバーなど)を起動
さらに便利にするなら
- 実行中のコンテナでも、サーバーを起動してdevcontainerを立ち上げたい。しかし、Attachする前に起動された永続プロセスが起動の邪魔になる。
→なので、postAttachCommandで以前に実行されたサーバーkillをするコマンド+永続プロセス(サーバーなど)を追記。以下はpythonのuvicornの場合。
"kill $(ps aux | grep 'uvicorn' | grep -v 'grep' | awk '{print $2}') || true && sleep 1 && DEBUG=1 python3 -m debugpy --listen 0.0.0.0:5679 -m uvicorn app:app --host '0.0.0.0' --port 8080 --reload"
- postStartCommandではなく、postAttachCommandでVSCodeのコマンド 「実行中のコンテナにアタッチ」 でも動作する上位互換コマンドだから。
devcontainer.jsonファイルの書き方
- プロジェクトのルートディレクトリに
.devcontainer/devcontainer.json
を用意する - 自分のPCの環境をホスト(
host
)、もしくはローカル(local
)と呼ぶ - コンテナ内の環境を、コンテナ(
Container
)、もしくはリモート(remote
)と呼ぶ
dockerファイルを使用する場合
重要な要素のみ解説します。
name
VSCodeの上部の枠に表示される名前(コンテナ名ではないので注意)
build/image
dockerfileを指定するパターンとimageを指定するパターンがある。buildとimageは並列できない。
"build" : {
"dockerfile": "../Dockerfile",
"context": "./"
"target": "development"
}
もしくは
"image": "YOUR DOCKER IMAGE NAME"
runArgs
docker runで指定する引数を指定できる。-vや-p相当のものは用意されているので、この設定ファイルでカバーしてない引数を指定する。例えば、コンテナの名前を定義する—-name
や終了後コンテナを削除する—-rm
などが候補である。
"runArgs": [
"--name=CONTAINER_NAME",
"--rm"
]
workspaceFolder
“workspaceFolder": "/workspace"
と指定することでワークスペースを変更できる。
workspaceMount
dockerコマンドの-v、docker-composeのvolumesに相当。
mounts
-v引数に相当。volumeやworkspace外のディレクトリをマウントする際に使用する。
sourceはローカルのフォルダを指定。targetはコンテナ内のフォルダを指定。typeはbindだとホストとコンテナ内のフォルダを同期する。volumeだと新規docker volumeを作成する。volumeの場合、sourceはvolumeの名前になる。
“”で括った範囲でカンマ(,)の前後でスペースを入れてはいけない。エラーとなる。
"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind,consistency=cached",
"source=node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
appPort
- ホストマシンからアクセスできるコンテナのポート番号を指定する。
- docker runするときの-pオプションで9000:9000と指定するのと同じ。
containerEnv
コンテナ内の環境変数を指定する。
overrideCommand
true
or false
で指定。devcontainerはdockerのCMDコマンドをデフォルトで上書きする。上書きされたくない場合はfalseを指定する。
initializedCommand
コンテナーの作成中およびその後の起動時を含む、初期化中にホストマシン上で実行される
initializedCommand→onCreateCommand→updateContentComannd→postCreateCommand→postStartCommand→postAttachCommandと続く
onCreateCommand
initializedCommandの後に実行。
- イメージをビルドした後にコンテナ内で実行されるコマンドを書くことができる
- dockerのCMDコマンドがこのコマンドで上書きされるわけではない(それがやりたいなら、overrideCommand: Trueで無効化)
- gitのインストールなど、VSCodeが開く前に実行したいコマンドを記述。postCreateCommand以降でgitやopenssh-clientをインストールすると、gitの設定がホストから引き継がれず、Gitが使用できない。リロードすれば使用できる。
updateCreateCommand
onCreateCommandの後に実行。
Codespaceの設定。ローカルでは使用しなくて良い
postCreateCommand
updateCreateCommandの後に実行。
VSCodeの操作が可能になる。gitのインストールなど、VSCodeが開く前に実行したいコマンドはこれよりも前に記述すべき。
postStartCommand
postCreateCommandの後に実行。
VSCodeの操作が可能になる。gitのインストールなど、VSCodeが開く前に実行したいコマンドはこれよりも前に記述すべき。
前回開いた時のプロセスが止まっていない場合、ポートを占拠し続けて、起動するサーバーが起動できない。
postAttachCommand
postStartCommandの後に実行。
コンテナにAttachするたびに実行されるコマンド。すでに起動されているコンテナにVScodeのコマンドパレット「実行中のコンテナにアタッチ」を実行した場合、唯一実行されるコマンド
前回開いた時のプロセスが止まっていない場合、ポートを占拠し続けて、起動するサーバーが起動できない
shotdownAction
none, stopContainerが指定可能。defaultではstopContainer。
VSCodeを閉じた時にコンテナの扱いを定義する。stopContainerはVSCodeがコンテナから接続を終了した際に、コンテナを停止する。コンテナの削除はされない。
stopContainerを指定をした場合の挙動は以下の通り。
VScode左下の”><”の「フォルダーをローカルから開く」「リモート接続を終了する」を選択すると停止する。またvscodeを❌で閉じても停止する。
コンテナ内で起動した永続プロセス(サーバー起動など)を停止しない場合、コンテナは停止しない
remoteUser
コンテナイメージ内に存在するユーザー名を指定することで、コンテナを開いたときに使用されるユーザーを指定できる。Dockerfileでユーザーの追加記述が必要。
extensions
コンテナ側のVS Codeにインストールされる拡張機能を指定できる。
ただし、開発メンバーによっては、どんな拡張機能を導入しているかまばらなことがあるので、基礎的なものだけを記述することを推奨する。
または、VScodeの設定で、Containers: Default Extensions If Installed LocallyからコンテナをVSCodeで開くたびに導入する拡張機能を指定しても良い。
変数
devcontainer.json内で使用できる変数を紹介する
${localWorkspaceFolder}
ホストマシンのワークスペースディレクトリを表す。.devcontainerが存在するフォルダ。
${containerWorkspaceFolder}
コンテナ内のワークスペースディレクトリを表す
${localEnv:VARIABLE_NAME}
ホストマシンの変数を指定できる。
例:${localEnv:HOME}
→ホストマシンの$HOME変数を表している。
${containerEnv:VARIABLE_NAME}
コンテナの変数を指定できる
例:${containerEnv:HOME}
→コンテナの$HOME変数を表している。
devcontainer.json例
{
"name": "Rag LLM" //VScodeの上部の枠に表示される名前
,"build" : {
"dockerfile": "../Dockerfile",
"target": "development",
"context": "."
} // ビルドするDockerファイルを指定
,"runArgs": [
"--name=rag-llm",
"--platform=linux/arm64"
] // devcontainer.jsonでは定義されていない引数を実装
,"workspaceFolder": "/usr/src/app"
,"workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached" // -v $(pwd):$HOME のようなホストPCとコンテナ内のファイルをマウントする引数
,"mounts":[
"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind,consistency=cached",//AWSの認証情報をリモートにマウント
] // -v引数
,"appPort": ["15999:8080", "5679:5679"] // -p引数
,"containerEnv": {
"PYTHON_ENV": "development"
}, // コンテナ内の環境変数を定義
,"onCreateCommand": "apt install -y curl git openssh-client procps && poetry config virtualenvs.create false && poetry install --only dev --no-root" // gitやopenssh-client, 開発パッケージをインストール
,"postAttachCommand": "kill $(ps aux | grep 'uvicorn' | grep -v 'grep' | awk '{print $2}') || true && sleep 1 && DEBUG=1 python3 -m debugpy --listen 0.0.0.0:5679 -m uvicorn app:app --host '0.0.0.0' --port 8080 --reload" // サーバーが存在したらプロセスキルして、新しいデバッグ用サーバーを設置
,"overrideCommand": true // DockerfileのCMDを無効化
,"shutdownAction": "stopContainer"
,"customizations": {
"vscode": {
"extensions": [ // 拡張機能を定義
"ms-python.python",
"ms-python.autopep8",
"ms-python.debugpy"
]
}
,"settings": { // VScodeの設定を記載
"files.autoSave": "afterDelay" // ファイルのオートセーブ
,"files.watcherExclude": { // ファイルの監視除外
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true
}
}
}
}
docker-compose.ymlを使用する場合
docker-compose.ymlファイルを指定する
["../../docker-compose.yml"]
のようにプロジェクトルート外から参照することもできる。大きいプロジェクトで、機能やレポジトリーがそれぞれ分かれており、それをまとめるためのdocker-compose.ymlがある場合に便利
{
"name": "Dev container",
"dockerComposeFile": "../docker-compose.yml",
"service": "app", // 起動するアプリ
}
詳しくは公式リファレンスへ
Tips
devcontainer.jsonで指定できる〇〇Commandの挙動
VSCodedeのコマンドパレットから開いた時によって、〇〇コマンドの挙動が変わるので、それを元に解説する。
「cmd + shift + p」で起動
「コンテナーでリビルドして再度開く」の挙動
initializedCommand→
onCreateCommand→
updateCreateCommand→
postCreateCommand→
postCreateCommand→
postStartCommand→
postAttachCommand
と順に起動していく
「コンテナーで再度開く」の挙動
「実行中のコンテナにアタッチ」の挙動
postAttachCommandのみ実行される
ホストの.gitconfigが自動でコピーされる
何もしなくてもgitconfigのuser.nameやemailがコピーされる
git config --global user.name "Your Name"
git config --global user.email "your.email@address"
opensshがインストールしてあれば、Githubにpushできる
dockerコンテナ内にopnesshとgitをインストールすることで、ホストのssh-agent経由でGithubの設定を使用できる
RUN apk add --no-cache git openssh
ubuntu
RUN apt install add git openssh
ホストで以下のような設定ファイルが~/.ssh/configにあればOK
Host github.com
HostName github.com
AddKeysToAgent yes
useKeychain yes
IdentityFile ~/.ssh/id_rsa
User git
ssh-agentにキーが追加されているか確認
ssh-add -l
ssh-agentにキーを追加していなければ、追加する。id_rsaは適宜変更。
macの場合はPC起動時に自動でこのコマンドが実行されるが、linuxとwindowsは.bashrcなどに追記が必要
ssh-add --apple-use-keychain ~/.ssh/id_rsa
余談
ssh-agentとは、常にユーザーと一緒に行動して、必要な時に「はいっ!」と秘密鍵を渡してくる存在
クラウド上のサーバーを立ててそこからGithubに接続したい時、サーバーにGithubに入れる秘密鍵を置いておくのは危険なので、ssh-agentを使用する。のような使い方ができる。
opensshをdockerファイル内でインストールせずにdevcontainer内でインストールする
onCreateCommandでインストールすることで、コンテナ内でインストールした時と同じ挙動になる(ssh-agentでホストの設定を受け継いでくれる)
"onCreateCommand": "apk add curl git openssh && npm i && npm i -D",
typescirptでのdevcontainerベストプラクティス
-
workspaceMountでフォルダーを指定
-
mountsでnode_modulesをボリューム化
-
onCreateCommandでボリュームにnodeのパッケージをインストール。ボリュームマウントした場合、Docker内のnode_modulesが上書きされているため。
-
overrideCommand: true, postAttachCommandで開発用サーバーを起動。起動したサーバーはコマンドから停止可能。
{
"name": "Rag LLM",
"build" : {
"dockerfile": "../Dockerfile"
},
"runArgs": [
"--name=rag-llm"
],
"workspaceFolder": "/usr/src/app",
"workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached",
"mounts": [
"source=rag-llm-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
],
"containerEnv": {
"NODE_ENV": "local"
},
"appPort": ["15999:8080"],
"onCreateCommand": "apk add curl git openssh && npm i && npm i -D",
"postAttachCommand": "npm run dev",
"overrideCommand": true
}
Reference
devcontainerの公式リファレンス
devcontainerをわかりやすく解説した記事
ssh-agentの解説