Skip to content
Galley

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.