VS Code 開発コンテナー(Dev Container)で Nest.js アプリのデバッグ
はじめに
VS Code で Docker コンテナーを開発コンテナーとして使うためには、開発コンテナー機能に対応する Docker コンテナーへ「Visual Studio Code をアタッチする」のが手軽です。開発コンテナーがどういうものか理解して慣れるためには、この方法が便利なのですが、本格的に開発コンテナーを使いたいとなってきたら devcontainer.json
ファイルを用意して、開発コンテナー専用の Docker イメージを使うのが良いです。
ここでは、VS Code 開発コンテナー(Dev Container)のための devcontainer.json
を用意する方法と、開発コンテナーで Nest.js アプリのデバッグをする方法について説明します。
筆者は、単純に devcontainer.json
の説明だけを読むよりは、実際に使用する開発コンテナーについて理解が深まるようにサンプルアプリも動かした方が良いと考えています。そのため、Nest.js の雛形アプリを作成して、そのソースコードを開発コンテナー内で編集したりデバッグ実行する方法と、そのために必要な VS Code のワークスペースファイルについても説明します。
必要な環境
- Docker Desktop (Docker Engine + Docker Compose でも可)
- Visual Studio Code
- Docker 拡張機能
- Dev Containers 拡張機能
- Bash 環境(Git Bash for Windows 含む)
- Node.js v22 環境
- Git が使える環境
動作確認は Ubuntu 22.04 を使っています。基本的に Windows や macOS でも動作する範囲で説明しています。なお、シェルスクリプトは改行コードが LF
となっている必要があります。他のファイルも Docker を使うことを考慮するなら LF
としておいた方が無用なトラブルが起こりにくくなります。Windows や macOS のテキストエディタでファイルを作成する場合は注意してください。
用意するファイルの構成
今回、用意するファイルの構成は次のようになります。
dvc-nestjs-debug/ ... Nest.js をデバッグ可能な開発コンテナー環境
├── .devcontainer/
│ └── devcontainer.json
├── .gitignore
├── README.md
└── app001/ ... dvc-nestjs で使用するサンプルアプリ
├── (略)
├── app001.code-workspace
├── node_modules/
├── (略)
└── tsconfig.json
ここでは、最初に Nest.js という Node.js のフレームワークを使うアプリの雛形を作成してから、それを開発するための VS Code 開発コンテナー環境を用意します。それから、開発コンテナー環境をカスタマイズしやすくするためのファイル構成について説明します。
なお、開発コンテナーに慣れてくると、Docker ホスト側に Node.js の環境を用意せずに開発コンテナーで Node.js 関連の作業ができるようになります。そのため、アプリの雛形を作成するところも開発コンテナーで行うこともできます。とはいえ、実際には既存のアプリ開発用のプロジェクトを開発コンテナーで開発できるように、後から開発コンテナー用のファイルを追加することも多いので、この方法を採用しています。
dvc-nestjs-debug
- Nest.js サンプル app001 の用意
- 開発コンテナー用ファイルの用意
最初に dvc-nestjs-debug フォルダーを作成して、VS Code で開きます。この VS Code の画面でターミナルを開き、そこでコマンドを実行します。
mkdir -p dvc-nestjs-debug
code dvc-nestjs-debug
Nest.js サンプル app001 の用意
最初に Nest.js をデバッグ可能な開発コンテナー環境として dvc-nestjs-debug
を用意します。サンプルで使用するアプリを npm exec
コマンドで作成します。 npm exec
はパッケージ名を指定することで、パッケージに含まれるデフォルトのコマンドを実行することができます。
npm exec <パッケージ名>
ここではパッケージ名として Nest.js 用のコマンドラインツールを提供する @nestjs/cli
を利用することにして、npm exec @nestjs/cli
コマンドを実行します。このコマンドを実行すると、@nestjs/cli
に含まれる nest
コマンドを実行するのと同じ処理が動きます。
ここで、nest
コマンドで新規アプリの作成をするには次のようにします。
nest new <アプリ名>
Node.js のアプリを開発するにあたっては、パッケージ管理システムというものを使って、使用するパッケージの管理をするのですが、その種類には npm
、yarn
といったものがあります。nest
コマンドでは、どれを選択するかを -p
オプションで指定することができます。
nest new -p <使用するパッケージ管理システム> <アプリ名>
この nest
コマンドを npm exec @nestjs/cli
コマンドへ置き換えます。ただし、nest
コマンドのパラメーターを指定するには --
の後に続ける必要があります。また、デフォルトでは対話モードで実行されるので、これを非対話モードにするために npm exec
コマンドに --yes
オプションも指定します。
また、アプリ名は app001
とします。
まとめると、実際に実行するコマンドは次のようになります。
npm exec @nestjs/cli -- new -p npm app001
作成されるアプリには .git
ディレクトリーが用意されて Git リポジトリとなっています。普段、Git を使っていない場合は、git config
で user.email
と user.name
の設定をする必要があります。ここでは使っている環境に影響がないように、app001
のリポジトリにだけ、これらの設定を反映します。
git -C app001 config user.email user001@example.jp
git -C app001 config user.name user001
git
コマンドを使う準備ができたら、リポジトリへ app001
のコードをコミットしておきます。
git -C app001 add .
git -C app001 commit -m "init"
以上で Nest.js サンプル app001 の用意はおしまいです。
開発コンテナー用ファイルの用意
次に開発コンテナー用ファイルを用意します。必要なファイルを手作業で作成することもできますが、ここでは VS Code であらかじめ用意されているテンプレートから作成することにします。
まず、VS Code でコマンドパレットを表示(Ubuntu Desktop なら Ctrl + Shift + P)します。それから、表示される入力欄に次のテキストを入力します。入力しはじめると一覧が表示されます。途中まで入力して、表示される一覧から同じテキストを選択します。
Dev Containers: Add Dev Container Configuration Files
「Dev Containers: Add Dev Container Configuration Files」を選択すると、次の選択肢が表示されます。
ユーザーデータフォルダーに構成を追加する
ワークスペースに構成を追加する
ここでは「ワークスペースに構成を追加する」を選択します。
次に、コンテナー構成テンプレートの選択となります。ここでは一覧の中から Node.js & TypeScript
を選択します。
次に、Node.js version の選択となります。ここでは規定の 22-bookworm
を選択します。
次に、機能の選択となります。ここでは、次の4つの Feature を選択(チェック)してから、「OK」をクリックします。
Common Utilities devcontainers
Docker(docker-outside-of-docker) devcontainers
Git(from source) devcontainers
Git Large File Support(LFS) devcontainers
次のオプションについては「既定値」を選択して「OK」をクリックします。
最後の「オプションのファイル/ディレクトリ」では、何も選択せずに「OK」をクリックします。
以上で、次の内容の .devcontainer/devcontainer.json
ファイルが作成されます。
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/git-lfs:1": {}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
//
で始まる行はコメント行です。有効になっているのは、次のキーです。
キー | 説明 |
---|---|
name | 開発コンテナー名 |
image | 使用する開発コンテナーのベースとなる Docker のイメージ名 |
features | 使用する開発コンテナー用のフィーチャー(機能)名 |
このファイルを見ることで、Node.js & TypeScript
という開発コンテナー名で利用できることがわかります。また、mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
という Microsoft が提供する開発コンテナー用の Docker イメージをベースにして、common-utils:2
、docker-outside-of-docker:1
、git:1
、git-lfs:1
の機能を追加した開発コンテナーを利用できることもわかります。
なお、features は <フィーチャーの識別子>:<バージョン>
で指定するので、使用しているフィーチャーのバージョンもわかります。ここではメジャーバージョンしか指定してませんが、バージョン管理をするときは、マイナーバージョンなどを指定したいときもあります。その場合は、より詳細なバージョン指定をします。また、フィーチャーについての説明は次の URL で確認できます。
開発コンテナー利用の準備
それでは、開発コンテナー利用の準備をしましょう。
使用する予定の mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
という Docker イメージはサイズが大きいので docker image pull
で取得しておきます。
docker image pull mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
次に、 app001
の開発がしやすくなるように、devcontainer.json
の内容を変更します。
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "dvc-nestjs-debug",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {}
},
"remoteUser": "node",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"workspaceFolder": "/workspace",
"mounts": [
{
"source": "dvc-nestjs-debug-node_modules",
"target": "/workspace/app001/node_modules",
"type": "volume"
}
],
"postCreateCommand": "sudo chown -R node app001/node_modules && cd app001 && npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint"
]
}
}
}
追加したキーは次のとおりです。ここで使うものについて、簡単に説明します。
キー | 説明 |
---|---|
remoteUser | 開発コンテナー内で使用するユーザー名 |
workspaceMount | ワークスペースとして利用するフォルダーのマウント情報 |
workspaceFolder | ワークスペースフォルダーのパス |
mounts | 開発コンテナーで使用するフォルダーのマウント情報 |
postCreateCommand | コンテナー作成直後に実行するコマンド |
customizations | カスタマイズ用の設定 |
今回使用する Docker イメージでは、あらかじめ node
というユーザーが用意されています。一般的に開発作業は一般ユーザーで作業するものなので、開発コンテナーでも基本的に root
以外のユーザーを使います。そのため、ここでは node
を指定してます。
なお、このユーザーを追加する機能は common-utils
フィーチャーに含まれていて、node
ユーザーは管理者権限を使ったコマンド実行ができる sudo
コマンドが使えるように設定されています。
開発コンテナーを開いたときに表示するデフォルトのワークスペースを workspaceFolder
で指定しますが、ここでは Docker ホストにある dvc-nestjs-debug
フォルダーを開発コンテナー内の /workspace
へ bind
タイプでマウントして使えるようにしてみます。
ワークスペースのマウントについては、workspaceMount
で指定します。なお、開発コンテナー内で Docker ホストにあるファイルを bind
タイプでマウントするときは ${localWorkspaceFolder}
という VS Code のワークスペースで使える変数を利用します。この値は .devcontainer
フォルダーを含むフォルダーのパスになります。
ここで app001
の開発プロジェクトでは、package.json
があるフォルダーに node_modules
フォルダーを用意して使用するパッケージを置きます。ここには OS 依存のファイルが置かれることもあるので、Docker ホストと開発コンテナー内とでは別に管理できた方が良いということになります。そこで、mounts
を使って、開発コンテナー内の /workspace/app001/node_modules
のパスに対しては、volume
タイプのマウントをすることで対策します。volume
タイプのマウント用には、Docker ボリュームの dvc-nestjs-debug-node_modules
を指定します。このボリュームは開発コンテナー起動時に自動で作成されます。
Docker ボリュームを使うにあたって、それが自動作成される場合は、使用するユーザーがファイル操作できるように調整する必要があります。dvc-nestjs-debug-node_modules
については node
ユーザーが使うためのものなので、chown
コマンドで調整します。この調整用の処理は、コンテナー作成直後に実行すれば良いので、postCreateCommand
で指定します。
開発コンテナーを起動したら、そこで開発をするため、VS Code の設定も同時に指定できると便利です。そのために用意されているのが customizations
です。この中で vscode
のキーと設定用の JSON オブジェクトの指定をすることができます。設定用の JSON オブジェクトには基本設定用の settings
、タスク用の tasks
、実行とデバッグ用の launch
、拡張機能用の extensions
といったものを指定することができます。
例では、VS Code の dbaeumer.vscode-eslint
拡張機能を使うように指定をしてあります。
開発コンテナーの利用
ファイルの準備ができたら、開発コンテナーを利用してみましょう。
ターミナルから code
コマンドで、devcontainer.json
を含む .devcontainer
フォルダーが存在するフォルダー、ここでは dvc-nestjs-debug
フォルダーのパスを指定すると、dvc-nestjs-debug
フォルダーを開いた VS Code が開きます。
code <dvc-nestjs-debug の絶対パス>
そのとき、右下に「コンテナーで再度開く」というボタンを含む通知が表示されます。この「コンテナーで再度開く」をクリックすると、VS Code をアタッチした開発コンテナーが起動します。
この通知が表示されない状態の dvc-nestjs-debug
フォルダーを開いた VS Codeを表示している場合は、VS Code の左下の隅のスペースに >
と <
を組み合わせたマークがあります。これをクリックして表示されるメニューから「コンテナーで再度開く」を選択します。
もしくは、VS Code でコマンドパレットを表示(Ubuntu Desktop なら Ctrl + Shift + P)します。それから、表示される入力欄に次のテキストを入力します。入力しはじめると一覧が表示されます。途中まで入力して、表示される一覧から同じテキストを選択します。
Dev Containers: Open Folder in Container
以上のいずれかの方法で、VS Code をアタッチした開発コンテナーを起動することができます。
VS Code をアタッチした開発コンテナーが起動すると、VS Code のエクスプローラーで開発コンテナー内の /workspace
を開いた画面となります。また、そのフォルダーには Docker ホストの dvc-nestjs-debug
フォルダー内のファイルが表示されているはずです。
次に、app001
の開発をするために必要なワークスペースの設定をするファイルを用意します。この VS Code の画面で /workspace/app001/app001.code-workspace
を次の内容で作成します。
{
"folders": [
{
"path": "."
}
],
"launch": {
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "npm run start:debug の起動",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"start:debug",
],
"console": "integratedTerminal",
},
{
"type": "node",
"request": "attach",
"name": "Debug (attach)",
"address": "localhost",
"port": 9229,
}
]
},
"extensions": {
"recommendations": [
"dbaeumer.vscode-eslint"
]
}
}
このファイルには、app001
の実行とデバッグをするための launch
の指定が含まれています。configurations
に含まれる name
が "npm run start:debug の起動"
となっている設定は、npm run start:debug
コマンドを実行するためのものです。name
が "Debug (attach)"
となっている設定は、別途実行されているデバッガーへ VS Code をアタッチするためのものです。
簡単に "npm run start:debug の起動"
の指定内容について説明しておくと、ここでは "type": "node"
で Node.js 用の設定であること、"request": "launch"
で実行やデバッグのためのプロセスを起動するリクエストを発行すること、"runtimeExecutable": "npm"
で npm
コマンドを実行すること、"console": "integratedTerminal"
でコンソールとして統合ターミナルを使うことを指定しました。なお、runtimeArgs
には、runtimeExecutable
で指定したコマンドへ渡すパラメーターを配列で指定します。
"name": "Debug (attach)"
の指定内容については、"type": "node"
で Node.js 用の設定であること、"request": "attach"
で別途実行されているデバッガーへ VS Code をアタッチすること、"address": "localhost"
と "port": 9229
でデバッガーが待機しているホストとポート番号を指定しました。
また、ここでは、開発コンテナーを使わない場合のことも考慮して、extensions
の recommendations
に dbaeumer.vscode-eslint
を指定しました。こちらはワークスペースで推奨する拡張機能となるので、devcontainer.json
で指定したものとは違ってインストールが済んでいない場合は、別途インストールが必要になります。
ワークスペースファイルが用意できたら使ってみましょう。続けて、同じ VS Code の画面(VS Code をアタッチした開発コンテナー)でターミナルを開き、下記コマンドを実行します。
code /workspace/app001/app001.code-workspace
すると app001.code-workspace
を開いた VS Code の画面が表示されます。次に、ワークスペースで指定した npm run start:debug の起動
を実行してデバッグ実行をしたいところですが、その前に、これが前提としているコマンドが動作するかを確認しておきます。
つまり、npm run start:debug
コマンドの動作を、ターミナルを開いて実行して、確認しておきましょう。
npm run start:debug
デバッガが起動すると、Debugger listening
という行を含むメッセージが表示されます。
[3:29:05 P] Starting compilation in watch mode...
[3:29:06 P] Found 0 errors. Watching for file changes.
Debugger listening on ws://127.0.0.1:9229/97834f8e-7699-4cce-9b02-2d01696806da
For help, see: https://nodejs.org/en/docs/inspector
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [NestFactory] Starting Nest application...
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [InstanceLoader] AppModule dependencies initialized +7ms
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [RoutesResolver] AppController {/}: +4ms
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [NestApplication] Nest application successfully started +2ms
app001.code-workspace
を開いた VS Code の画面で app001/src/app.service.ts
をエディタで開き、行番号の左側をマウスでクリックしてブレイクポイントを設定します。
それから、VS Code のアクティビティバーで「実行とデバッグ」のアイコンをクリックして「実行とデバッグ」の画面にします。その画面の上部にあるドロップボックスから Debug (Attach)
を選択して実行すると、このデバッグ用プロセスへ VS Code からアタッチできます。アタッチすると、npm run start:debug
を実行したターミナルの出力に Debugger attached.
が出力されます。
(略)
[Nest] 2054 - 09/30/2024, 3:29:06 PM LOG [NestApplication] Nest application successfully started +2ms
Debugger attached.
これでデバッグの準備ができました。
デバッグするには、デバッガーで動作しているプロセスの処理を進める必要があります。VS Code をアタッチした開発コンテナーで、npm run start:debug
を起動したターミナルとは別にターミナルを開き、curl
コマンドで http://localhost:3000/ へアクセスします。
実際にコマンドを実行するときは、次のように、出力結果に改行コードを追加するためのオプションである -w'\n'
をつけると見やすくなります。
curl -w'\n' http://localhost:3000/
すると、ブレークポイントで処理が一時停止して、デバッグ機能が動作していることを確認することができます。
VS Code の画面にある □ のアイコンをクリックすると、Debug (attach)
は終了します。それから、npm run start:debug
を起動したターミナルで Ctrl+C
を入力すると、Nest.js アプリのデバッグモードでの実行が終了します。
ここでは、動作確認のために、Nest.js アプリのデバッグモードでの実行と、VS Code のデバッグ実行を別々にしてみましたが、VS Code の「実行とデバッグ」で npm run start:debug の起動
を実行することで、これらを一緒に実行することができます。
VS Code をアタッチした開発コンテナーの「実行とデバッグ」を開いて npm run start:debug の起動
を実行します。これで、Debug (Attach)
を使ったときと同じように Nest.js アプリのデバッグができます。
開発作業が終了したら、VS Code をアタッチした開発コンテナーの画面を閉じます。これで、開発コンテナーは停止します。docker container ls -a
と grep
コマンドを組み合わせると確認できます。
$ docker container ls -a | grep dvc-nestjs-debug
affc41c3c0fb vsc-dvc-nestjs-debug-(略) Exited (0) (略)angry_pike
コンテナーを削除する場合は、docker container rm
コマンドを使います。ここではコンテナーについていた名前の angry_pike
を使っていますが、コンテナー ID を指定することもできます。
docker container rm angry_pike
作成された開発コンテナーのイメージとイメージの ID は下記のように確認できます。
$ docker image ls | grep debug
vsc-dvc-nestjs-debug-88436136cceb86fe3bc0cc0c87fa4c91c506537a71caf2bb8adb74dbbfaf6690-features-uid (略)
vsc-dvc-nestjs-debug-88436136cceb86fe3bc0cc0c87fa4c91c506537a71caf2bb8adb74dbbfaf6690-features (略)
$ docker image ls | grep debug | awk '{print $3}'
435c009ad924
8ed228e4eaf7
利用しなくなった開発コンテナー用のイメージについて削除したいと思うはずです。これらについて削除する場合は docker image rm
コマンドを使います。
$ docker image rm 435c009ad924
Untagged: vsc-dvc-nestjs-debug-88436136cceb86fe3bc0cc0c87fa4c91c506537a71caf2bb8adb74dbbfaf6690-features-uid:latest
Deleted: sha256:435c009ad924b8a0cab797b6f9a5fed2670ce105433b10c3c7c80d219d27a3cc
$ docker image rm 8ed228e4eaf7
Untagged: vsc-dvc-nestjs-debug-88436136cceb86fe3bc0cc0c87fa4c91c506537a71caf2bb8adb74dbbfaf6690-features:latest
Deleted: sha256:8ed228e4eaf78d915a9092d3154ffc129ea3fc30053d7993d7484f342be38146
Discussion