Forge Extension
Mount Ctrl Plane into a Forge application with shared auth and routing.
Ctrl Plane can run as a Forge extension. This lets you embed instance management into a larger Forge-based application and share authentication, middleware, and the HTTP server.
Basic setup
package main
import (
"github.com/xraph/forge"
cpext "github.com/xraph/ctrlplane/extension"
"github.com/xraph/ctrlplane/provider/docker"
)
func main() {
dockerProv, _ := docker.New(docker.Config{
Host: "unix:///var/run/docker.sock",
})
app := forge.New()
app.Use(cpext.New(
cpext.WithProvider("docker", dockerProv),
))
app.Run()
}The extension registers itself with Forge and mounts the Ctrl Plane HTTP API at the configured base path.
File-based configuration
When running inside Forge, Ctrl Plane automatically loads configuration from the application's YAML config file. The extension looks for settings under two keys (in order):
extensions.ctrlplane(namespaced, recommended)ctrlplane(legacy shorthand)
extensions:
ctrlplane:
database_url: "postgres://localhost:5432/ctrlplane"
default_provider: "k8s"
base_path: "/ctrlplane"
health_interval: "30s"
telemetry_flush_interval: "10s"
max_instances_per_tenant: 100
audit_enabled: true
disable_routes: false
disable_migrate: falseMerge behavior
File-based and programmatic configuration are merged together with these rules:
| Category | Precedence |
|---|---|
Boolean flags (disable_routes, disable_migrate) | Programmatic true wins |
String fields (base_path, database_url, default_provider) | YAML takes precedence; programmatic fills gaps |
Duration/int fields (health_interval, max_instances_per_tenant, etc.) | YAML takes precedence; programmatic fills gaps |
AuthProvider | Always programmatic (cannot be set from YAML) |
Any remaining zero-valued fields are filled from DefaultConfig().
Requiring file config
If your deployment relies on the config file being present, use WithRequireConfig:
app.Use(cpext.New(
cpext.WithRequireConfig(true),
))When RequireConfig is true and no config is found under either key, Register returns an error.
Extension options
| Option | Purpose |
|---|---|
WithAuthProvider(p) | Set the auth provider |
WithProvider(name, p) | Register a cloud provider |
WithBasePath(path) | Set the URL prefix (default: /ctrlplane) |
WithConfig(cfg) | Set extension configuration directly |
WithStore(opt) | Pass a store option to the underlying Ctrl Plane |
WithExtension(x) | Register a plugin extension (lifecycle hooks) |
WithDisableRoutes() | Disable automatic route registration |
WithDisableMigrate() | Disable automatic database migration on Register |
WithRequireConfig(bool) | Require config to be present in YAML files |
With Authsome
If you're using Authsome for authentication, connect it through the adapter:
package main
import (
"github.com/xraph/forge"
"github.com/xraph/authsome"
cpext "github.com/xraph/ctrlplane/extension"
authadapter "github.com/xraph/ctrlplane/auth/authsome"
)
func main() {
app := forge.New()
// Mount Authsome first
authProvider := authsome.New(/* ... */)
app.Use(authProvider.Extension())
// Mount Ctrl Plane with the Authsome adapter
app.Use(cpext.New(
cpext.WithAuthProvider(authadapter.New(authProvider)),
cpext.WithProvider("k8s", kubernetesProvider),
))
app.Run()
}The Authsome adapter translates Authsome's user and session model into Ctrl Plane's auth.Claims format.
Lifecycle
The extension implements the forge.Extension interface:
Register(fapp)-- Loads configuration from YAML files (or programmatic defaults), initializes the Ctrl Plane instance, runs migrations, registers routes, and provides the instance to the DI container.Start(ctx)-- Starts background workers.Stop(ctx)-- Gracefully shuts down background workers.Health(ctx)-- Returns an error if the extension is not initialized.
Forge calls these methods in order during its own startup and shutdown sequence.
Accessing the Ctrl Plane
If you need to call Ctrl Plane services from other parts of your Forge application:
ext := cpext.New(/* ... */)
app.Use(ext)
// After init, access the underlying Ctrl Plane
cp := ext.CtrlPlane()
instance, err := cp.Instances.Get(ctx, instanceID)