Build And Deploy

Deployment Checklist

Run a built GoLazy binary with production address, assets, and smoke checks.

By Guillermo Alvarez - Published - Updated

Run the binary

Build the app:

go build -o .tmp/build/sample-app ./cmd/app

Run it with the production address:

ADDR=0.0.0.0:8080 .tmp/build/sample-app

ListenAndServe reads ADDR, then PORT, then defaults to 127.0.0.1:3000.

Set CONTROL_PLANE_ADDR when the deployment platform expects operational endpoints on a separate listener:

ADDR=0.0.0.0:8080 CONTROL_PLANE_ADDR=127.0.0.1:9090 .tmp/build/sample-app

That activates the default control plane with /livez and /readyz. Read Control Plane when you need readiness checks, metrics, or pprof.

Enable Kubernetes observability

When a Kubernetes deployment should expose GoLazy metrics, run the app with a control-plane listener and Prometheus telemetry enabled:

env:
  - name: CONTROL_PLANE_ADDR
    value: "0.0.0.0:3001"
  - name: OTEL_METRICS_EXPORTER
    value: "prometheus"

Add a named control container port for that listener and point probes at it. If the cluster uses Prometheus Operator, add a PodMonitor that selects the app pods and scrapes the control port at /metrics. Pod annotations such as prometheus.io/scrape: "true" are useful only for annotation-based scrape configs.

For ingress-nginx tracing, set nginx.ingress.kubernetes.io/enable-opentelemetry: "true" on the application Ingress, then configure otlp-collector-host, otlp-collector-port, otel-sampler, and otel-sampler-ratio on the ingress controller ConfigMap. Those collector and sampler settings are controller configuration, not Ingress annotations. Read Telemetry for the complete Kubernetes example.

Provide secrets through the platform

Application code reads environment variables:

key := os.Getenv("SECURE_COOKIE_KEY")

Production deployments should provide those variables through the hosting platform or secret store. Development files such as secrets/development.env are examples, not production configuration.

Unpack assets when needed

Most deployments can serve assets from the Go binary. If a platform needs static files beside the app, use the asset registry:

if err := appinit.App().Assets.Unpack(
    "public",
    lazyassets.WithUnpackMode(lazyassets.UnpackPermanent),
); err != nil {
    log.Fatal(err)
}

UnpackBoth writes logical and permanent paths. UnpackLogical writes only logical paths. UnpackPermanent writes only content-hashed permanent paths.

Upload assets to object storage

For deployments that serve assets from object storage or a filer-backed ingress, upload the registered assets from the built app:

storage := s3.New(
    s3.WithEndpoint(os.Getenv("ASSETS_S3_ENDPOINT")),
    s3.WithBucket(os.Getenv("ASSETS_S3_BUCKET")),
    s3.WithCredentials(
        os.Getenv("ASSETS_S3_ACCESS_KEY_ID"),
        os.Getenv("ASSETS_S3_SECRET_ACCESS_KEY"),
    ),
    s3.WithPublicBaseURL(os.Getenv("ASSETS_PUBLIC_URL")),
)

if err := appinit.App().Assets.Upload(
    context.Background(),
    storage,
    lazyassets.WithUploadMode(lazyassets.UnpackPermanent),
); err != nil {
    log.Fatal(err)
}

Run this from the built binary or container image before starting the HTTP server so the exported hashed files match the embedded application assets.

Smoke test the deployment

After starting the deployed process, request the app and one public asset:

curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/styles.css
curl -i http://127.0.0.1:9090/livez
curl -i http://127.0.0.1:9090/readyz

Check that the app route returns HTML and the asset response includes cache headers and an ETag. If CONTROL_PLANE_ADDR is not set, production builds do not expose /livez or /readyz on the public app listener.

Build the sample container

The generated full app includes a Dockerfile for single-binary container deployments. Refresh generated assets first, then build and run the image:

lazy tailwind
lazy js
docker build -t sample-app .
docker run --rm -p 127.0.0.1:3000:3000 sample-app

The container listens on 0.0.0.0:3000 through ADDR. Provide production secrets such as SECURE_COOKIE_KEY through the container environment.

Keep platform concerns outside GoLazy

GoLazy leaves TLS termination, process supervision, log shipping, service discovery, and platform routing to the deployment environment. The application binary should stay a normal Go HTTP process.