Self-Hosting Code-Server: A Complete Guide to Running VS Code in the Browser

Self-Hosting Code-Server: A Complete Guide to Running VS Code in the Browser

Code-server is an open-source project that runs VS Code in the browser so your editor, terminal, and files live on a server you control. This guide walks through a practical Docker Compose deployment on Ubuntu 24.04 LTS with the official codercom image, hashed password auth, Nginx, HTTPS, backups, upgrades, and safe day-two operations.

· Updated · 5 min read #self-hosted #open-source #deployment #docker #vps #ide #vscode #node-js #code-server

 Code-server self-hosted VS Code overview

Caption: Code-server puts a VS Code workbench in the browser while the workspace, terminal, extensions, and compute stay on your VPS.

Introduction

Code-server is an open-source project from Coder that runs Visual Studio Code in a browser. The editor, file tree, integrated terminal, Git tools, and extension host execute on a machine you operate. Clients only need a modern browser. That is useful when you want a consistent Linux development environment from a laptop, tablet, or locked-down workstation without copying source trees onto every device.

Self-hosting code-server is a good fit when project files should stay on your VPS or homelab host. Agencies can keep client repositories on a hardened server and still get a full IDE. Homelab administrators can edit Compose files and application code next to the services those files deploy. Traveling developers can leave compiles and test suites on a VPS while a Chromebook only renders the UI.

This is not GitHub Codespaces, Microsoft's code serve-web, or Coder's team product. Code-server is a patched VS Code workbench with password authentication, a built-in port proxy, disk-backed settings, and a self-contained web UI. Coder provisions full remote workspaces with Terraform. Code-server is the smaller editor for individuals and small teams.

This guide installs code-server on Ubuntu 24.04 LTS with Docker Compose using the official codercom/code-server image, persistent host directories, hashed password authentication, Nginx, and Let's Encrypt certificates. The example publishes the editor at https://code.example.com, maps container port 8080 only to 127.0.0.1:8080, stores files under /opt/code-server, and keeps TLS termination on the host. Replace the example domain, email address, password hash, and firewall policy before using these commands in production.

Why Choose Code-Server?

  • Familiar VS Code workbench: The editing model, command palette, Git pane, and terminal match desktop VS Code closely enough for daily work.
  • Files stay on your server: The workspace is a directory on the VPS. Laptops become thin clients instead of another copy of production secrets.
  • Official Docker image: Coder publishes codercom/code-server for amd64 and arm64, with documented bind mounts for config, user data, and the project directory.
  • Password authentication: A randomly generated password or an Argon2 hashed password protects the UI. Login attempts are rate limited.
  • Built-in port proxy: Preview a local app through /proxy/<port> or a proxy-domain subdomain without opening extra public ports.
  • HTTPS-friendly reverse proxy: Official Caddy and Nginx examples exist, including the WebSocket headers VS Code needs.
  • Small operational footprint: One container, a few host directories, and a reverse proxy are enough for a personal IDE.

Treat code-server as a remote workstation, not a brochure site. Anyone who logs in can open a terminal, read mounted files, install extensions, and run commands as the container user. A leaked password is equivalent to a leaked shell on that workspace.

Prerequisites

Hardware Recommendations:

  • 2 vCPU and 2 GB RAM for light editing and small Node or PHP projects
  • 2-4 vCPU and 4-8 GB RAM if you run language servers, builds, or test suites in the workspace
  • SSD storage for the project tree, extension cache, and VS Code user data
  • Off-server backup storage such as another VPS, NAS, or encrypted object storage

Software and Accounts:

  • Ubuntu 24.04 LTS server with sudo access
  • A domain such as code.example.com with a DNS A or AAAA record
  • Docker Engine with the Docker Compose v2 plugin
  • Nginx, Certbot, OpenSSL, curl, ufw, jq, git, and rsync
  • A password manager for the editor password and backup keys
  • Optional wildcard DNS (*.code.example.com) for subdomain port proxying

Security Notes:

  • Do not publish container port 8080 on 0.0.0.0. Bind it to localhost and put Nginx in front.
  • Use HTTPS before treating the instance as a daily editor. Web views and service workers need a secure context.
  • Prefer hashed-password over a plaintext password in config files.
  • Mount only the project directories you intend to edit. Do not mount /, /root, or backup credential paths.
  • Keep /opt/code-server/.env, config.yaml, workspace files, and backups private.
  • Back up config, user data, and the workspace before image upgrades.

Start with a patched host and a tight firewall:

sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg openssl ufw nginx certbot python3-certbot-nginx jq git rsync

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Do not open port 8080 in UFW. Nginx will reach code-server over the loopback interface.

Installation Guide

This deployment uses the official codercom/code-server:4.132.0 image. That tag matches the current upstream release, which bundles VS Code 1.132.0. Inside the container, code-server listens on port 8080. Configuration lives under /home/coder/.config/code-server/config.yaml. Extensions and user data live under /home/coder/.local. The project directory is mounted at /home/coder/project.

1. Install Docker Engine

Install Docker and confirm that Compose v2 is available:

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker

docker --version
docker compose version

These examples use the modern docker compose plugin syntax. If docker compose version fails, fix the Docker installation before continuing.

2. Create the Project Directory

Create a private working directory for Compose files, config, user data, the workspace, and backups:

sudo mkdir -p /opt/code-server/{config,local,project,backups}
sudo chown -R "$USER":"$USER" /opt/code-server
chmod 700 /opt/code-server
cd /opt/code-server

The official Docker example forwards your host UID and GID into the container so files created in the workspace remain owned by your user on the host. This guide uses PUID and PGID from id -u and id -g. Adjust those values if a dedicated service user owns /opt/code-server.

3. Generate a Hashed Password and Environment File

Create a strong password, hash it with Argon2, and store Compose variables. The hash command comes from the official FAQ:

cd /opt/code-server

CODE_SERVER_PASSWORD="$(openssl rand -base64 24 | tr -d '\n')"
CODE_SERVER_HASH="$(printf '%s' "$CODE_SERVER_PASSWORD" | npx --yes argon2-cli -e)"

cat > .env <<EOF
CODE_SERVER_IMAGE=codercom/code-server:4.132.0
CODE_SERVER_DOMAIN=code.example.com
PUID=$(id -u)
PGID=$(id -g)
DOCKER_USER=$USER
EOF

chmod 600 .env
printf 'Editor password (store in your password manager):\n%s\n' "$CODE_SERVER_PASSWORD"
printf 'Argon2 hash:\n%s\n' "$CODE_SERVER_HASH"

Keep the plaintext password only in your password manager. The hash goes into config.yaml. If npx is not available on the host, generate the hash on a trusted workstation with the same argon2-cli command, then paste the hash into the config file.

4. Write the code-server Configuration

Create config/code-server/config.yaml before the first start. Inside the container the process must listen on 0.0.0.0:8080 so Docker's localhost publish on the host can reach it. The host mapping still binds only to 127.0.0.1.

cd /opt/code-server
mkdir -p config/code-server

# Replace HASH_FROM_STEP_3 with the argon2 hash printed above.
cat > config/code-server/config.yaml <<'EOF'
bind-addr: 0.0.0.0:8080
auth: password
hashed-password: "HASH_FROM_STEP_3"
cert: false
disable-telemetry: true
EOF

chmod 600 config/code-server/config.yaml

hashed-password takes precedence over password. Do not commit this file to a public repository. If you later put the hash in Compose as HASHED_PASSWORD, double every $ as $$ because Compose treats $ as interpolation.

5. Write Docker Compose Configuration

Create docker-compose.yml with the official image, UID forwarding, localhost-only publishing, and persistent mounts:

services:
  code-server:
    image: ${CODE_SERVER_IMAGE}
    container_name: code-server
    restart: unless-stopped
    user: "${PUID}:${PGID}"
    environment:
      DOCKER_USER: ${DOCKER_USER}
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - ./config:/home/coder/.config
      - ./local:/home/coder/.local
      - ./project:/home/coder/project

 Code-server Docker Compose stack

Caption: A single-server code-server deployment keeps the editor on localhost port 8080 while Nginx terminates public HTTPS.

This follows the official docker run example: config under .config, user data under .local, the workspace at /home/coder/project, UID/GID forwarding, and DOCKER_USER. The only production change is publishing 8080 on loopback instead of all interfaces.

Validate and start the stack:

cd /opt/code-server
docker compose config
docker compose pull
docker compose up -d

docker compose ps
docker compose logs --tail=80 code-server
curl -sS http://127.0.0.1:8080/healthz

/healthz is unauthenticated and should return JSON with "status": "alive". If the container exits immediately, check ownership of /opt/code-server/config and /opt/code-server/local, then confirm PUID/PGID match those directories.

6. Put Nginx and Let's Encrypt in Front

The official guide uses Nginx with WebSocket upgrade headers and Certbot. Create the site file, then request a certificate:

sudo tee /etc/nginx/sites-available/code-server >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name code.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        proxy_set_header Accept-Encoding gzip;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/code-server /etc/nginx/sites-enabled/code-server
sudo nginx -t
sudo systemctl reload nginx

sudo certbot --non-interactive --redirect --agree-tos --nginx -d code.example.com -m you@example.com

Replace code.example.com and you@example.com. After Certbot succeeds, open https://code.example.com and sign in with the password from your password manager.

Configuration

Code-server reads ~/.config/code-server/config.yaml plus flags and environment variables. Review the live file after the container has started:

cat /opt/code-server/config/code-server/config.yaml
docker compose exec code-server cat /home/coder/.config/code-server/config.yaml

 Code-server security configuration map

Caption: A production code-server configuration should align localhost binding, hashed password auth, HTTPS WebSockets, disabled telemetry, and proxy boundaries.

Useful production settings:

bind-addr: 0.0.0.0:8080
auth: password
hashed-password: "$argon2i$v=19$m=4096,t=3,p=1$replace-with-your-own-hash"
cert: false
disable-telemetry: true

Restart after config changes:

cd /opt/code-server
docker compose restart code-server
docker compose logs --tail=80 code-server

To change the password, generate a new Argon2 hash, replace hashed-password, and restart. Do not leave a plaintext password field beside a hash unless you intend the hash to win.

If you want forwarded ports on subdomains such as 3000.code.example.com, add a wildcard DNS record and set:

proxy-domain: code.example.com

The reverse proxy must forward the Host header. The official Nginx example already sets Host $http_host. Without a wildcard certificate, use the path proxy instead: https://code.example.com/proxy/3000/.

Optional environment variables from the FAQ:

    environment:
      DOCKER_USER: ${DOCKER_USER}
      CS_DISABLE_GETTING_STARTED_OVERRIDE: "true"
      LOG_LEVEL: info

Leave the built-in proxy enabled if you preview web apps from the Ports panel. Do not mount /var/run/docker.sock until you accept that the editor can talk to the host Docker daemon. Skip that until the HTTPS editor is stable.

Usage

Start with a small checklist before using this as your daily IDE:

  1. Open https://code.example.com and log in.
  2. Use File → Open Folder and choose /home/coder/project.
  3. Create hello.txt, save it, and confirm the file appears in /opt/code-server/project on the host.
  4. Open the integrated terminal and run pwd, id, and git --version.
  5. Install one extension from the editor or CLI.
  6. Start a tiny HTTP server in the workspace and open it through /proxy/<port>.
  7. Confirm curl -sS https://code.example.com/healthz still returns alive.

 Code-server first login and workspace flow

Caption: A first-run checklist covers login, the project folder, extensions, the integrated terminal, a proxied preview port, and /healthz.

Install extensions with the code-server CLI. The default marketplace is Open VSX, not the Microsoft Visual Studio Marketplace:

docker compose exec code-server code-server --install-extension gitlens.gitlens
docker compose exec code-server code-server --list-extensions

You can also pass a downloaded .vsix file. Some Microsoft-branded extensions are unavailable or limited compared with desktop VS Code. If you need the official Microsoft marketplace, the FAQ points to VS Code web (code serve-web) as the alternative.

Clone a repository into the mounted workspace from the integrated terminal or from the host:

cd /opt/code-server/project
git clone git@github.com:example/app.git

Because /opt/code-server/project is bind-mounted, Git objects and node_modules consume host disk. Watch disk usage before large installs.

To preview an app on port 3000 after it is listening inside the container, browse to https://code.example.com/proxy/3000/. Use /absproxy/3000 for apps that require the full path, such as some Create React App or Vue dev servers. Subdomain proxying with proxy-domain is cleaner when you can add wildcard DNS and TLS.

Screenshots and Visuals

The visuals in this guide are original diagrams rather than copied product screenshots. They show the deployment model you are operating: a browser client, Nginx with TLS, code-server on localhost 8080, persistent config and workspace mounts, and a backup path that includes the password hash and project files.

 Code-server backup and restore workflow

Caption: Useful code-server backups capture config.yaml, user data, the workspace, an off-server copy, a restore drill, and a planned upgrade window.

Troubleshooting

  • Browser cannot connect after Compose starts: Confirm curl -sS http://127.0.0.1:8080/healthz works on the server. If it fails, inspect docker compose logs code-server and bind-addr.
  • Login loop or immediate disconnect: Check that Nginx sets Upgrade and Connection upgrade. VS Code needs WebSockets.
  • Web views fail with a ServiceWorker SecurityError: You are not in a secure context. Use a real hostname and Let's Encrypt certificate, not a raw public IP.
  • Permission denied on the workspace: Align PUID/PGID with ls -ln /opt/code-server. The official image example runs as your host UID.
  • Password rejected after restart: hashed-password must be the Argon2 string for the password you type. Compose must not swallow $ if you used HASHED_PASSWORD.
  • Editor shows the wrong public URL: Set proxy_set_header Host $http_host; as in the official Nginx snippet.
  • Extensions missing after recreate: Confirm ./local is still mounted at /home/coder/.local.
  • Port preview 404s: The app must listen on 0.0.0.0 inside the environment code-server can reach.
  • Safari WebSocket errors with TLS 1.3-only configs: The official guide notes Safari may need TLS 1.2 for WebSockets.

Scaling, Securing, and Next Steps

Backups must include the password configuration, VS Code user data, and the project directory. A simple maintenance script can capture all three:

sudo tee /opt/code-server/backup.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

cd /opt/code-server
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p backups

tar --one-file-system -czf "backups/code-server-config-${stamp}.tar.gz" config
tar --one-file-system -czf "backups/code-server-local-${stamp}.tar.gz" local
tar --one-file-system -czf "backups/code-server-project-${stamp}.tar.gz" project
mkdir -p "backups/code-server-compose-${stamp}"
cp -a docker-compose.yml .env "backups/code-server-compose-${stamp}/"

find backups -type f -mtime +14 -delete
EOF

sudo chmod 700 /opt/code-server/backup.sh
/opt/code-server/backup.sh
rsync -avz /opt/code-server/backups/ backup-user@backup.example.net:/srv/backups/code-server/

Test a restore on a separate machine or directory: unpack config, local, and project, start Compose, log in, and open /home/coder/project. A backup that omits config.yaml leaves you with a new password and a confusing login failure. A backup that omits local drops extensions and settings.

For upgrades, read the code-server release notes, take a fresh backup, pin the next image tag, and verify /healthz plus a login:

cd /opt/code-server
/opt/code-server/backup.sh
# Edit CODE_SERVER_IMAGE in .env, for example codercom/code-server:4.132.0
docker compose pull
docker compose up -d
docker compose logs --tail=120 code-server
curl -sS http://127.0.0.1:8080/healthz

Pinning codercom/code-server:4.132.0 keeps upgrades deliberate. Moving to latest is convenient for a homelab and risky for a shared editor, because a surprise VS Code bump can change extensions and settings.

If you outgrow a single-user editor, the FAQ recommends one VM per user rather than packing many code-server processes onto one shared kernel. Teams that need Terraform-provisioned workspaces should look at Coder.

The outcome of this guide is a private, HTTPS-secured code-server instance with hashed password authentication, localhost-only publishing, persistent config and workspace mounts, Nginx WebSocket proxying, and a backup path you can restore. From here, clone one repository into /opt/code-server/project, install the extensions you actually use, document the restore steps, watch disk usage, and keep an upgrade log so the editor remains simple enough to rebuild when a VS Code release lands.

Need this done on your server?

I deploy and harden Laravel, CodeCanyon, and open-source apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.