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@latestMinimal 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.goCreate 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 .stateDeploy 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
Read the Architecture overview to understand how the packages fit together.
Learn about TypeID identifiers and entities.
Explore deployment strategies in detail.
Set up a Forge extension if you're building on Forge.