Stacks / Node
Preview environments for Node.
Node or Bun frontends and APIs with Postgres and Redis.
- Node 20
- Postgres 16
- Redis 7
Node apps — Express, Hono, Fastify, Next.js — are the best-tested preview flow in Galley. Container-friendly by default, build graphs are shallow, everything you’d expect to just work, does.
The config
version: 1
services:
web:
kind: web
build:
path: ./
expose: 3000
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app
REDIS_URL: redis://cache:6379/0
NODE_ENV: production
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
The web container reaches Postgres and Redis by their galley.yml names
on the env network — no IP wrangling, no compose link semantics to learn.
A kind: web service gets a public route (pr-N-myrepo.preview.yourco.dev)
plus screenshots once it’s healthy. The data services stay internal.
The usual gotcha
Dockerfile layer ordering. A naive COPY . . before RUN pnpm install
invalidates the install layer on every source change, so every preview
rebuild reinstalls node_modules from scratch.
The fix is to copy the lockfile first:
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
CMD ["pnpm", "start"]
If you don’t have a Dockerfile at all, omit it — Galley falls back to
language autodetect. It picks node:20, runs pnpm install, and uses
the start script. Workable for many apps; opt into a Dockerfile when
you need control over the image.
For Next.js specifically, use the standalone output mode — cuts the
final image from ~1.2 GB to ~180 MB and ships only the server’s runtime
files.