A personal code server and build pipeline that stays mine
The editor should feel like a place, not a tab. I run VS Code on my own hardware, reachable from any browser. Here is how I set it up, connect securely, and 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. Predictable. Fast.
Cloud IDEs are convenient until they are not. Slow file trees, laggy terminals, storage limits, and the sense that your environment is someone else's problem. I wanted something mine.
It runs on Debian 12 Bookworm. systemd keeps it alive, Nginx handles TLS, and a Tailscale subnet router lets every device on my tailnet reach it without touching the firewall.
Access that feels local, even when I am not.
Keep code-server private, let Nginx be the edge.
From anywhere with a browser I can reach code-server over HTTPS. The config stays intentionally minimal — bound to localhost, with Nginx handling anything 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 punching holes in the router.
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 is on the same LAN — no open ports, no exposed IP, no port forwarding.
100.x.x.x:443127.0.0.1:8080Docker turns the browser into a build surface.
Any image, any pipeline, right from the IDE terminal.
Mounting the Docker socket is where this becomes more than a remote editor. The terminal in my browser VS Code has full access to Docker — spin up any build environment without installing a single thing on the host.
Build Android APKs from the browser.mingchen/docker-android-build-box ships Android SDK, Flutter, Kotlin, Node, and fastlane. The toolchain lives in the image, so the host stays clean. Mount your project and run.
Build Flutter apps with the same image. Switch Java versions with jenv, run flutter build apk, get your artifact. It even works from a browser tab on a phone if needed.
# 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' # flutter build in the same container $ docker run --rm \ -v `pwd`:/project \ mingc/android-build-box \ bash -c '. ~/.bash_profile; cd /project; flutter build apk --release'
OTA updates are 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 boring, in the best way.
Keep the host stable, keep 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 keep ports closed.
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, and themes survive reinstalls and server migrations without any manual work.
Tailscale subnet router over port forwarding. Instead of punching holes in my router, Tailscale as a subnet router means every device on my tailnet can reach it like it is on the same LAN — zero exposed ports, end-to-end encrypted, free for personal use.
The setup takes a weekend but the payoff is a dev environment that is identical on every device — laptop, tablet, phone. No syncing, no "works on my machine," no subscription. It turned a homelab project into a calm day-to-day workflow and the foundation for everything else I build, like my OTA updater pipeline.