I rebuilt my dev box so it behaves like a real platform instead of a pile of pets. The stack lives the same two-node Proxmox cluster I’ve been using. The controller node runs GitLab CE and a few utilities. The second node is a dedicated DevBox where I build and stage web apps behind repeatable automation. The goal is simple: edit on my Macbook Air (or iPad), push to GitLab, mirror to GitHub for the public paper trail, and preview the result on real infrastructure without touching the keyboard more than necessary.
The platform is deliberately boring. Containers where they help, native services where state and ergonomics matter. All of it can be torn down and reconstructed in minutes with a single Ansible playbook.
Right now Traefik is parked and will be used later for pretty hostnames and path routing. For speed, the services are exposed directly: React on http://192.168.1.32/, WordPress on http://192.168.1.32:8081/, and VS Code Server natively on http://192.168.1.32:8443/. GitLab is on the controller at http://192.168.1.30/.
I tried running VS Code Server in a container. It worked until reboot. The container’s startup logic regenerated a password because the config path and service wrapper didn’t line up with the way I wanted persistence handled. I don’t have time to babysit editor logins, so I installed it as a systemd service on the node, pinned the config under /home/ghost/.config/code-server/, and set an explicit password in the YAML. Now it survives reboots and still gives me the same web UI on :8443.
GitLab on the controller handles repos and CI later. On the DevBox, Docker and Compose provide the app surfaces. A tiny Nginx container serves a built React site; it’s intentionally static so build steps happen in CI or locally, and the container only ever serves files from /srv/dev/data/react-app. WordPress runs as a pair. WordPress:latest plus mariadb:11—with MariaDB state on disk at /srv/dev/data/mysql/wp1. All the app containers sit on a shared user-defined web network so I can introduce Traefik without reshuffling stacks.
Workflow
When a change is ready, I push to GitLab on the controller. GitLab mirrors the repository out to GitHub using a push mirror so the public “green dots” stay current without me touching two remotes. When I want to preview something, I either docker compose up the relevant stack or let the DevBox’s one-shot systemd unit bring all app stacks up after a reboot. The result is a URL I can open on the LAN from my laptop or iPad without thinking about it.
Ansible
There’s one playbook for the DevBox. It installs Docker and the Compose plugin, lays down the /srv/dev tree, writes minimal Compose files for the React and WordPress stacks, creates the shared web network, and registers a small dev-stacks.service unit that brings stacks online in a predictable order. Another playbook handles native code-server: it installs the package, writes the config with the chosen password, opens the port, and enables the service. The GitLab host has its own role, but it’s straightforward Omnibus with persistent volumes.
Purpose & Guardrails
The platform exists to move quickly on web projects without pretending I’m running a cloud provider. It gives me real URLs, real state, and the discipline of infrastructure as code, but it stays small enough to reason about. Containers hold the web surfaces and their immediate dependencies. Native services are allowed when the UX payoff is obvious, like the editor. Data lives under /srv/dev/data/ and gets regular snapshots at the hypervisor layer so rollback is trivial.
When I care about pretty hostnames and TLS, I’ll put Traefik back in front and route /wp and /app cleanly, or move to subdomains with an internal CA. CI jobs in GitLab will take over React builds and WordPress plugin/theme checks. For now, the platform is exactly what I need: reproducible, fast to iterate, and honest about its complexity.