Architecture
How Ctrl Plane's packages fit together, from providers to the HTTP API.
Ctrl Plane is organized as a set of Go packages, each responsible for one concern. There is no central god object. The app.CtrlPlane struct wires the packages together, but you can also use any package independently.
Package map
github.com/xraph/ctrlplane/
id/ TypeID wrapper (prefix-qualified identifiers)
auth/ Authentication and authorization interface
provider/ Cloud provider abstraction + implementations
instance/ Instance lifecycle management
deploy/ Deployments, releases, and strategies
health/ Health checking subsystem
network/ Domains, routes, and TLS certificates
secrets/ Secret management and vault interface
telemetry/ Metrics, logs, traces, resource snapshots
admin/ Tenant management, quotas, audit
event/ Event bus and webhooks
worker/ Background task scheduler
store/ Persistence layer (postgres, sqlite, memory)
api/ HTTP handlers and middleware
app/ Root orchestrator
extension/ Forge extension adapter
cmd/ Reference binaryLayered design
The library is structured in layers. Lower layers know nothing about higher layers.
Layer 1: Foundation
The root ctrlplane package and the id package. These define the base Entity type, sentinel errors, configuration, and the TypeID identity system. Every other package depends on these.
Layer 2: Interfaces
Each subsystem package (instance, deploy, health, network, secrets, telemetry, admin, event) defines its own Service interface, Store interface, and domain entities. These packages depend only on the foundation layer and each other where necessary.
Layer 3: Infrastructure
The provider package defines the Provider interface that abstracts over cloud orchestrators. The store package composes all subsystem store interfaces into a single aggregate interface. The auth package defines the authentication abstraction.
Layer 4: Wiring
The app package creates a CtrlPlane struct that instantiates all service implementations, registers background workers, and connects the event bus. The api package mounts HTTP handlers. The extension package adapts everything for Forge.
Data flow
A typical request flows through these layers:
HTTP Request
-> api.AuthMiddleware (extracts tenant from token)
-> api.Handler (parses request, calls service)
-> instance.Service (business logic, state validation)
-> provider.Provider (talks to Docker/K8s/AWS)
-> instance.Store (persists state change)
-> event.Bus (publishes lifecycle event)Key interfaces
Every subsystem follows the same pattern: a Service interface for business logic, a Store interface for persistence, and domain-specific extension interfaces.
| Interface | Package | Role |
|---|---|---|
provider.Provider | provider/ | Cloud orchestrator abstraction (15+ methods) |
instance.Service | instance/ | Instance CRUD and lifecycle actions |
deploy.Service | deploy/ | Deployment orchestration and release management |
deploy.Strategy | deploy/ | Pluggable deployment strategy (rolling, blue-green, canary, recreate) |
health.Service | health/ | Health check configuration and execution |
health.Checker | health/ | Individual check type implementation |
network.Service | network/ | Domain, route, and certificate management |
network.Router | network/ | External traffic routing abstraction |
secrets.Service | secrets/ | Secret lifecycle and injection |
secrets.Vault | secrets/ | Backend secret storage abstraction |
telemetry.Service | telemetry/ | Metrics, logs, and trace querying |
telemetry.Collector | telemetry/ | Custom telemetry data source |
admin.Service | admin/ | Tenant, quota, and audit management |
event.Bus | event/ | Publish/subscribe event dispatch |
auth.Provider | auth/ | Authentication and authorization |
store.Store | store/ | Aggregate persistence (composes all subsystem stores) |
worker.Worker | worker/ | Background periodic task |
Usage modes
Standalone library
Import the packages you need, create a CtrlPlane with app.New(), and serve the HTTP API with api.New(). You own the process.
cp, _ := app.New(
app.WithStore(postgresStore),
app.WithProvider("k8s", kubernetesProvider),
app.WithAuth(myAuthProvider),
)
cp.Start(ctx)
http.ListenAndServe(":8080", api.New(cp).Handler())Forge extension
Wrap Ctrl Plane in the extension adapter and mount it into a Forge application. Forge manages the HTTP server and process lifecycle.
forgeApp := forge.New()
forgeApp.Use(cpext.New(
cpext.WithProvider("k8s", kubernetesProvider),
cpext.WithAuthProvider(authsomeAdapter),
))
forgeApp.Run()Individual packages
You can use any subsystem package on its own. For example, the health package works independently if you provide a store and a set of checkers.
Background workers
The worker.Scheduler manages five built-in workers that run on configurable intervals:
| Worker | Purpose | Default interval |
|---|---|---|
| Reconciler | Detects drift between stored state and provider state | 60s |
| HealthRunner | Executes configured health checks | 30s |
| TelemetryCollector | Collects metrics and resource snapshots from providers | 10s |
| GarbageCollector | Cleans up old deployments, health results, and audit entries | 5m |
| CertRenewer | Renews TLS certificates approaching expiry | 1h |
Workers are started by cp.Start(ctx) and stopped by cp.Stop(ctx).