<!--
  canonical-url: https://www.avonture.be/blog/vscode-remote-ssh-proxyjump-devcontainer
  published:     2026-09-04
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# 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.

---
![Open a DevContainer on a Remote Server with VSCode Remote SSH and ProxyJump](/img/v2/ssh-devcontainer.webp)

**TL;DR**

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](/blog/vscode-remote-ssh) covers the extension setup and the SSH config file from scratch.*

**Quick Jump:** [Recommended Approach — ProxyJump from your laptop](#recommended-approach--proxyjump-from-your-laptop) | [Fallback Approach — VSCode on the Windows VM](#fallback-approach--vscode-on-the-windows-vm) | [Connect with VSCode](#connect-with-vscode) | [Open the DevContainer](#open-the-devcontainer)

## What You Get At the End

The architecture, with the recommended approach, looks like this:

```mermaid
graph TD
    A[💻 Your laptop] -->|"ProxyJump — transparent"| B[🖥️ Windows VM]
    B -->|SSH relay| C[🐧 Linux server]
    C -->|1 — fetch files| D[🗄️ SFTP server]
    D -->|files| C
    C -->|2 — POST requests| E[🔐 Secured API]

    style A fill:#4a90d9,color:#fff
    style B fill:#888,color:#fff
    style C fill:#27ae60,color:#fff
    style D fill:#8e44ad,color:#fff
    style E fill:#e67e22,color:#fff
```

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:

```mermaid
flowchart LR
    L["💻 Your laptop"]
    V["🖥️ Windows VM"]
    S["🐧 Linux server"]

    L -->|"Recommended\nProxyJump\nVSCode on laptop"| S
    L -->|"Fallback\nManual session\nVSCode on VM"| V
    V --> S

    style L fill:#4a90d9,color:#fff
    style V fill:#888,color:#fff
    style S fill:#27ae60,color:#fff

```

> **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:

```bash
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal "whoami && hostname"
```

* It prints dev-user and the Linux server hostname → **use the recommended approach**.
* It times out → your laptop cannot reach the VM at all → **use the fallback approach**.
* `Connection refused` on 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:

```bash
$ 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 `ProxyJump` in `~/.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](/blog/github-connect-using-ssh). 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:

```txt
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:

```bash
$ ssh linux-test "whoami && hostname"
```

The first run will look like this:

```bash
$ 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. Type `yes` for each — the fingerprint is stored in `~/.ssh/known_hosts` and 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:

```bash
$ cat ~/.ssh/id_ed25519.pub
```

**Step 1b** — on the VM (PowerShell as Administrator), append the copied key:

```bash
$ 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:

```bash
$ 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:
>
> ```bash
> $ 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"
> ```

**Quick Jump:** [SSH configured — continue with VSCode](#connect-with-vscode)

### 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.

```txt
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:

```bash
$ 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:

```bash
$ 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:

```bash
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](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh)** extension — it provides the Remote Explorer sidebar used to connect to the remote host:

Prerequisite: Remote - SSH

```bash
$ 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:

```bash
$ code --list-extensions | grep remote-ssh
ms-vscode-remote.remote-ssh
```

> **On Powershell, use findstr instead of grep:**
>
> ```bash
> $ 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:
>
> 1. The config file extension. Open Explorer, navigate to `C:\Users\`vm-user`\.ssh\`, and confirm the file is called `config`, not `config.txt`. Windows Explorer hides known extensions by default — enable "File name extensions" in the View menu to be sure.
>
> 2. 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:

```bash
$ hostname
test.example.internal
```

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:

```bash
$ 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

```bash
$ 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:

```bash
$ 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](https://docs.docker.com/engine/install/) 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?
>
> ```bash
> 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:
>
> ```bash
> 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:**

```bash
ssh -J vm-user@windows-vm-ip dev-user@test.example.internal "whoami && hostname"
```

**Interactive shell on the Linux server:**

```bash
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:**

```bash
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:**

```bash
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:

```bash
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](/blog/vscode-remote-ssh) 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.
