Controlplane
Preview (test fixture)
Older
You are reading Preview (test fixture). The current release is documented here.
Docs/Controlplane/Getting Started
Open

Reading4 min
Updated31 Jul 2026
Sourcepreview/getting-started.mdx

TEST SCAFFOLD: this paragraph exists only to give the docs engine's version-inheritance machinery something to override. Ctrl Plane has no such release; preview is a test fixture added in Task 11 solely so the e2e suite has two real versions to switch between, and this is the one page in it that departs from what v1 inherits. See docs/docs-engine.md for how inheritance actually resolves.

This guide walks you through adding Ctrl Plane to a Go project, wiring up a Docker provider, and creating an instance through the HTTP API.

Prerequisites01

  • Go 1.22 or later

  • Docker running locally (for this example)

Install02

Add the module to your project:

go get github.com/xraph/ctrlplane@latest

Minimal server03

Create a main.go that sets up a Ctrl Plane with an in-memory store and a 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()

    // In-memory store for development. Use postgres for production.
    memStore := memory.New()

    // Docker provider talks to the local Docker daemon.
    dockerProv, err := docker.New(docker.Config{
        Host:      "unix:///var/run/docker.sock",
        Namespace: "ctrlplane",
    })
    if err != nil {
        log.Fatal(err)
    }

    // Wire everything together.
    cp, err := app.New(
        app.WithStore(memStore),
        app.WithAuth(auth.NewNoopProvider()),
        app.WithProvider("docker", dockerProv),
        app.WithDefaultProvider("docker"),
    )
    if err != nil {
        log.Fatal(err)
    }

    // Start background workers (health checks, reconciliation, etc.)
    if err := cp.Start(ctx); err != nil {
        log.Fatal(err)
    }
    defer cp.Stop(context.Background())

    // Mount the HTTP API.
    handler := api.New(cp).Handler()

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           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)
    }
}

Run it:

go run main.go

Create a tenant04

Every operation in Ctrl Plane is scoped to a tenant. Create one first:

curl -s -X POST http://localhost:8080/v1/admin/tenants \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "slug": "acme",
    "plan": "pro"
  }' | jq .

Note the id in the response -- you'll need it as the tenant_id for subsequent requests.

Create an instance05

With a tenant in place, create an instance:

curl -s -X POST http://localhost:8080/v1/instances \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "ten_YOUR_TENANT_ID_HERE",
    "name": "web-app",
    "image": "nginx:alpine",
    "provider_name": "docker",
    "resources": {
      "cpu_millis": 500,
      "memory_mb": 256
    },
    "ports": [
      {"container_port": 80, "protocol": "tcp"}
    ]
  }' | jq .

The instance starts in the provisioning state and transitions through starting to running as the Docker container comes up.

Check instance status06

curl -s http://localhost:8080/v1/instances/inst_YOUR_INSTANCE_ID | jq .state

Deploy a new version07

Push a new image to a running instance:

curl -s -X POST http://localhost:8080/v1/instances/inst_YOUR_INSTANCE_ID/deploy \
  -H "Content-Type: application/json" \
  -d '{
    "image": "nginx:latest",
    "strategy": "rolling",
    "notes": "upgrade to latest nginx"
  }' | jq .

Next steps08