Build And Deploy
Control Plane
Expose liveness, readiness, metrics, and Go diagnostics outside application routes.
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
Applications that configure lazyapp.Config.Jobs also expose read-only job
state at GET /jobs on the control plane. The endpoint reports registered job
definitions, state counts, and recent jobs.
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. GET / on the control-plane listener shows a compact
HTML index of the registered control-plane endpoints. GoLazy automatically
registers /debug/pprof/ and the standard pprof subpaths on that separate
control-plane listener.
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. In that mode the control-plane index is
not mounted, so application / routes keep their normal behavior:
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 automatic when CONTROL_PLANE_ADDR points at a separate
listener. For same-listener or manually served control planes, opt in with
config:
ControlPlane: lazycontrolplane.Config{
Pprof: true,
},
That registers /debug/pprof/ and the standard pprof subpaths. Do not expose
pprof on a public application listener.
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.StandaloneHandler(),
}
When you manage the server manually, lazyapp.ListenAndServe is not responsible
for starting that control-plane server. Use plane directly or
plane.Handler(next) instead when the control plane shares an application
listener and / should remain application-owned.
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, GET /jobs, GET /buildinfo, POST /views, and
POST /_golazy/open-editor.
Those endpoints are not part of production builds. Read Lazydev Mode for the development-only surface.