Stacks / Go
Preview environments for Go.
Go APIs with Postgres and Redis. Quick cold starts.
- Go 1.22
- Postgres 16
- Redis 7
Go services are among the friendliest stacks for preview environments. A single static binary, a modest Dockerfile, migrations that run in a few hundred milliseconds.
The config
version: 1
services:
api:
kind: api
build:
path: ./
expose: 8080
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app?sslmode=disable
REDIS_URL: redis://cache:6379/0
PORT: "8080"
health:
path: /healthz
status: 200
postgres:
kind: database
image: postgres:16-alpine
expose: 5432
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: pw
POSTGRES_DB: app
cache:
kind: cache
image: redis:7-alpine
expose: 6379
kind: api is the right choice for a JSON service: it gets a public
subdomain route (api.pr-N-myrepo.preview.yourco.dev) but skips
post-deploy screenshots — those are wasted bytes against a JSON endpoint.
Migrations: run them on container start (exec migrate-up && exec ./api)
or use a separate kind: worker service with restart: never that
exits 0 after applying the schema. Galley’s depends_on waits for the
worker to reach a healthy state before starting api.
The usual gotcha
Go build images balloon quickly. A plain FROM golang:1.22 lands at
~1.5 GB before your app is in it. The fix is a standard multi-stage
Dockerfile that copies the binary into a distroless final image:
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/api ./cmd/api
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
ENTRYPOINT ["/api"]
The trap is CGO_ENABLED=0. Skip it and distroless silently fails at
runtime with a confusing “file not found” on the binary — it’s a
dynamic-linker error from the glibc-vs-static mismatch, not a missing
binary. If your app touches net, crypto/x509, or time/tzdata, set
CGO_ENABLED=0 explicitly so you get the pure-Go implementations.