Layer caching, build context, and multi-stage builds — trim a bloated image down fast.
Docker images grow large for three common reasons: a heavy base image, unnecessary files baked into a layer, and build tools that belong in the build stage but end up in production.
A FROM ubuntu base pulls in the entire Ubuntu userland. Most Go or Node apps need a fraction of that. Start from alpine or a distroless image and the base drops from 80MB to under 10MB.
Every COPY . . in your Dockerfile copies your entire build context — including node_modules, .git, test fixtures, and local secrets. A well-tuned .dockerignore is not optional.
Dockerfile# Bad: build tools ship to production FROM node:20 COPY . . RUN npm install && npm run build # Good: two stages, only the artifact moves forward FROM node:20-alpine AS build COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html
QUICK WINS Multi-stage builds + a tight .dockerignore will cut most images by 60–90%.
Layer order matters too: put instructions that change rarely (installing dependencies) before instructions that change often (copying source). Docker’s cache reuses unchanged layers, so a cache miss on COPY . . doesn’t force a reinstall.