DEV Community

Cover image for I Built My Own VS Code Server in 5 Minutes, Now My IDE Lives in a Browser Tab
Mothilal M
Mothilal M

Posted on

I Built My Own VS Code Server in 5 Minutes, Now My IDE Lives in a Browser Tab

I have a confession: I own three machines and none of them agree on anything.

The desktop has my Node version. The laptop has the Python environment. The old ThinkPad in the corner has the one Docker setup that actually works. Every time I switch, I lose twenty minutes to nvm install, missing extensions, and a settings.json that drifted apart three months ago.

So last week I fixed it. Not with dotfile syncing, not with a $20/month cloud IDE subscription. I put VS Code itself in a browser tab.

Real VS Code. Real terminal. My extensions. Running at localhost:8080 in a Docker container.

It took about five minutes. Here's exactly how.


The 30-second version

If you have Docker, you are one command away:

docker run -it --rm \
  -p 8080:8080 \
  -e PASSWORD="demo1234" \
  codercom/code-server:latest \
  --bind-addr 0.0.0.0:8080 --auth password
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8080, type demo1234, and you're staring at VS Code.

Not a Monaco editor in a box. Not a "code playground." The actual workbench — activity bar, file explorer, tabs, command palette (Ctrl+Shift+P), settings UI, extension marketplace.

Now hit Ctrl+`.

A real terminal. Bash, running inside the container. apt install, npm install, python3 — it's a full Linux box, and you're driving it from a browser.

That's the moment it clicked for me. This isn't a toy.


Wait — what is this thing?

It's code-server, built by the team at Coder. It takes the open-source core of VS Code and runs it as a web server. Your browser is the window; everything else happens in the container.

It's MIT licensed. It's free. There is no account, no telemetry you didn't ask for, and no monthly bill.


Making it survive a restart

The command above uses --rm, which means the moment you stop it, everything vanishes. Extensions, settings, files — gone. Fine for a test drive, useless for real work.

For a setup you'll actually use, you want three things to persist: your projects, your settings, and your extensions.

Make a folder and drop in a docker-compose.yml:

services:
  code-server:
    image: codercom/code-server:latest
    container_name: code-server
    restart: unless-stopped
    user: "1000:1000"
    environment:
      PASSWORD: "pick-something-better-than-this"
    volumes:
      - ./projects:/home/coder/projects   # your code
      - ./config:/home/coder/.config      # settings.json, keybindings
      - ./local:/home/coder/.local        # installed extensions
    command:
      - "--bind-addr=0.0.0.0:8080"
      - "--auth=password"
      - "--disable-telemetry"
      - "/home/coder/projects"
    ports:
      - "8080:8080"
Enter fullscreen mode Exit fullscreen mode

Then:

mkdir -p projects config local
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Back to localhost:8080. Install your extensions, tweak your theme, write some code. Now docker compose restart — or reboot your machine entirely — and it all comes back exactly as you left it.


The bug that ate 20 minutes of my life

Here's the part most tutorials skip, and it's the part that will actually bite you.

My first attempt used Docker named volumes instead of folders. Looked cleaner. The container immediately went into a crash loop, and the logs said this, over and over:

[error] EACCES: permission denied, mkdir '/home/coder/.config/code-server'
Enter fullscreen mode Exit fullscreen mode

Here's what's happening. code-server runs as the coder user — UID 1000 — not as root. When Docker creates an empty named volume for a path that doesn't already exist in the image, that volume comes up owned by root. So code-server boots, tries to create its config directory, gets slapped by the filesystem, and dies. Restart policy brings it back. It dies again. Forever.

Two ways out:

Use bind mounts and pin the user — that's the user: "1000:1000" line in the compose file above. On most Linux systems your own account is UID 1000, so the folders you create are already owned correctly and everything just works. Check with id -u.

Or chown the volume first, if you'd rather keep named volumes:

docker run --rm -v code_config:/data alpine chown -R 1000:1000 /data
Enter fullscreen mode Exit fullscreen mode

I went with bind mounts. Beyond dodging the bug, I can ls into ./config and see my actual settings.json sitting there in plain sight, which is worth something on its own.


What you actually get

A few things surprised me:

Extensions work. Themes, language support, formatters, GitLens — install them from the built-in marketplace like normal.

Port forwarding is automatic. Start a dev server inside the terminal on port 3000 and VS Code notices and offers to forward it. Your Vite app shows up in another tab.

It runs beautifully on a tablet. This was the unexpected win. An iPad with a keyboard case is suddenly a genuinely usable dev machine. Not "usable in an emergency" — actually fine.

It's a clean room. Every project can have its own container with its own toolchain. No more version conflicts bleeding across projects, no more "which Python is this."


The honest limitations

I'd rather you hear this from me than discover it at 1am.

Some Microsoft extensions will not work. Pylance, C# Dev Kit, Remote Development, Live Share — these are proprietary, and their licenses restrict them to official Microsoft products. code-server pulls from the Open VSX registry instead. For Python you'll fall back to Jedi or basedpyright. They're fine. They're not Pylance.

Browser keyboard shortcuts fight you. Ctrl+W closing your entire tab instead of the editor is a rite of passage. Installing it as a PWA fixes most of this.

It's as fast as the machine it runs on. On localhost that's instant. Put it on a $5 VPS and you'll feel every keystroke.


Where this actually earns its keep

  • One environment, every device. Desktop, laptop, tablet — same tab, same state, same everything.
  • Onboarding that takes a minute. Hand someone a compose file instead of a three-page README.
  • Teaching and workshops. Everyone gets an identical environment. No more "it doesn't work on mine."
  • Reviewing a PR from a borrowed laptop. Just log in.

Try it

Seriously — go run the one-liner at the top. It costs you thirty seconds and one docker pull, and the moment you hit Ctrl+` and a real shell opens inside a browser tab, you'll get why I bothered writing this.

docker run -it --rm -p 8080:8080 -e PASSWORD="demo1234" \
  codercom/code-server:latest --bind-addr 0.0.0.0:8080 --auth password
Enter fullscreen mode Exit fullscreen mode

localhost:8080. Go.


If you end up running this on a machine other people can reach, treat it seriously — that terminal is a full shell on your box. Put it behind a real password at minimum, and ideally behind HTTPS and a VPN or an auth proxy. That's a whole post of its own, and I'll write it if there's interest.

What would you use this for? I'm curious whether the tablet thing lands for anyone else, or if that's just me.

Top comments (0)