本文へ移動
購読
GUIDE#devtools更新 2026.10.31

VS CodeのRemote SSHとDev Containersでサーバー開発を快適にする

VS Code Serverの仕組み、~/.ssh/configの書き方、拡張機能がどちら側で動くか、ポート転送、devcontainer.jsonの主要キーとチーム共有、接続できない・遅いときの切り分けまで。

level
初級
time
約20分
stack
VS Code 1.144.0 / Remote - SSH 0.128.0 / Dev Containers 0.469.0 / Docker 29.1.3(執筆時点)
author
@ryoma_t
// 前提条件
SSHでLinuxサーバーにログインできること / Dockerの基本操作(Dev Containersの節)
VS CodeでRemote - SSH先のDev Containerを開き、記事のdevcontainer.jsonと、コンテナ内のターミナルでREMOTE_CONTAINERS・ユーザー・Terraformのバージョンを確認した画面
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

VS CodeのRemote - SSHとは、SSHで接続したサーバー上に「VS Code Server」を自動で配置し、手元のVS Codeの画面からサーバー上のファイル編集・ターミナル・デバッグを直接行える拡張機能です。Dev Containersは同じ仕組みをDockerコンテナに対して使い、devcontainer.jsonに書いた開発環境をチーム全員で同一に再現します。

前提は手元のVS Code(Windows / macOS / Linux)と、SSHで入れるLinuxサーバー1台です。Dev Containersの節ではDockerが動く環境(手元でもサーバー上でも可)を使います。

この記事でわかること

  • UI(手元)とVS Code Server(接続先)の分離と、拡張機能がどちらで動くか(UI拡張 / ワークスペース拡張)
  • ~/.ssh/configの書き方(ProxyJump、IdentityFile、ServerAliveInterval)と接続手順
  • ポートフォワーディングの仕組みと、Portsパネル / LocalForwardの使い分け
  • devcontainer.jsonの主要キー(image / features / postCreateCommand / customizations)
  • 接続できない・遅いときの切り分け(~/.vscode-server、files.watcherExclude)

リモート開発の仕組み:VS Code Server

Remote - SSHで接続すると、VS Codeはまず通常のSSHセッションを張り、接続先の~/.vscode-server/に手元のVS Codeと同じコミットのサーバープロセスをダウンロードして起動します。以後、エディタのUIだけが手元で動き、ファイルの読み書き、ターミナル、言語サーバー(補完・定義ジャンプ)、デバッガー、Git操作はすべてサーバー側のプロセスが担当します。

この分離が拡張機能の動作場所を決めます。テーマやキーバインドのようにUIだけに関わる「UI拡張機能」は手元で、PythonやESLintのようにファイルを解析する「ワークスペース拡張機能」は接続先で動きます。接続先ごとに拡張機能を入れ直す必要があるのはこのためで、Pythonの拡張機能は接続先にPythonが入っていて初めて補完が効きます。

要素手元(UI側)接続先(VS Code Server側)
実行されるものウィンドウ、テーマ、キーバインドファイルI/O、ターミナル、言語サーバー、デバッガー、Git
拡張機能UI拡張(テーマ、アイコン)ワークスペース拡張(Python、ESLint、Terraform)
設定の保存先ユーザー設定(settings.json)リモート設定(接続先ごとのsettings.json)
必要なものVS CodeとSSHクライアントSSHサーバー、bash、tar、curlまたはwget(執筆時点の要件は公式ドキュメント参照)

SSH configの書き方と接続

~/.ssh/config
Host homelab
    HostName 192.168.10.20
    User dev
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 30
    ServerAliveCountMax 3

# 踏み台経由。VS Codeはこの設定をそのまま解釈する
Host app-prod
    HostName 10.0.0.5
    User dev
    ProxyJump bastion.example.com

Remote - SSH拡張機能は~/.ssh/config(Windowsは%UserProfile%\.ssh\config)をそのまま読むので、接続情報はVS Code側ではなくこのファイルに集約します。ServerAliveIntervalはNAT機器やWi-Fiで無通信のセッションが切られるのを防ぐ設定、ProxyJumpは踏み台経由の多段接続を1行で表す書き方です。パスワード認証でも繋がりますが、再接続のたびに入力を求められるうえサーバー側の安全性も下がるので、鍵認証にしたうえでサーバー側のパスワード認証を無効化しておきます。

接続は拡張機能「Remote - SSH」(ms-vscode-remote.remote-ssh)を入れ、コマンドパレット(F1)で「Remote-SSH: Connect to Host...」→ configのHostに書いた接続名(上の例ではhomelab)を選ぶだけです。初回は数十秒かけてVS Code Serverが配置され、左下のステータスバーに「SSH: 接続名」(例では「SSH: homelab」)と表示されれば接続済みです。

拡張機能はどこにインストールされるか

接続中に拡張機能ビューを開くと「Local - Installed」と「SSH: 接続名 - Installed」(例では「SSH: Homelab - Installed」。表記は執筆時点のVS Code 1.144.0のもの)の2つのセクションに分かれ、手元にだけ入っているワークスペース拡張機能には「Install in SSH: 接続名」ボタンが表示されます。接続先ごとに入れ直すのが面倒なら、ユーザー設定のremote.SSH.defaultExtensionsに拡張機能IDを列挙しておくと、新しい接続先へ自動で導入されます。

settings.json(ユーザー設定)
{
  "remote.SSH.defaultExtensions": [
    "ms-python.python",
    "redhat.vscode-yaml",
    "hashicorp.terraform"
  ],
  "remote.SSH.remotePlatform": { "homelab": "linux" },
  "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.venv/**": true
  }
}

設定は、ユーザー設定(手元)→ リモート設定(接続先ごと。~/.vscode-server/data/Machine/settings.json)→ ワークスペース設定(.vscode/settings.json)の順に上書きされます。フォーマッタやインタープリターのパスのように接続先の環境に依存する設定は、リモート設定かワークスペース設定に置くと、手元の設定と混ざりません。

ポートフォワーディング

サーバー上でpython3 -m http.server 8000のように起動したサービスは、サーバーのlocalhostにしか待ち受けていなければ手元のブラウザからは見えません。Remote - SSHはssh -L 8000:localhost:8000と同じ転送をSSHセッションの中で行い、手元のhttp://localhost:8000に届くようにします。ターミナルに「http://localhost:8000」のような出力が出ると自動でPortsパネルに追加され、手動なら「Forward a Port」で追加します。

転送されたポートは手元の127.0.0.1にしか開かないため、同じLANの別端末から見せたい場合はSSHの転送ではなく、サーバー側で0.0.0.0に待ち受けたうえでファイアウォールを開けます。恒常的に使う転送は~/.ssh/configにLocalForward 5432 localhost:5432と書いておくと、接続と同時に有効になります。

Dev Containersとは、devcontainer.jsonの書き方

Dev Containersは「開発環境そのものをコンテナにする」拡張機能で、リポジトリ直下の.devcontainer/devcontainer.jsonに書いた定義からコンテナを作り、その中にVS Code Serverを配置して接続します。Remote - SSHとの違いは接続先がサーバーかコンテナかだけで、拡張機能の分離やポート転送の考え方は同じです。手元にDockerがあればそのまま、サーバー上のDockerを使うならRemote - SSHで接続してから「Reopen in Container」する二段構えになります。WindowsではWSL2上のDockerを使うのが基本で、WSL2とDockerで作るWindows開発環境に導入手順があります。

.devcontainer/devcontainer.json
{
  "name": "infra-tools",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
  "features": {
    "ghcr.io/devcontainers/features/python:1": { "version": "3.12" },
    "ghcr.io/devcontainers/features/terraform:1": {},
    "ghcr.io/devcontainers/features/aws-cli:1": {},
    "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
  },
  "forwardPorts": [8000],
  "postCreateCommand": "pipx install --include-deps ansible",
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python", "hashicorp.terraform", "redhat.vscode-yaml"],
      "settings": { "terminal.integrated.defaultProfile.linux": "bash" }
    }
  },
  "remoteUser": "vscode",
  "mounts": [
    "source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind,readonly"
  ]
}

imageの代わりにbuild.dockerfileでDockerfileを指定でき、DBなど複数コンテナが必要ならdockerComposeFileとserviceでCompose定義を流用します。featuresは「ツールのインストーラーを合成する」仕組みで、Dockerfileを書かずにTerraformやAWS CLIを追加できます。postCreateCommandはコンテナ作成後に1回だけ走るので依存関係の導入に向き、customizations.vscode.extensionsに書いた拡張機能はコンテナ側へ自動で入ります。mountsで~/.awsを読み取り専用で渡しているのは、認証情報をイメージに焼き込まないためです。

コマンドパレットの「Dev Containers: Reopen in Container」でビルドと接続が始まります。ビルド済みイメージは再利用され、devcontainer.jsonを変えたときは「Rebuild Container」で作り直します。

チームで開発環境を共有する

.devcontainer/をリポジトリにコミットすると、cloneした全員が同じツールバージョン・同じ拡張機能・同じ設定で作業を始められます。READMEの「環境構築手順」がdevcontainer.jsonに置き換わる、と考えると分かりやすいです。再現性を保つにはimageのタグをubuntu-24.04のように固定し、featuresにもバージョンを指定します。latestを使うと、人によって環境が違うという元の問題に戻ります。

選択肢向いている場面向いていない場面
Remote - SSH(サーバー直接)既存サーバーの運用作業、GPUや大容量データがサーバー側にある環境をチームで揃えたい(サーバーの状態が人ごとに変わる)
Dev Containers(手元のDocker)チームで環境を統一、プロジェクトごとにツールのバージョンが違う手元のPCが非力、会社PCにDockerを入れられない
Remote - SSH + Dev ContainersサーバーのDockerを使いつつ環境を統一設定の層が増えるため初学者には重い
GitHub Codespaces手元に何も入れたくない、外部協力者に一時的な環境を渡す社内ネットワークのリソースに直接触る必要がある、従量課金を避けたい

おすすめ拡張機能(用途別に少数)

用途拡張機能使いどころ
リモート接続Remote Development(拡張機能パック。執筆時点ではRemote - SSH、Dev Containers、WSL、Remote - Tunnelsの4つを同梱)この記事の全機能
コンテナMicrosoft製のコンテナ拡張機能(執筆時点の名称はContainer Tools。旧Docker拡張機能)イメージ・コンテナの一覧とログ確認
IaCHashiCorp Terraform(hashicorp.terraform)、YAML(redhat.vscode-yaml)HCLの補完・整形、Composeやワークフローのスキーマ検証
言語Python(ms-python.python)など対象言語1つ言語サーバーは接続先で動くので、接続先にランタイムが必要
整形EditorConfig(editorconfig.editorconfig)改行コードとインデントをチームで固定

選び方は「接続先で動くワークスペース拡張機能は必要最小限」です。拡張機能は接続先ごとにプロセスとメモリを消費し、非力なサーバーでは遅さの原因になります。Gitの履歴表示はVS Code標準のGit機能で足りることが多く、GitLensのような高機能な拡張機能は必要になってから入れます。

動作確認

接続先で動いているかどうかは、ステータスバーの表示だけでなくコマンドの出力で確かめます。下の結果は、執筆時点のVS Code 1.144.0とRemote - SSH 0.128.0で接続先に入り、続けて接続先のDockerでDev Containerを開いて記録したものです。出力のnagoyadevlab02は私たちの検証用サーバーのホスト名の例なので、自分のサーバーの名前に読み替えてください。

terminal
# 手元のターミナル:VS Codeを使う前に、素のsshで接続先に入れることを確認
$ ssh homelab hostname
nagoyadevlab02

# 手元のターミナル:VS Codeのバージョンとコミット
$ code --version
1.144.0
2c33b50b16c472efe58357d1d2a8a5414bc23fbf
x64

# Remote - SSHで接続した後、手元のターミナルから拡張機能の配置先を見る
$ ssh homelab 'ls ~/.vscode-server/extensions/'
extensions.json
hashicorp.terraform-2.40.0-linux-x64
ms-python.debugpy-2026.6.0-linux-x64
ms-python.python-2026.10.0-linux-x64
ms-python.vscode-pylance-2026.4.1
ms-python.vscode-python-envs-1.40.0-linux-x64
redhat.vscode-yaml-2.0.0

# VS Code内のターミナル(接続先で動いている)
$ hostname && echo "$TERM_PROGRAM" && ls ~/.vscode-server/cli/servers/
nagoyadevlab02
vscode
Stable-2c33b50b16c472efe58357d1d2a8a5414bc23fbf  lru.json

# Dev Container内のターミナル
$ echo "$REMOTE_CONTAINERS" && whoami && terraform version | head -n 1
true
vscode
Terraform v1.17.1

最初のssh homelab hostnameはVS Codeを通さない確認で、ここで失敗するならRemote - SSHも同じ理由で失敗します。接続後の拡張機能の一覧には、remote.SSH.defaultExtensionsに書いた3つのほかに、ms-python.pythonと一緒に入ったms-python.vscode-pylance、ms-python.debugpy、ms-python.vscode-python-envsが並びます。

VS Code内のターミナルでは、TERM_PROGRAMのvscodeが統合ターミナルであることを、hostnameがそのシェルが接続先で動いていることを示します。~/.vscode-server/cli/servers/のディレクトリ名はStable-の後ろが手元のcode --versionの2行目(コミットID)と一致しており、手元と同じビルドのサーバーが配置されたことが分かります。ターミナルの先頭に「This shell is running on your local machine, NOT on the connected remote machine」と出ていたら、開いたのは手元のシェルで、hostnameは手元の名前を返します。

最後のDev Container内のターミナルでREMOTE_CONTAINERSがtrue、ユーザーがremoteUserに書いたvscodeになり、terraformがfeatureで入ったバージョンを返せば、devcontainer.jsonどおりのコンテナに入っています。接続先のDockerを使ったため、ステータスバーの表示は「Dev Container: infra-tools @ homelab」でした。featureのビルドは検証環境で初回347秒かかり、同じ定義で開き直した2回目はキャッシュが効いて1秒未満で終わりました。手元がWindows・macOSの場合の動作は、この環境では試していません。

つまずきポイント

症状・エラーメッセージ原因対処
「Could not establish connection to "接続名"」ssh自体が通っていない。Windowsでは「Permissions for '…id_ed25519' are too open」で鍵が拒否されることが多いまずssh -v 接続名で原因を見る。鍵ファイルのアクセス権は自分だけに絞る(Windowsはプロパティ→セキュリティで継承を無効化)
「Downloading VS Code Server」で止まる接続先からインターネットに出られない(閉域網、プロキシ)ユーザー設定でremote.SSH.localServerDownloadをalwaysにし、手元でダウンロードしてSSH経由で転送させる
接続はできるが操作が重い、サーバーのCPUが高止まりファイル監視がnode_modulesや.venvまで見ている、または拡張機能が多すぎるfiles.watcherExcludeを設定し、不要なワークスペース拡張機能を無効化。「Remote-SSH: Kill VS Code Server on Host」で一度プロセスを落とす
Dev Containersが始まらず、ログに「failed to connect to the docker API at unix:///var/run/docker.sock」「connect: no such file or directory」Dockerを動かすマシン(Remote - SSH経由なら接続先)でDockerデーモンが起動していない。ユーザーがdockerグループに入っていない場合は、docker CLIが「permission denied while trying to connect to the docker API」を返す接続先でdocker psが通るか確認。デーモンが止まっていれば起動し、permission deniedならユーザーをdockerグループへ追加して再ログイン
Dev Containerが起動せず、ログに「invalid mount config for type "bind": bind source path does not exist: /home/ユーザー名/.aws」mountsのsource(この記事の例では~/.aws)が、Dockerを動かしているマシン(Remote - SSH経由なら接続先)に存在しない。featureのビルドが終わった後のdocker runで止まる接続先でmkdir -p ~/.awsを実行してから「Dev Containers: Reopen in Container」をやり直す。AWSを使わないならmountsの行を消す

VS Codeを更新するたびに、接続先の~/.vscode-server/cli/servers/には新しいバージョンのサーバーがStable-コミットIDという名前で追加されます(執筆時点の配置)。ディスクの小さなVPSで「No space left on device」が出たら、接続先でdu -sh ~/.vscode-serverを確認し、使っていないバージョンを削除してください。

Dev Containerのmountsやdocker-outside-of-dockerは、コンテナの中から手元の認証情報やDockerソケットに触れられるということです。信頼できないリポジトリのdevcontainer.jsonをそのまま開くと、postCreateCommandが任意のコマンドを実行できるため、VS Codeのワークスペース信頼の確認で「信頼しない」を選び、中身を読んでから開いてください。

よくある質問

Remote SSHはサーバーに何をインストールしますか?

接続したユーザーのホームディレクトリ配下(~/.vscode-server/)にVS Code Serverの本体と拡張機能を置き、root権限やシステム領域への変更はありません。削除したいときはこのディレクトリを消します。前提として接続先にbash、tar、curlまたはwgetが必要で、対応ディストリビューションの条件は執筆時点の公式ドキュメントで確認してください。

Dev Containerは本番と同じ環境にできますか?

ランタイムのバージョンやOSは揃えられますが、完全に同一にはなりません。Dev Containerには開発ツールやvscodeユーザーが追加されるためです。近づける方法は、本番と同じベースイメージをbuild.dockerfileで指定し、マルチステージビルドのtargetで開発用ステージを分けることです。本番の挙動確認は本番用イメージで行います。

GitHub Codespacesとの違いは?

Codespacesは同じdevcontainer.jsonの仕様を使い、GitHubのクラウドでコンテナを動かすサービスです。Dockerが不要でブラウザだけで使えますが、利用時間とストレージに応じた従量課金があり(条件は公式ページ参照)、社内LANのリソースには直接届きません。社内サーバーでDev Containersを動かす方が、費用とネットワークの面で情シス用途に合います。

まとめ

Remote - SSHは「UIは手元、処理は接続先」という分離で成り立っているので、接続情報は~/.ssh/configに、環境依存の設定はリモート設定に、ワークスペース拡張機能は必要最小限に、という置き場所の整理がそのまま快適さになります。Dev Containersは同じ仕組みをコンテナに向けたもので、devcontainer.jsonをコミットすれば環境構築手順そのものをチームで共有できます。まず素のsshが通ることを確認し、遅ければfiles.watcherExcludeと拡張機能の棚卸しから手を付けてください。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。