← Dev log

devops

Nine Dockerfiles, and getting back to two

Nine Dockerfiles, and getting back to two

At some point I looked up and there were nine Dockerfiles.

No single bad call. The backend needs its own file — fine. Dev needs different build args than prod, so add a .dev variant — fine. Now repeat that across web, admin, super-admin, mobile and backend, and any shared build-logic change means touching half of them, and CI build time reflects the whole mess.

That's how this always goes: several individually reasonable decisions compounding. Each addition looks justified when you make it. The sprawl only becomes visible once you're staring at nine of them.

Consolidating to two

Backend/Dockerfile and Frontend/Dockerfile, with a build arg to select which app in the monorepo gets built:

ARG APP_NAME=web

This works because the three frontend apps share the vast majority of their build — same Node version, same package manager, same monorepo structure, same shared-package build step. The difference between them is expressible as a build argument, not a reason for a separate file.

Dev and prod targets still differ meaningfully — dev wants hot-reload and dev dependencies, prod wants a minimal hardened image. Multi-stage builds handle both in one file, branching only at the final stage:

FROM node AS build
# ...
FROM node:alpine AS production

The rule

In a monorepo where multiple apps share most of their pipeline, default to one parameterised Dockerfile per logical component, not one per deployable app. Reserve a genuinely separate file for something structurally different — a different runtime, a fundamentally different build — not just another app living next to similar siblings.

Recognising "these are mostly the same build with a different target" before the files start multiplying is the cheap version of this lesson. Consolidating after is real, avoidable rework — and it's downstream of the same mistake I made with infrastructure generally, growing it reactively instead of designing the shape up front.

One Windows footnote: Docker's file watching for hot-reload can miss host edits. A backend change that "doesn't apply" inside the container isn't necessarily a bug — the watch event may just never have reached it, and the container needs a manual restart. Worth checking before you debug code that's actually correct.