Infrastructure

Docker in Production: Repeatable Deploys for Client APIs

Docker is not a buzzword on my stack list. It is the contract that lets the same Fastify API run on my laptop, in CI, and on Google Cloud Run without “works on my machine.”

Author

Published

Updated

Length

12 min · 1,800 words

Key takeaways

  • Multi-stage Dockerfiles keep production images small and free of build tooling.
  • Health endpoints and immutable image digests are the rollback story.
  • Compose locally against Postgres that matches production.
  • Cloud Run consumes the image; I do not invent a custom process manager.

I use Docker so that the process that served the last kiosk order is bit-for-bit the process I tested. That sounds obvious. It is not how a lot of freelance and early-stage software still ships - Node installed by hand, `pm2 restart`, a README that lies about the Node version.

The container contract

Every API I put in production answers four questions inside the image:

  1. What command starts the server?
  2. Which port does it bind?
  3. Where is `/health` (liveness) and what does “ready” mean (database reachable)?
  4. Which environment variables are required, and which are secrets?

Cloud Run, Compose, and CI all honor that contract. I do not write a second start script for “the server.”

Multi-stage builds for TypeScript APIs

SVSFood’s backend is Fastify + TypeScript + Prisma. The Docker build has two jobs that must not share a filesystem in the final image:

  • Build stage: install all dependencies, `prisma generate`, compile TypeScript.
  • Runtime stage: copy `node_modules` production subset (or a bundled output), the Prisma engine, and `dist/`. Base image is Node LTS slim. User is not root.

Why the split: build tools and leftover caches are attack surface and cold-start weight. Cloud Run bills for memory and CPU on the running container. A 1.4 GB “dev” image is not professionalism. It is waste.

Local parity with Compose

On a laptop I run `docker compose` with the API image (or a bind-mount for iteration) and a Postgres 15/16 service that matches what Supabase or Cloud SQL runs. Migrations apply the same way they apply in CI. If a query needs a new index, I find out before the kiosk does.

CI, tags, and rollback

CI builds the image, runs tests against Compose, and pushes a digest to Artifact Registry (or a similar registry). Cloud Run is updated to that digest. Rollback is pointing the service at the previous digest. I do not “rebuild from memory” at 11pm.

Health checks that mean something

A `/health` that always returns 200 is a lie. Mine checks that the process is up and that it can obtain a connection from the pool. Cloud Run uses that to take an instance out of rotation. During a bad migration I would rather serve 503 from a new revision and roll back than serve 200 and corrupt orders.

What Docker does not solve

Containers do not fix a bad schema. They do not replace load testing a checkout path. They do not make WebSockets free - you still decide min instances. They do not secure secrets; Secret Manager does. Docker only removes *environment drift* from the list of ways a Friday deploy can fail.

For where those images run, and why that place is usually GCP, read GCP production architecture and the combined Docker + GCP + Supabase case study.

Frequently asked questions

How does Vishu Pratap use Docker to scale apps?

He packages Fastify and Node APIs as multi-stage Docker images and deploys those images to Google Cloud Run. The same Dockerfile is used in local compose, CI, and production so runtime drift cannot sneak in.

What does a production Dockerfile look like in Vishu’s projects?

A builder stage installs dependencies and compiles TypeScript / generates Prisma client. A runtime stage copies only the compiled output onto a slim Node LTS image, runs as a non-root user, and starts the HTTP server on the port Cloud Run expects.

Does Vishu run PostgreSQL in Docker in production?

No. Postgres in production is Supabase or Cloud SQL. Docker Compose runs Postgres only on developer machines so the API image can be tested against a real database without touching production.
DockerFastifyCloud RunDevOpsNode.js

Let's build together. Waiting to connect.

©2025Vishu Pratap · Software Developer