Self-hosted code server & Docker build pipeline
I run VS Code on my own hardware, accessible from any browser. Here's how I set it up, how I connect remotely, and how I trigger Docker builds straight from the IDE.
# pull the android build box image $ docker pull mingc/android-build-box:latest # mount project and run a build — all from the browser IDE $ docker run --rm \ -v `pwd`:/project \ -v "$HOME/.dockercache/gradle:/root/.gradle" \ mingc/android-build-box \ bash -c 'cd /project; ./gradlew assembleRelease' # output apk sitting in build/outputs/apk/release/
Mine. Persistent. Fast.
Cloud IDEs are convenient until they're not — slow file trees, laggy terminals, storage limits, and the creeping feeling your dev environment is someone else's problem. I wanted something mine.
Running on Debian 12 Bookworm. Code-server is managed by systemd, Nginx handles TLS, and a Tailscale subnet router means every device on my tailnet can reach it without touching the firewall.
Secure access from any browser.
Keep code-server local, let Nginx face the world.
From anywhere with a browser I can hit my code-server over HTTPS. The config is intentionally minimal — bound to localhost, Nginx handles everything public-facing.
bind-addr: 127.0.0.1:8080 auth: password password: your-secure-passphrase cert: false # nginx handles TLS
# devices on your tailnet hit the subnet router # then access code-server directly via Tailscale IP $ open http://100.x.x.x:8080
Private access without port forwarding.
The subnet router bridges tailnet and LAN.
My server runs a Tailscale subnet router, advertising its local network to my tailnet. Any device I own can reach it like it's on the same LAN — no open ports, no exposed IP, no port forwarding.
Docker makes the browser a build farm.
Any image, any pipeline, right from the IDE terminal.
Mounting the Docker socket is where things get genuinely exciting. The terminal in my browser VS Code has full access to Docker — which means I can spin up any build environment without installing a single thing on the host.
# pull once, reuse forever $ docker pull mingc/android-build-box:latest # assemble a release APK — gradle cache mounted for speed $ docker run --rm \ -v `pwd`:/project \ -v "$HOME/.dockercache/gradle:/root/.gradle" \ mingc/android-build-box \ bash -c 'cd /project; ./gradlew assembleRelease' # build an AAB bundle for Play Store $ docker run --rm \ -v `pwd`:/project \ mingc/android-build-box \ bash -c 'cd /project; ./gradlew bundleRelease'
jenv, run flutter build apk, get your artifact. All from a browser tab on my phone if I need to. $ docker run --rm \ -v `pwd`:/project \ -v "$HOME/.dockercache/gradle:/root/.gradle" \ mingc/android-build-box \ bash -c '. ~/.bash_profile; cd /project; flutter build apk --release' # output: build/app/outputs/apk/release/app-release.apk
OTA updates — this pipeline in production.
The most concrete use of this setup is my Capacitor OTA updater. Both shell scripts — deploy-ota.sh and build-apk.sh — run directly in this terminal and upload to my Hono API on Cloudflare Workers. No CI, no external build service.
/var/run/docker.sock gives root-equivalent access to the host. Only do this on hardware you fully own, behind strong authentication. The habits that keep it reliable.
Keep the host stable and the workflow portable.
After a few rebuilds and late-night fixes, a few practices made the setup predictable: systemd for reliability, Settings Sync for instant portability, and Tailscale to avoid exposed ports.
Use systemd, not screen
When the server reboots at 3am for a kernel update you want code-server back automatically. A proper systemd unit handles that without you thinking about it.
Settings Sync is a must
Turn on VS Code Settings Sync backed by GitHub. Extensions, keybindings, themes — all survive reinstalls and server migrations without any manual work.
Tailscale subnet router over port forwarding
Instead of punching holes in my router, I run Tailscale as a subnet router. Every device on my tailnet can reach it like it's on the same LAN — zero exposed ports, end-to-end encrypted, free for personal use.
The whole setup takes a weekend but the payoff is a dev environment that's identical on every device — laptop, tablet, phone. No syncing, no "works on my machine," no subscription. It's turned a homelab project into a genuinely solid day-to-day workflow — and the foundation for everything else I build from here, like my OTA updater pipeline.