A composable Go library for deploying and managing SaaS instances at scale.
Ctrl Plane handles instance lifecycle, zero-downtime deployments, health monitoring, secret management, traffic routing, and multi-tenant isolation. You bring a cloud provider and an auth system. Ctrl Plane wires everything together.
Ctrl Plane is a library, not a framework. You import Go packages, configure them with functional options, and embed them into your own application. It runs standalone or as a Forge extension.
cp, err := app.New(
app.WithStore(postgresStore),
app.WithProvider("k8s", kubernetesProvider),
app.WithAuth(myAuthProvider),
)
cp.Start(ctx)
http.ListenAndServe(":8080", api.New(cp).Handler())What it does:
- Provisions and manages tenant instances across any cloud provider (Docker, Kubernetes, AWS ECS, Fly.io, etc.)
- Deploys with rolling, blue-green, canary, or recreate strategies
- Runs HTTP, TCP, gRPC, and command-based health checks
- Manages custom domains, TLS certificates, and traffic routing
- Stores secrets with pluggable vault backends
- Publishes lifecycle events and delivers webhooks
- Collects metrics, logs, traces, and resource snapshots
- Enforces tenant quotas and records audit trails
go get github.com/xraph/ctrlplane@latestRequires Go 1.26 or later.
A minimal server with an in-memory store and Docker provider:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/xraph/ctrlplane/api"
"github.com/xraph/ctrlplane/app"
"github.com/xraph/ctrlplane/auth"
"github.com/xraph/ctrlplane/provider/docker"
"github.com/xraph/ctrlplane/store/memory"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
memStore := memory.New()
dockerProv, err := docker.New(docker.Config{
Host: "unix:///var/run/docker.sock",
})
if err != nil {
log.Fatal(err)
}
cp, err := app.New(
app.WithStore(memStore),
app.WithAuth(auth.NewNoopProvider()),
app.WithProvider("docker", dockerProv),
app.WithDefaultProvider("docker"),
)
if err != nil {
log.Fatal(err)
}
if err := cp.Start(ctx); err != nil {
log.Fatal(err)
}
defer cp.Stop(context.Background())
srv := &http.Server{
Addr: ":8080",
Handler: api.New(cp).Handler(),
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
<-ctx.Done()
srv.Shutdown(context.Background())
}()
log.Println("ctrlplane listening on :8080")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}Create a tenant:
curl -X POST http://localhost:8080/v1/admin/tenants \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corp", "slug": "acme", "plan": "pro"}'Create an instance:
curl -X POST http://localhost:8080/v1/instances \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "ten_...",
"name": "web-app",
"image": "nginx:alpine",
"provider_name": "docker",
"resources": {"cpu_millis": 500, "memory_mb": 256},
"ports": [{"container_port": 80, "protocol": "tcp"}]
}'Register the Ctrlplane extension before the Forge dashboard extension, then add @forge-go/dashboard-plugin-ctrlplane to your React shell. You can find the plugin and persistent local demo in forge-dashboard/packages/plugin-ctrlplane in the sibling dashboard checkout. The app mounts at /@ctrlplane and exposes workloads, replicas, deployments, templates, health, networking and administration through typed contract intents.
Your dashboard authentication must supply a trusted subject, ctrlplane:read or ctrlplane:write, and a nonempty tenant_id claim. Global administration requires system:admin. Every intent also checks the configured Ctrlplane authorization provider. The old templ contributor and its injected administrator claims have been removed.
See the migration record for the legacy inventory, behavior changes, verification evidence and remaining infrastructure limits. The local demo persists domain records in Badger and simulates provider operations; it does not start containers or provision cloud resources.
ctrlplane.go Root package (Entity, Config, sentinel errors)
id/ TypeID-based identifiers (prefix-qualified, UUIDv7)
auth/ Authentication and authorization interface
instance/ Instance lifecycle management
deploy/ Deployments, releases, and strategies
health/ Health checks (HTTP, TCP, gRPC, command)
network/ Domains, routes, TLS certificates
secrets/ Secret management with pluggable vault
telemetry/ Metrics, logs, traces, resource snapshots
admin/ Tenant management, quotas, audit
event/ Event bus and webhooks
worker/ Background task scheduler
provider/ Cloud provider abstraction
docker/ Docker provider (implemented)
kubernetes/ Kubernetes (interface defined)
aws/, gcp/, azure/ Cloud providers (interface defined)
nomad/, fly/ Additional providers (interface defined)
store/ Persistence layer
memory/ In-memory (tests and development)
sqlite/ SQLite (standalone deployments)
postgres/ PostgreSQL (production)
api/ HTTP handlers and middleware
app/ Root orchestrator (wires everything together)
extension/ Forge extension adapter
cmd/ctrlplane/ Reference binary
Every subsystem is defined by Go interfaces. Swap out any piece with your own implementation.
| Interface | Package | Purpose |
|---|---|---|
provider.Provider |
provider/ |
Cloud orchestrator abstraction |
instance.Service |
instance/ |
Instance CRUD and lifecycle |
deploy.Service |
deploy/ |
Deployment orchestration |
deploy.Strategy |
deploy/ |
Pluggable deployment strategy |
health.Checker |
health/ |
Health check implementation |
network.Router |
network/ |
Traffic routing abstraction |
secrets.Vault |
secrets/ |
Backend secret storage |
telemetry.Collector |
telemetry/ |
Custom telemetry source |
event.Bus |
event/ |
Event publish/subscribe |
auth.Provider |
auth/ |
Authentication and authorization |
store.Store |
store/ |
Aggregate persistence |
Mount Ctrl Plane into a Forge application:
app := forge.New()
app.Use(cpext.New(
cpext.WithProvider("k8s", kubernetesProvider),
cpext.WithAuthProvider(authsomeAdapter),
))
app.Run()Ctrl Plane traces and measures its own work. In a Forge app you get it for free: the extension records on app.Metrics(), so /_/metrics carries series like forge_ctrlplane_provider_call_duration_seconds and forge_ctrlplane_deployment_duration_seconds, and spans such as execute deployment and provision instance nest under forge's HTTP server span. The reconciler, garbage collector and provider health poller each start one root span per pass that has work.
Outside Forge, pass a go-utils metrics sink with app.WithMetrics(m) and install an OpenTelemetry tracer provider. The sink has to keep one series per name and label set, as forge's metrics do; go-utils' NewMetricsCollector keys by name alone and would merge them. Error messages, tenant IDs, names, URL paths and query strings stay off spans and labels, Docker's client spans included, and calls to providers never forward your callers' baggage. The full list of spans and metrics is in docs/content/docs/subsystems/observability.mdx.
Full documentation is available in the docs/ directory and covers architecture, core concepts, every subsystem, guides for writing custom providers and deployment strategies, and the complete HTTP API reference.
go build ./...
go test ./...
golangci-lint run ./...
goimports -w -local github.com/xraph/ctrlplane .See LICENSE for details.