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.
During migrations
When LAZYAPP_MIGRATE=up or LAZYAPP_MIGRATE=auto is set and
CONTROL_PLANE_ADDR is configured on a separate listener, lazyapp.New starts
the real control plane before applying migrations. During that phase /livez
returns 200 OK and /readyz returns 503 with a migration-in-progress message.
In auto mode, the listener stays active after migrations finish; later startup
stages add jobs, metrics, and development handlers to that same control plane,
and ListenAndServe starts only the public app listener. In up mode, GoLazy
closes the early control-plane listener before the process exits.
When CONTROL_PLANE_ADDR resolves to the app listener, migrations do not touch
the control plane. The app waits until migrations finish, then ListenAndServe
starts the app listener and mounts the shared control plane normally.
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.