Learn / Prove it — practice ladder

Dockerize and automate an existing project

Lesson 36 of 37 · 8 min read ·

The brief

Take an existing project that currently runs with npm run dev and a database you installed by hand, and get it to: build as a container, run locally with one command, test automatically on every pull request, and deploy on merge to main.

The bar is specific — a new developer clones the repository and has it running in under five minutes without asking you anything.

Step 1: Dockerfile

  • Multi-stage: build with the full toolchain, ship only the artefact. See containers.
  • Dependency manifests copied and installed before source, so a source change does not reinstall dependencies.
  • .dockerignore with node_modules, .git, .env, build output. Check what you are actually sending: docker build prints the context size.
  • Pinned base image tag. Not latest.
  • USER set to a non-root user.
  • Handles SIGTERM — drains connections and exits, rather than being killed.
  • Config entirely from environment variables. No baked-in secrets, no build-time API keys.

Target: rebuild after a source-only change in under 30 seconds, and an image measured in tens of megabytes rather than gigabytes.

Step 2: Compose for local development

  • docker compose up starts the app, the database and anything else it needs.
  • Database data in a named volume so it survives a restart.
  • Healthcheck on the database, and depends_on: condition: service_healthy so the app does not start against a database that is not ready.
  • Source mounted for hot reload in development.
  • .env.example committed with every variable and dummy values.
  • Migrations run automatically on start, or via one documented command.

Step 3: CI on every pull request

Ordered cheapest-first so failures come back fast:

  • Lint and type check.
  • Unit tests.
  • Build the image.
  • Integration tests against real services in containers.
  • Dependency audit and secret scan.
  • Dependency cache between runs.
  • Branch protection: no merging to main without green checks.

Target: under ten minutes from push to green. Beyond that people start working around it, and a pipeline people work around is worse than none. See CI/CD.

Step 4: Deploy on merge

  • Build the image once, tag it with the commit SHA, push to a registry.
  • Deploy that exact tag — never rebuild per environment.
  • Migrations run before the new version takes traffic, and are backward compatible so the old version survives until it is replaced.
  • Smoke test after deploy: hit /health and one real endpoint; fail the pipeline if either fails.
  • A documented rollback, which is redeploying the previous SHA. Do it once for real so you know it works and how long it takes.
  • Deployment secrets in the CI provider's secret store, never in the workflow file.

The test

Wipe your local environment — docker compose down -v, delete node_modules. Then follow your own README exactly as written, without using anything you know that is not on the page.

Every point where you have to improvise is a missing line in the README. Fix it and repeat until the run is clean. Better still, hand it to someone else and watch without helping. It is uncomfortable and it is the fastest way to find every hidden assumption.

Prove you know it

Time three numbers: clone to running locally, push to green CI, merge to live in production. Then roll back a deploy and time that too. Those four numbers are the honest measure of the pipeline — and they are the numbers a team lead will ask you about.

Go deeper