All articles
TutorialsJul 22, 2026 · 15 min read · By The RDP.sh Team

Self-Host code-server on a VPS: VS Code in the Browser

Self-Host code-server on a VPS: VS Code in the Browser

Editing code from a Chromebook, a locked-down work laptop, an iPad, or a friend's machine usually means fighting with tooling that isn't yours. Self-hosting your editor fixes that. Your workspace, extensions, terminal, and Git credentials live on a VPS and follow you to any device with a browser.

code-server is the open-source project that runs Visual Studio Code as a web app. It's the same editor you know - extensions, integrated terminal, settings sync, the works - served over HTTPS from a server you control. It's light enough for a small VPS and pairs perfectly with a remote development workflow.

This guide walks through a production setup: Docker, Caddy for automatic HTTPS, a strong password gate, and a workspace that survives restarts.

code-server exposes a full terminal on your server. Anyone who gets past the login has shell access as the container user. Use a long password and, ideally, keep it behind a VPN or Tailscale. Never run it open on the internet with a weak secret.

TL;DR

  • Install Docker and Docker Compose on a fresh VPS
  • Point a subdomain like code.example.com to your server
  • Run code-server and Caddy behind a single docker-compose.yml
  • Set a long password and generate a hashed secret
  • Open the editor in any browser and install extensions
  • Optionally hide the whole thing behind Tailscale

Total time: about 15 minutes.

What You Need

  • A VPS with at least 1 GB RAM (2 GB recommended if you build projects) running Ubuntu 22.04 or 24.04
  • A domain name you can add DNS records to
  • Ports 80 and 443 open to the internet (required by Let's Encrypt)
  • Root or sudo access

code-server itself idles at around 100-150 MB of RAM. The headroom is for your language servers, compilers, and Node processes.

Step 1: Point a Subdomain at Your VPS

In your DNS provider, create an A record:

code.example.com → YOUR_VPS_IPV4

If you use IPv6, add an AAAA record too. Wait a minute, then verify:

dig +short code.example.com

The output should match your VPS IP. DNS must resolve before Caddy can issue a certificate.

Step 2: Install Docker and Docker Compose

On a fresh Ubuntu box:

sudo apt update sudo apt install -y ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verify:

docker --version docker compose version

Step 3: Open the Firewall

If you use UFW:

sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable

Caddy needs 80 for the ACME HTTP challenge and 443 for HTTPS.

Step 4: Create the Project Directory

sudo mkdir -p /opt/code-server cd /opt/code-server sudo mkdir -p config project caddy-data caddy-config

Two folders matter for backups:

  • config/ holds your code-server settings, installed extensions, and UI state.
  • project/ is your actual code. Point your repos here so nothing lives inside the throwaway container layer.

Step 5: Generate a Hashed Password

You can log in with a plaintext PASSWORD, but a hashed password keeps the secret out of your environment. Generate one with argon2:

sudo apt install -y argon2 echo -n "your-long-random-password" | \ argon2 "$(openssl rand -hex 16)" -e -id -k 65540 -t 3 -p 4

Copy the full $argon2id$... string it prints. You'll paste it into the compose file next. Keep the plaintext password somewhere safe - that's what you type at the login screen.

Step 6: Write the Compose File

Create /opt/code-server/docker-compose.yml:

services: code-server: image: codercom/code-server:latest container_name: code-server restart: unless-stopped environment: HASHED_PASSWORD: "$argon2id$v=19$m=65540,t=3,p=4$PASTE_YOUR_HASH_HERE" DOCKER_USER: coder volumes: - ./config:/home/coder/.config - ./project:/home/coder/project user: "1000:1000" networks: - codenet caddy: image: caddy:2 container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./caddy-data:/data - ./caddy-config:/config networks: - codenet networks: codenet:

A few notes:

  • Paste the full Argon2 hash into HASHED_PASSWORD, including the leading $argon2id$. Wrap it in double quotes.
  • Don't publish code-server's port 8080 on the host. Caddy reaches it over the internal Docker network, so the editor is never directly exposed.
  • The user: "1000:1000" line makes files in project/ owned by your host user, which avoids permission headaches when you git clone or chmod from the terminal.

Step 7: Write the Caddyfile

Create /opt/code-server/Caddyfile:

code.example.com { encode zstd gzip reverse_proxy code-server:8080 }

Caddy requests a Let's Encrypt certificate for code.example.com on first boot. code-server speaks plain HTTP internally and handles WebSockets on the same port, so this one line is all the proxy config you need.

Step 8: Start the Stack

cd /opt/code-server sudo docker compose up -d sudo docker compose logs -f

Watch the Caddy logs until the certificate is issued, then open https://code.example.com. You'll get a password prompt. Type the plaintext password from Step 5 (not the hash), and the familiar VS Code interface loads in your browser.

Open the built-in terminal with Ctrl+` and confirm you're inside the container:

whoami # coder pwd # /home/coder ls project

Step 9: Set Up Your Workspace

Open the project folder from File > Open Folder so it becomes your default workspace. Clone a repo from the integrated terminal:

cd ~/project git clone https://github.com/you/your-app.git

Install extensions the usual way from the Extensions panel. code-server pulls from the Open VSX registry rather than Microsoft's Marketplace, so a handful of proprietary extensions (like the official C# or Pylance builds) aren't available. The vast majority - GitLens, ESLint, Prettier, language packs, themes - are all there.

Your settings and extensions persist in the mounted config/ folder, so a container restart or image update never wipes them.

Step 10: Install Language Toolchains

code-server ships with a minimal base image. Add whatever runtimes you need from the terminal. For example, Node via nvm:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash source ~/.bashrc nvm install --lts

These installs live under /home/coder, which is mapped to your config/ volume, so they survive restarts too. For heavier setups, build your own image FROM codercom/code-server with the toolchains baked in.

Anything you install into the running container that isn't under `/home/coder` disappears on the next `docker compose pull`. Keep persistent tooling under the home directory, or bake it into a custom image.

Step 11: Lock It Down Further

A password gate is the minimum. Two strong additions:

  • Rate-limit logins. code-server has no built-in brute-force protection. Front it with Fail2ban watching the Caddy access log, or put an auth layer like Authelia in front for full 2FA.
  • Hide it from the public internet entirely. The safest option is to not expose it at all. Pair this with our Tailscale guide so code.example.com only resolves and connects over your tailnet. Then even a leaked password is useless to an outsider.

Step 12: Automate Backups

The state you care about is under /opt/code-server/config and /opt/code-server/project. Your code should already live in Git, but settings, extensions, and uncommitted work are worth snapshotting.

Create /usr/local/bin/code-server-backup.sh:

#!/usr/bin/env bash set -euo pipefail BACKUP_DIR="/var/backups/code-server" DATE="$(date +%F)" mkdir -p "$BACKUP_DIR" tar -czf "$BACKUP_DIR/code-server-$DATE.tar.gz" \ -C /opt/code-server config project find "$BACKUP_DIR" -name "code-server-*.tar.gz" -mtime +14 -delete

Make it executable and schedule a daily run:

sudo chmod +x /usr/local/bin/code-server-backup.sh echo "20 3 * * * root /usr/local/bin/code-server-backup.sh" | \ sudo tee /etc/cron.d/code-server-backup

For off-site safety, push the backup directory to object storage on the same schedule - see our restic to S3 guide.

Troubleshooting

Caddy cannot issue a certificate. DNS hasn't propagated, or ports 80/443 are blocked. Check with dig +short code.example.com and your provider firewall.

The editor loads but the page keeps reconnecting. That's a broken WebSocket. Make sure you're reaching the site over https:// through Caddy, not hitting port 8080 directly. Any proxy in front of Caddy must forward Upgrade and Connection headers.

Password is rejected. You pasted the hash into the login form. The prompt wants the plaintext password you hashed in Step 5, not the $argon2id$ string.

Permission denied when cloning into project. The container user doesn't own the folder. Run sudo chown -R 1000:1000 /opt/code-server/project on the host and restart the stack.

An extension won't install. It's probably Marketplace-only. Search Open VSX for an equivalent, or sideload the .vsix from the command palette with Install from VSIX.

Going Further

  • Bake a custom image with your language toolchains so a fresh container is instantly ready.
  • Use the --link feature or a reverse tunnel to reach the editor without opening any ports.
  • Add Settings Sync to mirror your local VS Code config into the browser instance.
  • Give each project its own container if you juggle very different stacks, and route them as app1.code.example.com, app2.code.example.com.

That's it. A full VS Code environment on a VPS means your dev setup is one URL away from any device, and the heavy lifting happens on the server instead of a borrowed laptop.


Need a VPS with the headroom to compile and run your projects? Our Linux plans include fast NVMe storage and IPv6 out of the box. See the options.