Remote Development with VS Code
A practical guide to remote development with VS Code using SSH, containers, WSL, and GitHub Codespaces for full-featured development on remote machines.
Remote Development with VS Code
Local development has limits. Your laptop might not have enough RAM for the full stack. Your production environment runs Linux but you are on Windows. The codebase lives on a powerful build server. Remote development solves all of these by running VS Code's backend on a remote machine while you interact with the frontend locally.
I switched most of my development to remote setups two years ago. The experience is nearly indistinguishable from local development once configured properly. This guide covers every remote development approach VS Code supports.
Prerequisites
- VS Code installed (v1.80+)
- SSH access to a remote machine (for SSH development)
- Docker installed (for Dev Containers)
- WSL installed (for WSL development, Windows only)
- Basic terminal and SSH knowledge
The Remote Development Architecture
VS Code's remote development works by splitting the editor into two parts:
- UI Frontend — runs locally, handles rendering, keyboard input, and UI
- VS Code Server — runs on the remote machine, handles file access, language features, terminal, and extensions
┌─────────────┐ ┌──────────────────┐
│ Local PC │ │ Remote Machine │
│ │ SSH/ │ │
│ VS Code UI │◄──────►│ VS Code Server │
│ (frontend) │ WSL/ │ Extensions │
│ │ Docker │ Terminal │
│ │ │ File System │
└─────────────┘ └──────────────────┘
Extensions run on the remote side, so language servers, linters, and debuggers have direct access to the remote file system and tools.
Remote SSH Development
The most common setup. Connect to any machine you can SSH into.
Installation
Install the Remote - SSH extension:
ext install ms-vscode-remote.remote-ssh
Configuring SSH Hosts
VS Code reads your SSH config file (~/.ssh/config). Set up your hosts there:
# ~/.ssh/config
Host dev-server
HostName 192.168.1.100
User shane
Port 22
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
Host cloud-vm
HostName dev.example.com
User ubuntu
IdentityFile ~/.ssh/cloud_key
Port 2222
ServerAliveInterval 60
ServerAliveCountMax 3
Host jump-server
HostName 10.0.0.50
User deploy
ProxyJump bastion.example.com
Host gpu-box
HostName ml-server.internal
User researcher
ProxyJump jump-server
LocalForward 8888 localhost:8888
LocalForward 6006 localhost:6006
Connecting
- Press
Ctrl+Shift+P→ "Remote-SSH: Connect to Host" - Select your host from the list
- VS Code opens a new window connected to the remote machine
Or click the green remote indicator in the bottom-left corner.
First Connection Setup
On first connect, VS Code installs its server component on the remote machine. This requires:
- A compatible OS (Linux x64, ARM64, or macOS)
wgetorcurlavailable- 200MB of disk space in `/.vscode-server/`
# If auto-install fails, manually install the server:
# VS Code tells you the commit hash it needs
curl -L "https://update.code.visualstudio.com/commit:<commit-hash>/server-linux-x64/stable" \
-o vscode-server.tar.gz
mkdir -p ~/.vscode-server/bin/<commit-hash>
tar -xzf vscode-server.tar.gz -C ~/.vscode-server/bin/<commit-hash> --strip-components 1
Port Forwarding
Access remote services locally. VS Code auto-detects ports and offers to forward them:
// .vscode/settings.json on the remote
{
"remote.autoForwardPorts": true,
"remote.autoForwardPortsSource": "process"
}
Manual port forwarding via the command palette:
Ctrl+Shift+P→ "Forward a Port"- Enter the port number (e.g., 3000)
- Access
http://localhost:3000in your local browser
Or in the SSH config:
Host dev-server
LocalForward 3000 localhost:3000
LocalForward 5432 localhost:5432
LocalForward 6379 localhost:6379
SSH Configuration for Reliability
Host *
# Keep connections alive
ServerAliveInterval 30
ServerAliveCountMax 5
# Reuse connections (faster reconnection)
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
# Compression (helps on slow connections)
Compression yes
Create the sockets directory:
mkdir -p ~/.ssh/sockets
Workspace Settings for Remote
// .vscode/settings.json
{
// Reduce file watching on large codebases
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/objects/**": true,
"**/dist/**": true
},
// Reduce bandwidth
"search.followSymlinks": false,
// Use remote terminal
"terminal.integrated.defaultProfile.linux": "bash"
}
Dev Containers
Dev Containers package your entire development environment — OS, tools, runtimes, extensions — into a Docker container. Every developer gets the exact same setup.
Installation
ext install ms-vscode-remote.remote-containers
Basic Dev Container Configuration
Create .devcontainer/devcontainer.json in your project:
{
"name": "Node.js Dev Environment",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"forwardPorts": [3000, 5432],
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"humao.rest-client"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly",
"source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly"
],
"remoteUser": "node"
}
Custom Dockerfile Dev Container
For more control, use a custom Dockerfile:
# .devcontainer/Dockerfile
FROM node:20-bullseye
# Install system dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
redis-tools \
jq \
httpie \
&& rm -rf /var/lib/apt/lists/*
# Install global npm packages
RUN npm install -g nodemon jest eslint prettier
# Create non-root user workspace
RUN mkdir -p /workspace && chown node:node /workspace
USER node
WORKDIR /workspace
// .devcontainer/devcontainer.json
{
"name": "Custom Node.js Environment",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install",
"remoteUser": "node",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
}
Docker Compose Dev Containers
For multi-service applications, use Docker Compose:
# .devcontainer/docker-compose.yml
version: "3.8"
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/workspace:cached
- node_modules:/workspace/node_modules
ports:
- "3000:3000"
depends_on:
- db
- redis
environment:
DATABASE_URL: postgres://dev:dev@db:5432/myapp
REDIS_URL: redis://redis:6379
db:
image: postgres:15
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
node_modules:
pgdata:
// .devcontainer/devcontainer.json
{
"name": "Full Stack Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install",
"remoteUser": "node"
}
WSL Development
For Windows developers, WSL provides a native Linux environment.
Installation
ext install ms-vscode-remote.remote-wsl
Opening a Project in WSL
# From WSL terminal
cd /home/shane/projects/my-app
code .
# Or from Windows
code --remote wsl+Ubuntu /home/shane/projects/my-app
WSL Performance Tips
// .vscode/settings.json
{
// Keep files in the Linux filesystem, not /mnt/c/
// Linux FS is much faster than the Windows mount
// Reduce file watchers
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/objects/**": true
},
// Use Linux shell
"terminal.integrated.defaultProfile.linux": "bash"
}
Critical performance rule: Store your code in the Linux filesystem (/home/user/projects/), not in the Windows mount (/mnt/c/Users/). The Windows mount adds significant I/O overhead.
WSL-Specific Git Configuration
# In WSL
git config --global core.autocrlf input
git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"
GitHub Codespaces
Codespaces provide cloud-hosted dev environments with VS Code in the browser or connected to your local VS Code.
Configuration
Codespaces use the same devcontainer.json format:
// .devcontainer/devcontainer.json
{
"name": "Codespace",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [3000],
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"GitHub.copilot"
]
},
"codespaces": {
"openFiles": ["README.md"]
}
}
}
Connecting Local VS Code to Codespaces
# Install the Codespaces extension
# Then: Ctrl+Shift+P -> "Codespaces: Connect to Codespace"
# Or from the command line
gh codespace code --codespace <name>
Codespace Secrets
Set environment variables that are injected into every Codespace:
gh secret set API_KEY --body "your-key-here" --repos owner/repo
gh secret set DATABASE_URL --body "postgres://..." --repos owner/repo
Extension Management for Remote
Some extensions run on the UI side (themes, keybindings), while most run on the remote side (language servers, linters). Control this in your extension:
// In extension's package.json
{
"extensionKind": ["workspace"] // Runs on remote
// or
"extensionKind": ["ui"] // Runs locally
// or
"extensionKind": ["ui", "workspace"] // Can run either side
}
Configure where specific extensions run:
// User settings
{
"remote.extensionKind": {
"esbenp.prettier-vscode": ["workspace"],
"ms-vscode.theme-monokai-pro": ["ui"]
}
}
Complete Working Example: Full Remote Dev Setup
Project structure with all remote configurations:
my-project/
.devcontainer/
devcontainer.json
docker-compose.yml
Dockerfile
.vscode/
settings.json
launch.json
tasks.json
extensions.json
src/
package.json
// .devcontainer/devcontainer.json - Works for Codespaces AND local Dev Containers
{
"name": "My Project Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"forwardPorts": [3000, 5432],
"portsAttributes": {
"3000": { "label": "App", "onAutoForward": "openBrowser" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" }
},
"postCreateCommand": "npm install && npm run db:migrate",
"postStartCommand": "npm run dev",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"humao.rest-client",
"cweijan.vscode-database-client2"
],
"settings": {
"editor.formatOnSave": true,
"terminal.integrated.defaultProfile.linux": "bash",
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/objects/**": true
}
}
}
},
"remoteUser": "node",
"remoteEnv": {
"NODE_ENV": "development"
}
}
// .vscode/settings.json - Shared between local and remote
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"search.exclude": {
"**/node_modules": true,
"**/dist": true
}
}
Common Issues and Troubleshooting
SSH connection times out
The remote machine might have a firewall blocking the SSH port, or the VS Code server cannot download:
Fix: Test basic SSH first with ssh user@host. If that works but VS Code fails, check if the remote machine has internet access (needed to download the server). Use Remote-SSH: Settings to set remote.SSH.connectTimeout higher.
Dev Container builds but extensions fail to install
The container might not have internet access during the post-create phase:
Fix: Check Docker network settings. Use "features" instead of installing tools in the Dockerfile when possible — features handle caching better.
Files are slow to open on SSH
High latency connections make every file open feel sluggish:
Fix: Enable Compression yes in your SSH config. Use files.watcherExclude aggressively. Consider using a Dev Container on the remote machine instead of plain SSH.
WSL performance is terrible
Files stored on the Windows filesystem (/mnt/c/) go through a translation layer:
Fix: Move your project to the Linux filesystem: cp -r /mnt/c/projects/myapp ~/projects/myapp. The performance difference is dramatic — npm install can be 10x faster.
Port forwarding conflicts
A local service is already using the port VS Code wants to forward:
Fix: Change the local port in the Ports panel (click the port number and edit it). Or stop the conflicting local service.
Best Practices
- Store devcontainer.json in your repo. This is how you guarantee every developer has the same environment. It works for Codespaces too.
- Use SSH connection multiplexing.
ControlMasterandControlPersistin your SSH config make reconnections instant. - Keep code on the remote filesystem. For SSH and WSL, do not work on mounted Windows filesystems. The I/O overhead is severe.
- Forward only the ports you need. Auto-forwarding is convenient but can expose services unintentionally. Use
portsAttributesto control behavior. - Pin your Dev Container base image versions.
node:20today is different fromnode:20next month. Use specific tags likenode:20.11-bullseye. - Use postCreateCommand for one-time setup. Use postStartCommand for things that should run every time the container starts.
- Test your devcontainer.json in Codespaces. If it works in Codespaces, it works everywhere.