Open a DevContainer on a Remote Server with VSCode Remote SSH and ProxyJump
Chain VSCode Remote SSH, ProxyJump and Dev Containers to open a devcontainer that runs directly on a remote Linux server — so your scripts can reach services that are only accessible from that server's network.
This article is part of the SSH - From your first key to remote development series:
- GitHub - Connect your account using SSH and start to work with git@ protocol
- Docker secrets - Using your SSH key during the build process
- Using sftp on the command line, with or without a proxy
- SSH - Autosuggestions with ZSH
- SSH - Launch a terminal on your session without having to authenticate yourself
- How to connect to your hosting server using SSH
- SSH Remote development with VSCode
- Open a DevContainer on a Remote Server with VSCode Remote SSH and ProxyJump
- SSH Config Generator: Wildcard Stanzas, Aliases, and Automated Logins

VSCode's Remote - SSH and Dev Containers extensions chain together: connect to a remote Linux server over SSH, then reopen the project inside a devcontainer running on that server. The container inherits the server's network privileges, so any service only reachable from that server — a private SFTP endpoint, a secured API — becomes reachable from your terminal. Two paths to get there: ProxyJump from your laptop (one click, transparent), or VSCode on an intermediate Windows VM (fallback if direct SSH isn't available).
I needed to write a Bash script that fetches files from an SFTP server and sends POST requests to a secured API. Simple enough — except both sit on a private network segment only reachable from a specific Linux server. That Linux server itself is only reachable from a Windows VM at the office. My laptop, at the end of the chain, cannot see either service directly.
Without the technique below, the complex workflow is: open a terminal on the VM, SSH to the Linux server, edit the script with nano, run it, copy-paste errors back to my editor. Not great.
Or I can skip the VM entirely and work straight from my laptop. This article shows both paths — and the direct-from-laptop route is simpler than it sounds.
With VSCode Remote SSH chained to Dev Containers, the workflow becomes: open VSCode, connect once, and work exactly as I would locally — full editor, full devcontainer, and the SFTP server and API are reachable from the integrated terminal. There are two ways to set this up, and the article covers both.
Prerequisite: you should already be comfortable with the basics of VSCode Remote SSH. If not, SSH Remote development with VSCode covers the extension setup and the SSH config file from scratch.
What You Get At the End
The architecture, with the recommended approach, looks like this:
VSCode runs on your laptop and connects to the Linux server in one click — the Windows VM is just a relay in the SSH config, invisible during normal use. The devcontainer runs on the Linux server, so it inherits the server's network: the SFTP server and the secured API are reachable from inside the container, even though they are invisible from your laptop.
Which Approach Works for You?
Two situations, two paths:
SSH server on the Windows VM
The recommended approach requires a SSH server on the Windows VM. If you're admin on the VM; it's just a command to run to install the SSH server.
To check if the recommended approach is available, run this from your laptop terminal:
- It prints
dev-userand the Linux server hostname → use the recommended approach. - It times out → your laptop cannot reach the VM at all → use the fallback approach.
Connection refusedon port 22 → the VM is reachable but OpenSSH Server is not installed → see the fix below.
The second line is the output of hostname on the remote machine — it reflects the name set in /etc/hostname, which may differ from the address you used to connect. That is normal; what matters is that the command returned output rather than timing out or being refused.
Fix: install OpenSSH Server on the Windows VM (requires admin rights)
Connection refused means the network path works — only the SSH service is missing. If you have admin rights on the VM, open a PowerShell as administrator and run:
$ Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 $ Start-Service sshd $ Set-Service -Name sshd -StartupType Automatic
Once done, re-run the test from your laptop — it should now print dev-user and the server hostname. If this is the case, follow the recommended approach.
The key authorization step is optional. The connection works with password authentication too — you will simply be prompted for your VM password each time VSCode connects. For occasional use that is fine; for daily use, setting up the key makes the connection fully silent.
Prefer a graphical install? Open Settings → System → Optional features, search for OpenSSH Server, and install it from there — then run the Start-Service and Set-Service commands above to start and enable it.
Remark If you've trouble when installing the OpenSSH Server (using the CLI or the GUI), as an alternative, you can download the .msi file by hand from https://github.com/PowerShell/Win32-OpenSSH/releases. Once downloaded; double-click on the .msi file to start the installation.
Why It Works
- Two extensions, one chain. Remote - SSH opens a VS Code Server process on the Linux host. Dev Containers then starts a container on that host and connects VSCode to it — the container runs on the server's Docker engine, not your laptop's.
- Network privilege is inherited from the host. The container's network stack lives on the Linux server, so every outbound connection goes out from the server's IP. Services that block your laptop's address let the server through.
- ProxyJump delegates the relay to SSH. With
ProxyJumpin~/.ssh/config, SSH opens a connection to the VM, then tunnels a second SSH connection to the Linux server through it. VSCode sees only one host and one click. - No file transfer. Files live on the server. The editor reads and writes them directly — no
scp, no rsync, no upload step.
Configure SSH
Recommended Approach — ProxyJump from Your Laptop
Prerequisites
Your SSH private key (~/.ssh/id_ed25519) should be on your laptop — if you don't have one yet, this article walks through generating an ed25519 key pair. ProxyJump tunnels the authentication — your laptop's key authenticates to both hosts, so the key never needs to leave your laptop. Password authentication also works if key-based auth is not set up on the windows-vm-ip VM.
Configure SSH on your laptop
Create the C:\Users\your_laptop_user\.ssh\config (Windows; included WSL) or ~/.ssh/config (if you're under Linux/macOS) file to your laptop's:
Host linux-test
Hostname test.example.internal
User dev-user
ProxyJump vm-user@windows-vm-ip
IdentityFile ~/.ssh/id_ed25519
The ProxyJump line tells SSH to reach linux-test by tunneling through the windows-vm-ip VM. From this point on, ssh linux-test from your laptop connects directly to the Linux server — no manual VM step.
Test the full chain
Run the command with the named alias now that the config is in place:
The first run will look like this:
$ ssh -J vm-user@windows-vm-ip dev-user@test.example.internal "whoami && hostname" The authenticity of host 'windows-vm-ip (1.2.3.4)' can't be established. ED25519 key fingerprint is SHA256:AbCdEfGhIjKlMnOpQrStUvWxYz0123456789ABCD. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'windows-vm-ip' (ED25519) to the known hosts. vm-user@windows-vm-ip's password: The authenticity of host 'test.example.internal (<no hostip for proxy command>)' can't be established. ED25519 key fingerprint is SHA256:AbCdEfGhIjKlMnOpQrStUvWxYz0123456789BCDE. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'test.example.internal' (ED25519) to the known hosts. dev-user@test.example.internal's password: dev-user test.example.internal
Two things happen here, both normal and both one-time only:
- Host-key prompts (
The authenticity of host ... can't be established): SSH has never seen these hosts before. Typeyesfor each — the fingerprint is stored in~/.ssh/known_hostsand the prompt never appears again. - Password prompts: you will see one for the VM and one for the Linux server — the connection works either way. To make it fully silent, authorize your public key on each host (see below).
Optional: eliminate password prompts with key-based auth
There are two hops, so two places to authorize your key. Do them in order.
Step 1 — authorize your key on the Windows VM
For admin accounts on Windows, OpenSSH reads a system-wide file — not the per-user ~\.ssh\authorized_keys. The directory C:\ProgramData\ssh\ has restrictive ACLs: even a member of the Administrators group needs a truly elevated PowerShell session (right-click → Run as Administrator). On the VM:
Step 1a — on your laptop, display your public key and copy the entire line:
$ cat ~/.ssh/id_ed25519.pub
Step 1b — on the VM (PowerShell as Administrator), append the copied key:
$ Add-Content C:\ProgramData\ssh\administrators_authorized_keys "ssh-ed25519 AAAA...your-key-here"
Re-run the test — the VM password prompt should be gone. The Linux server will still ask for a password; that is step 2.
Step 2 — authorize your key on the Linux server
With ProxyJump already configured in ~/.ssh/config, ssh-copy-id works transparently — it connects through the VM and copies your key to the Linux server in one command:
$ ssh-copy-id linux-test /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys dev-user@linux-test's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'linux-test'" and check to make sure that only the key(s) you wanted were added.
It asks for the Linux server password one last time, then adds your public key to ~/.ssh/authorized_keys on the server. After that, the full chain is silent — no prompts at all.
PowerShell: no ssh-copy-id available
ssh-copy-id is not available in Windows PowerShell. Run this one-liner instead — with ProxyJump already configured, it routes through the VM automatically:
$ Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh linux-test "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Fallback Approach — VSCode on the Windows VM
Use this path if the recommended approach is not available (SSH server not installed on the windows-vm-ip VM, or the VM is not reachable from your laptop via SSH).
Create the SSH config file on the VM
Open a Powershell session on the windows-vm-ip VM. If the $HOME\.ssh subfolder does not exist yet, create it: New-Item -ItemType Directory -Path "$HOME\.ssh".
Open (or create) C:\Users\vm-user\.ssh\config in Notepad. The file must be called config with no extension — config.txt is silently ignored by every SSH client.
Host linux-test
Hostname test.example.internal
User dev-user
IdentityFile ~/.ssh/id_ed25519
Copy your SSH private key to the VM
The SSH client on the VM needs your private key. Because we can need your public key for later use, copy both manually via Explorer (navigate to your Linux home under \\wsl$\ and copy ~/.ssh/id_ed25519 to C:\Users\vm-user\.ssh\), or run this one-liner in a Powershell session that has access to WSL:
$ wsl sh -c 'cp "$HOME/.ssh/id_ed25519.pub" "/mnt/c/Users/$(cmd.exe /c echo %USERNAME% | tr -d "\r")/.ssh/id_ed25519.pub"'
$ wsl sh -c 'cp "$HOME/.ssh/id_ed25519" "/mnt/c/Users/$(cmd.exe /c echo %USERNAME% | tr -d "\r")/.ssh/id_ed25519"'
Authorize your key on the Linux server
With the public key now in C:\Users\vm-user\.ssh\, push it to the Linux server from PowerShell on the VM:
$ Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh linux-test "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
You will be prompted for your Linux server password once — that is the last time. From now on, ssh linux-test from PowerShell connects without a password prompt. (WSL users: ssh-copy-id linux-test does the same.)
Test the connection from the VM
From Powershell on the VM, confirm the alias resolves and authenticates:
PS C:\Users\vm-user\.ssh> ssh linux-test No updates available to install Last update check: 2026-09-04 00:00:01 Last login: Thu Sep 3 17:41:10 2026 from 10.x.x.x [dev-user@linux-server-01 ~]$ whoami dev-user [dev-user@linux-server-01 ~]$ pwd /home/dev-user [dev-user@linux-server-01 ~]$ exit logout Connection to test.example.internal closed. PS C:\Users\vm-user\.ssh>
Connect with VSCode
SSH is now configured and the test command returned your username — the connection works end to end. Whether you followed the recommended approach or the fallback, you are at the same point: ready to open VSCode and connect to the Linux server.
WSL users — do not use `code .` for this step
If you work exclusively in WSL, your reflex is code . from the terminal. Do not use it here.
When VSCode opens via code . from WSL, it connects to WSL as a remote host. In that mode, the Remote Explorer only shows WSL Targets and Dev Containers — the Remotes (Tunnels/SSH) category never appears, no matter which extensions you install.
Open VSCode the Windows way instead: from the Start menu, the taskbar, or a PowerShell terminal (just code, no arguments). That gives you a local window, and the full Remote Explorer dropdown — including Remotes (Tunnels/SSH) — will be there.
So, from your Windows-side; start a Powershell console and install the Remote - SSH extension — it provides the Remote Explorer sidebar used to connect to the remote host:
Prerequisite: Remote - SSH
$ code --install-extension ms-vscode-remote.remote-ssh Installing extension 'ms-vscode-remote.remote-ssh'... Extension 'ms-vscode-remote.remote-ssh' v0.120.0 was successfully installed.
Verify:
$ code --list-extensions | grep remote-ssh ms-vscode-remote.remote-ssh
On Powershell, use findstr instead of grep
$ code --list-extensions | grep remote-ssh
Open VSCode — on your laptop if you followed the recommended approach, or on the Windows VM if you followed the fallback approach. In the Remote Explorer sidebar, select Remotes (Tunnels/SSH) from the dropdown.
WSL users: once again because it's important; start your Windows Start Menu and run VSCode from there.
Locate linux-test in the list and click the arrow icon next to it to open it in a new window.
VSCode deploys a small server binary on the Linux host the first time — watch the status bar at the bottom left for the Opening Remote... message. Once the host name appears there, you are connected.
Host not visible in the list?
Two things to check:
-
The config file extension. Open Explorer, navigate to
C:\Users\vm-user\.ssh\, and confirm the file is calledconfig, notconfig.txt. Windows Explorer hides known extensions by default — enable "File name extensions" in the View menu to be sure. -
An outdated extension. If the host was visible before and has now disappeared, open the Extensions pane and update Remote - SSH. This has happened at least once after a VS Code update.
You just opened a project on a server your laptop cannot directly reach
VSCode is connected to the Linux server. You can already open files, run terminals, use Git — everything works from your laptop, on files that live on the server. The SFTP endpoint and the secured API are already reachable from the integrated terminal. Congratulations!
The next chapter is optional. It adds a DevContainer on top of this connection — useful if your project requires a specific runtime or isolated environment. If you only need the remote editor, you are done.
Verify the Connection
Open a terminal in VSCode (Terminal → New Terminal) and run:
The hostname matches the Linux server — the terminal runs on the remote machine, not your laptop.
That network context is what makes the rest possible. Services invisible from your laptop but reachable from the server are now accessible from this terminal. If your project connects to an SFTP server, try it right now:
$ sftp -i ~/.ssh/id_ed25519 -P 22 user@sftp.example.internal Connected to sftp.example.internal. sftp>
You are at an sftp> prompt from a terminal inside VSCode on your laptop — the Linux server's network does the routing, invisibly.
Open the DevContainer
Once connected to the remote host, install the Dev Containers extension to open the project inside a container running on that host:
Prerequisite: Dev Containers
$ code --install-extension ms-vscode-remote.remote-containers Installing extension 'ms-vscode-remote.remote-containers'... Extension 'ms-vscode-remote.remote-containers' v0.395.0 was successfully installed.
Verify:
$ code --list-extensions | grep remote-containers ms-vscode-remote.remote-containers
Open the project folder on the Linux server, then press F1 and run Dev Containers: Rebuild and Reopen in Container the first time (or Reopen in Container on subsequent connections).
Docker must be installed on the Linux server
Dev Containers requires Docker on the remote host, not on your laptop or the VM. If you see an error saying Docker is not found, your system administrator needs to install it, or follow the official Docker installation guide for your server's distribution.
After the container starts, your terminal prompt changes to reflect the container environment. From here, the SFTP server and the secured API are reachable — both services that were invisible from your laptop are now on the local network of the container.
Under the Hood (skip this if you just want to use it)
Remote - SSH installs a vscode-server process on the Linux host the first time you connect, reused on subsequent connections. That server handles all editor communication — file reads, terminal I/O, language server protocol — over the SSH tunnel.
Dev Containers then talks to the Docker socket on that remote host. It reads devcontainer.json, builds or pulls the image, and starts the container. VSCode reconnects its editor protocol to the container runtime, leaving the SSH layer underneath unchanged. The result is two nested connections: SSH tunnel → VS Code Server on host → container runtime.
With the recommended approach, there is a third layer: the SSH tunnel itself goes through the VM via ProxyJump. The VS Code Server sees none of this — it only knows it is running on the Linux host. The ProxyJump is entirely at the SSH layer.
The container's bind mounts reference paths on the Linux server, not paths on your laptop or the VM — which is exactly what you want.
That test command is also a complete SSH session
Remember the command from earlier, used to check whether the recommended approach was available?
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal "whoami && hostname"
Drop the quoted command at the end and it becomes a standalone one-liner that opens an interactive SSH session on the Linux server — straight from your laptop, with no SSH config file required:
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal
With the SSH config now in place, ssh linux-test does the same in one word. But the -J form works on any machine without touching ~/.ssh/config first — handy whenever you need a quick session from an unfamiliar workstation.
Bonus - ssh -J One-Liners
All commands below work from any machine without touching ~/.ssh/config — the full chain is encoded in the -J flag.
Quick check — runs a command and exits:
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal "whoami && hostname"
Interactive shell on the Linux server:
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal
For interactive sessions that run a specific program, add -t to allocate a pseudo-terminal. Without it, the remote program starts but shows no prompt — the session looks frozen, but is actually waiting for input silently.
SFTP session on a server only reachable from the Linux host:
ssh -t -J vm-user@windows-vm-ip dev-user@test.example.internal sftp -i ~/.ssh/id_ed25519 user@sftp.example.internal
PostgreSQL session on a database only reachable from the Linux host:
ssh -t -J vm-user@windows-vm-ip dev-user@test.example.internal psql -h db.example.internal -U postgres
Port forwarding — use any GUI tool from your laptop:
For Windows GUI clients (HeidiSQL, DBeaver, TablePlus…) that can't run over SSH directly, port forwarding is the answer. It exposes the remote port as localhost on your machine — the GUI tool never knows it is crossing two SSH hops:
ssh -fNL 5432:db.example.internal:5432 -J vm-user@windows-vm-ip dev-user@test.example.internal
Then connect HeidiSQL (or any client) to Host: localhost, Port: 5432. The tunnel runs silently in the background (-f) with no remote command (-N). Kill the ssh process when you are done.
Conclusion
The combination of VSCode Remote SSH and Dev Containers solves a problem that otherwise requires a clunky multi-step manual workflow: developing code that must run — or reach services that only exist — on a server your laptop cannot directly access.
The recommended approach (ProxyJump) is the cleaner option: once the SSH config is in place, the connection is one click and the Windows VM becomes invisible infrastructure. The fallback approach is the reliable alternative when the VM's SSH server is not available.
SSH Remote development with VSCode is the companion article if you want to start from a simpler case — editing files on a remote host without a devcontainer, with a local Docker container as a safe practice target before touching a real server.
This article is part of the SSH - From your first key to remote development series:
- GitHub - Connect your account using SSH and start to work with git@ protocol
- Docker secrets - Using your SSH key during the build process
- Using sftp on the command line, with or without a proxy
- SSH - Autosuggestions with ZSH
- SSH - Launch a terminal on your session without having to authenticate yourself
- How to connect to your hosting server using SSH
- SSH Remote development with VSCode
- Open a DevContainer on a Remote Server with VSCode Remote SSH and ProxyJump
- SSH Config Generator: Wildcard Stanzas, Aliases, and Automated Logins






