April 11, 2026
I Vibe-Coded a Full-Stack Proxmox Dashboard: Here's What Actually Matters
14 Commits, 11K Lines, One AI Agent
A few weeks ago I shipped PVE Pilot, a full-stack Proxmox VE management dashboard with a Go/Gin backend, a Next.js 16 frontend, and a NATS message queue for async provisioning. It has 123 tests. The entire thing was built through “vibe coding” with Claude Code, Anthropic’s CLI agent.
I want to be precise about what that means, because the term “vibe coding” carries a lot of baggage. I didn’t type “build me a Proxmox dashboard” and watch magic happen. What I actually did was architect a system. I chose Go for the backend because I wanted strong typing, excellent concurrency primitives, and a single binary deployment. Chose NATS for async job processing because provisioning a VM involves multiple slow Proxmox API calls that shouldn’t block an HTTP handler. Designed a proxmox.API interface with 40 methods so every handler and worker depends on an abstraction, not a concrete client, making the whole thing testable with mocks.
The AI executed my architectural vision. It didn’t create the vision.
What Vibe Coding Actually Looks Like
The workflow resembled pair programming with a very fast but sometimes overconfident junior developer. I’d describe what I wanted at a high level, and the AI would produce a working first draft across the full stack. I described the backup flow in three sentences (“Proxmox uses vzdump with snapshot mode and zstd compression, backups go to the NFS storage, I need instant and scheduled options”) and had a working UI with real-time SSE progress streaming forty minutes later. Go handler, Proxmox API calls, React components, the lot.
That breadth is where AI genuinely shines. Context-switching between Go, TypeScript, Docker Compose, and shell scripts in a single conversation without any of the mental overhead that usually comes with polyglot development. The boilerplate and plumbing came together remarkably fast. And once I established the interface-based pattern with proxmox.API, the AI generated 93 Go tests and 30 frontend tests with appropriate mocks and table-driven test cases. The pattern was clear enough that it could replicate it consistently.
But here’s where the story gets more interesting.
Where Deep Understanding Saved the Project
The UPID Problem
Proxmox’s clone and start operations don’t complete synchronously. They return UPIDs (unique process IDs for background tasks). The AI’s first implementation tried to verify a clone succeeded by checking if the target VM existed. This worked… most of the time. Except when it didn’t, because the disk copy was still in progress and the VM was locked. Silent race conditions, the worst kind.
It took three iterations to get right. Not because the AI couldn’t write the polling code, but because I had to understand why the clone was intermittently failing. The disk was still being copied. The VM was in a locked state. You can’t configure cloud-init on a locked VM. Once I understood the root cause, the fix was straightforward: use WaitForTask(upid) to poll the task status endpoint until completion. The AI wrote the implementation in minutes. But the diagnosis required understanding how Proxmox actually works under the hood.
LXC vs QEMU: Fundamentally Different Worlds
This one was the most architecturally significant. The AI kept trying to apply VM patterns to LXC containers, and it kept breaking. LXC containers have no guest agent. The /config endpoint doesn’t accept password or ssh-public-keys after creation, only during the initial POST /lxc. Restore uses different parameter names (ostemplate with restore=1 instead of archive). Storage filtering uses rootdir instead of images.
No amount of retrying would fix this. I had to architect a completely separate code path: SSH from the backend to the Proxmox host, then use pct exec to run commands inside the container. This meant adding an SSH client (golang.org/x/crypto/ssh), new environment variables, Docker volume mounts for the private key, and base64 encoding for scripts to avoid shell escaping issues.
The AI wrote all of that code once I designed the approach. But the approach itself came from understanding Proxmox’s architecture deeply enough to know that LXC and QEMU are fundamentally different virtualization technologies with fundamentally different management APIs.
The Guest Agent Trap
The Proxmox guest agent’s /agent/exec endpoint requires a JSON body with command: "bash" and input-data as stdin. The AI sent form-encoded requests. Proxmox returned a 596 status code with “Broken pipe.” That’s it. No helpful error message, no hint about content types.
The AI guessed. It tried different encodings, different parameters, different endpoints. I diagnosed it by recognizing the pattern: “Broken pipe” on a POST usually means the server rejected the body before reading it completely, which screams content-type mismatch. Switching to JSON with the right field names fixed it immediately.
I saw this same dynamic with SSH key encoding. Proxmox needs %20 for spaces, not +, and Go’s url.QueryEscape produces +. The AI tried three approaches; I understood RFC 3986 vs HTML form encoding and prescribed the fix in one shot. When the problem is “two valid standards and this API chose the less common one,” understanding the standards beats brute-force iteration every time.
When No Documentation Exists
Some problems the AI simply cannot solve. While wiring up static IP support, VMs with IPs outside the DHCP range couldn’t reach the internet. The AI tried different cloud-init configs, different gateway settings, different subnet masks. Nothing worked, because nothing was wrong with the configuration. My consumer router just won’t NAT IPs it hasn’t handed out via DHCP. You only discover this by running real hardware on a real network. The fix was to assign static IPs within the DHCP range but outside the dynamic pool, something no documentation mentions because enterprise routers don’t have this limitation.
This pattern kept recurring. Debian 12 LXC containers have a ssh.socket vs ssh.service issue where SSH appears “running” but won’t accept connections. Grafana’s sandbox silently fails in unprivileged LXC containers. These aren’t bugs in the AI. They’re the kind of knowledge that comes from operating infrastructure, not from reading about it.
What’s Actually Scarce Now
Here’s the thesis I keep coming back to: writing code is becoming a commodity. AI can produce syntactically correct, well-structured, idiomatically appropriate code faster than any human. But the code was always the easy part. The hard parts were always the parts around the code, and those haven’t gotten any easier.
Systems thinking. Debugging beyond the error message. Domain knowledge from running real infrastructure. Architectural judgment about which abstractions are worth the investment and which are leaky. Every story in this post is an example of one of these, and in every case, the AI was stuck until I brought the understanding it couldn’t generate on its own.
Computer science fundamentals (concurrency, networking, systems design, API design patterns) aren’t being made obsolete by AI. They’re becoming the differentiator between someone who can prompt an AI and someone who can actually ship working software.
Try It, Break It, Improve It
PVE Pilot is open source at github.com/ashkankamyab/pve-pilot. If you run a homelab with Proxmox, give it a spin. It handles VM and container provisioning with real-time progress streaming, backup and restore (instant or scheduled), vertical scaling, disk management, and static or DHCP networking, all through a clean dark-themed UI.
The codebase is well-tested (123 tests across backend and frontend) and the architecture is intentionally clean. Check out CONTRIBUTING.md if you want to get involved. PRs are welcome, whether you bring deep Proxmox expertise, Go or Next.js chops, or you’re looking for a real project to practice your own vibe coding workflow on.
If you find bugs or want features, open an issue. That’s how open source works. One commit at a time, each one making the thing a little better than it was before. AI or not.