Ctrl Plane

Configuration

Global configuration, the option pattern, and how to customize Ctrl Plane behavior.

Ctrl Plane uses two complementary configuration mechanisms: a Config struct for static settings, and functional options for wiring dependencies.

Global config

The root ctrlplane.Config struct holds settings that affect the entire system:

type Config struct {
    DatabaseURL            string        `json:"database_url"             env:"CP_DATABASE_URL"`
    DefaultProvider        string        `json:"default_provider"         env:"CP_DEFAULT_PROVIDER"`
    HealthInterval         time.Duration `json:"health_interval"          env:"CP_HEALTH_INTERVAL"          default:"30s"`
    TelemetryFlushInterval time.Duration `json:"telemetry_flush_interval" env:"CP_TELEMETRY_FLUSH"           default:"10s"`
    MaxInstancesPerTenant  int           `json:"max_instances_per_tenant" env:"CP_MAX_INSTANCES"             default:"0"`
    AuditEnabled           bool          `json:"audit_enabled"            env:"CP_AUDIT_ENABLED"             default:"true"`
}

Each field has a json tag for file-based config, an env tag for environment variables, and a default tag where applicable.

Functional options

The app.CtrlPlane is configured with functional options passed to app.New(). Each option is a function that modifies the CtrlPlane during construction and can return an error if the configuration is invalid:

cp, err := app.New(
    app.WithStore(postgresStore),
    app.WithAuth(myAuthProvider),
    app.WithProvider("k8s-prod", kubernetesProvider),
    app.WithProvider("docker-dev", dockerProvider),
    app.WithDefaultProvider("k8s-prod"),
    app.WithEventBus(natsEventBus),
)

Available options

OptionPurpose
WithConfig(cfg)Set the global configuration struct
WithStore(s)Set the persistence backend
WithAuth(p)Set the authentication provider
WithProvider(name, p)Register a named cloud provider
WithDefaultProvider(name)Set which provider to use when none is specified
WithEventBus(b)Replace the default in-memory event bus

Provider configuration

Each provider has its own Config struct. Here's the Docker provider as an example:

dockerProv, err := docker.New(docker.Config{
    Host:          "unix:///var/run/docker.sock",
    Namespace:     "ctrlplane",
    DefaultCPU:    500,
    DefaultMemory: 256,
    NetworkName:   "cp-network",
})

Subsystem configuration

Individual subsystem services accept configuration through their constructors. When using app.New(), the wiring happens automatically. When using packages independently, you create services directly:

instanceSvc := instance.NewService(instance.ServiceConfig{
    Store:    myStore,
    Provider: myProvider,
    Events:   myEventBus,
})

File-based configuration (Forge extension)

When Ctrl Plane runs as a Forge extension, it can load configuration from the application's YAML config file. The extension checks two keys in order:

  1. extensions.ctrlplane -- namespaced under the Forge extensions convention (recommended)
  2. ctrlplane -- legacy top-level key
forge.yaml
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: false

Extension-specific fields

In addition to the global config fields, the extension config adds:

FieldTypeDescriptionDefault
base_pathstringURL prefix for all ctrlplane routes/ctrlplane
disable_routesboolDisable automatic route registrationfalse
disable_migrateboolDisable automatic database migrationfalse

Configuration precedence

When both file-based and programmatic options are provided, they are merged:

  1. YAML values take precedence for string, duration, and integer fields.
  2. Programmatic boolean flags (DisableRoutes, DisableMigrate) override when set to true.
  3. AuthProvider can only be set programmatically (not from YAML).
  4. Any remaining zero-valued fields are filled from DefaultConfig().

If no file-based config is found and RequireConfig is not set, the extension falls back to programmatic options merged with defaults.

Environment variables

All environment variables use the CP_ prefix to avoid collisions:

VariableDescriptionDefault
CP_DATABASE_URLDatabase connection string(none)
CP_DEFAULT_PROVIDERDefault provider name(none)
CP_HEALTH_INTERVALHealth check interval30s
CP_TELEMETRY_FLUSHTelemetry flush interval10s
CP_MAX_INSTANCESMax instances per tenant (0 = unlimited)0
CP_AUDIT_ENABLEDEnable audit loggingtrue

On this page