Build And Deploy

Control Plane

Expose liveness, readiness, metrics, and Go diagnostics outside application routes.

By Guillermo Alvarez - Published - Updated

Enable probes in app config

Generated apps do not include same-handler control-plane routes by default. Add an empty control-plane config when the app should expose framework-owned operational endpoints on the same handler as the app:

return lazyapp.New(lazyapp.Config{
    Name:         "sample_app",
    Drawer:       Draw,
    Public:       app.Public,
    Views:        app.Views,
    Dependencies: Dependencies,
    ControlPlane: lazycontrolplane.Config{},
})

The empty config exposes:

GET /livez
GET /readyz

When the control plane is mounted on the app listener, control-plane routes are checked before application routes. If the app also draws /livez, the control-plane route wins.

Use a separate address

ListenAndServe activates the default control plane when CONTROL_PLANE_ADDR is set:

ADDR=0.0.0.0:8080 CONTROL_PLANE_ADDR=127.0.0.1:9090 ./app

When CONTROL_PLANE_ADDR differs from the app address, ListenAndServe starts a second HTTP server for the control plane. The public app server then serves only application traffic.

If CONTROL_PLANE_ADDR matches ADDR, PORT, or the default 127.0.0.1:3000, GoLazy mounts the control plane into the app server instead of trying to bind the same port twice:

CONTROL_PLANE_ADDR=3000 ./app

If CONTROL_PLANE_ADDR is unset, production ListenAndServe does not mount control-plane endpoints on the public app listener, even when the app config builds a control plane. This keeps liveness, readiness, metrics, and diagnostics off public routes by default.

Add readiness checks

Readiness checks receive the request context and return an error when the app is not ready:

ControlPlane: lazycontrolplane.Config{
    Readiness: []lazycontrolplane.ReadinessCheck{{
        Name: "database",
        Check: func(ctx context.Context) error {
            return db.PingContext(ctx)
        },
    }},
},

/readyz returns 503 Service Unavailable when a check fails.

Attach metrics and pprof

Metrics are opt-in:

ControlPlane: lazycontrolplane.Config{
    Metrics: metricsHandler,
},

That registers GET /metrics. GoLazy does not add a Prometheus dependency for you; pass a handler from the exporter you choose.

Go diagnostics are also opt-in:

ControlPlane: lazycontrolplane.Config{
    Pprof: true,
},

That registers /debug/pprof/ and the standard pprof subpaths. Prefer a separate CONTROL_PLANE_ADDR before enabling diagnostics in production.

Serve manually

If you need a custom server lifecycle, instantiate the control plane yourself:

plane := lazycontrolplane.New(lazycontrolplane.Config{})
server := &http.Server{
    Addr:    "127.0.0.1:9090",
    Handler: plane,
}

When you manage the server manually, lazyapp.ListenAndServe is not responsible for starting that control-plane server.

Development endpoints

Apps started by lazy are built with the lazydev tag and receive a separate internal CONTROL_PLANE_ADDR. In that mode GoLazy adds development-only endpoints such as GET /routes, GET /cache, POST /cache/on, POST /cache/off, POST /views, and POST /_golazy/open-editor.

Those endpoints are not part of production builds. Read Lazydev Mode for the development-only surface.