Self-service infrastructure for small teams. One sysadmin sets it up once; developers get their own test/dev machines through a web dashboard — no more "hey can you spin up a VM for me."
Runs entirely on your own hardware. No cloud account required, no per-minute billing, no vendor lock-in.
- Control plane — Cloudflare Workers + D1 + R2 (free tier is enough for small teams). Handles auth, RBAC, environment lifecycle, audit log.
- Executor — GitHub Actions, running on a self-hosted runner on your own infrastructure. Provisions with OpenTofu, configures with shell scripts over SSH.
- Dashboard — a small React app. Create environments, run actions (reboot, install services, backups), see who did what.
- Provider — Incus (LXC containers) today. Proxmox and public cloud are on the roadmap — see below.
- A Linux server (or VM) for Incus — needs real KVM/hardware virtualization,
not an OpenVZ/container-based VPS. Check with
systemd-detect-virtand confirm/dev/kvmexists before you start; Incus will not run without it. - 2GB+ RAM recommended. It technically starts on less, but you won't have room for more than one or two guest environments.
- A GitHub repo (fork or clone this one) and a Cloudflare account, both free.
git clone https://github.com/casablanque-code/platform-infra.git
cd platform-infra
cp platform.config.example.yml platform.config.ymlEdit platform.config.yml:
provider.type: incus- Leave
incus.remote_addrblank if you're runningsetup.shon the same machine you want Incus on — it'll install and configure Incus for you. - Fill in
cloudflare.*andgithub.*(see comments in the file for exact steps — D1 database, R2 bucket, GitHub PAT). - Generate the three
secrets.*values withopenssl rand -hex 32.
Then:
sudo bash setup.shThis deploys the control plane, sets all secrets, and (for Incus) installs and configures everything needed on the current machine. At the end it prints the command to register your self-hosted GitHub Actions runner — run that on the same machine as Incus.
Open the dashboard URL it gives you, sign in with the admin key, create your first environment.
| Provider | Status | Notes |
|---|---|---|
| Incus | ✅ Working | LXC containers, runs on a single server. First and only fully implemented provider. |
| Proxmox | 🚧 Not implemented | Stub exists in providers/proxmox/, see NOTES.md there for what's needed. |
| Cloud (Hetzner etc.) | 🚧 Not implemented | Stub exists in providers/cloud/, see NOTES.md there. |
platform.config.yml's provider.type selects which one setup.sh uses.
Proxmox and cloud currently fail with a clear error pointing at their
NOTES.md — they were deliberately left as stubs with a defined contract
(same tfvars/outputs shape as Incus) rather than half-implemented.
- docker-host — Ubuntu container, Docker CE + Compose pre-installed. Actions: install Portainer, Uptime Kuma, Node Exporter.
- postgres — Ubuntu container, PostgreSQL pre-installed, DB + user created automatically, credentials saved encrypted in the control plane. Actions: install pgAdmin, Node Exporter, backup to R2.
Both are LXC containers on Incus — not full VMs. For most dev/test workloads this is indistinguishable in practice (same SSH, same Docker, same everything), but keep it in mind if you need kernel-level isolation or non-Linux guests.
Services installed via actions (Portainer, Uptime Kuma, pgAdmin) can be
exposed through Cloudflare Tunnel
instead of opening firewall ports. Fill in cloudflare_tunnel.* in
platform.config.yml to enable it — leave domain blank to skip.
SSH access for provisioning and actions always goes over your normal network path; the tunnel only covers the services themselves.
- Proxmox provider
- Cloud provider (Hetzner first)
- Test coverage — currently none; see
_archive/for notes on what was here before and why it was removed - Packaged install — today this is git clone + shell script; a single binary or installer script is the goal
- Template catalog — K3s, WireGuard, Redis, n8n
- Per-environment secrets management
- Multi-tenancy — project isolation, team RBAC beyond the current admin/operator/viewer roles
workers/control-plane/ Cloudflare Worker — API, auth, D1 schema
apps/dashboard/ React dashboard
providers/ One folder per provider: provider.json (+ NOTES.md for stubs)
templates/ One folder per template: template.json, runtime.json, tofu/
bootstrap/ Shell scripts run over SSH after provisioning
.github/workflows/ provision.yml, destroy.yml, action.yml
_archive/ Retired code kept for reference (old cloud provider
stubs, mock gateway) — not part of the running system
MIT