πŸ“š Learning Hub
Β· 3 min read

Code Review This: A Dockerfile That Produces a 2GB Image


This Dockerfile builds and runs a Node.js application. It works. It also produces a 2.1GB image. Here are 10 things wrong with it.

The Dockerfile

FROM ubuntu:latest

RUN apt-get update
RUN apt-get install -y nodejs npm python3 build-essential curl wget git vim

COPY . /app
WORKDIR /app

RUN npm install
RUN npm run build

ENV NODE_ENV=production
ENV DATABASE_URL=[postgresql](/blog/what-is-postgresql/)://admin:secretpass@db.example.com:5432/prod
ENV API_KEY=sk-1234567890abcdef

EXPOSE 3000

USER root

CMD npm start

Find the problems, then check below.


The 10 problems

1. πŸ“¦ Using Ubuntu instead of Alpine

# ❌ Ubuntu base: ~75MB
FROM ubuntu:latest

# βœ… Node Alpine: ~50MB, includes Node.js
FROM node:20-alpine

Ubuntu doesn’t even include Node.js. You’re installing it manually on top of a larger base image.

2. πŸ“Œ Using latest tag

# ❌ "latest" changes without warning
FROM ubuntu:latest

# βœ… Pin to a specific version
FROM node:20-alpine

Your build might work today and break tomorrow when latest updates.

3. πŸ”§ Installing unnecessary packages

# ❌ vim, wget, git, python3 in a production image?
RUN apt-get install -y nodejs npm python3 build-essential curl wget git vim

# βœ… Only what you need (and with Alpine, Node is already there)
# If you need build tools for native modules:
RUN apk add --no-cache python3 make g++

Every extra package increases image size and attack surface.

4. πŸ“š Too many RUN layers

# ❌ Each RUN creates a layer
RUN apt-get update
RUN apt-get install -y ...

# βœ… Combine and clean up in one layer
RUN apt-get update && \
    apt-get install -y --no-install-recommends nodejs npm && \
    rm -rf /var/lib/apt/lists/*

5. πŸ“‹ COPY before dependency install (cache busting)

# ❌ Any file change invalidates npm install cache
COPY . /app
RUN npm install

# βœ… Copy package files first, install, then copy source
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

This way, npm ci is cached unless package.json changes.

6. πŸ“¦ Using npm install instead of npm ci

# ❌ npm install can modify package-lock.json
RUN npm install

# βœ… npm ci uses lockfile exactly, faster, deterministic
RUN npm ci --only=production

7. πŸ”‘ Secrets in environment variables

# ❌ Secrets baked into the image (visible with docker inspect)
ENV DATABASE_URL=postgresql://admin:secretpass@db.example.com:5432/prod
ENV API_KEY=sk-1234567890abcdef

# βœ… Pass at runtime
# docker run -e DATABASE_URL=... myapp

Anyone with access to the image can extract these secrets.

8. πŸ—οΈ No multi-stage build

# ❌ Dev dependencies and build tools in production image

# βœ… Multi-stage: build in one stage, run in another
FROM node:20-alpine AS builder
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

9. πŸ‘€ Running as root

# ❌ Container runs as root
USER root

# βœ… Create and use a non-root user
RUN addgroup --system app && adduser --system --ingroup app app
USER app

If the container is compromised, the attacker has root access.

10. 🐚 Using shell form for CMD

# ❌ Shell form: runs via /bin/sh, doesn't receive signals properly
CMD npm start

# βœ… Exec form: process receives SIGTERM for graceful shutdown
CMD ["node", "dist/index.js"]

Shell form means your app won’t shut down gracefully in Kubernetes.

The fixed Dockerfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/index.js"]

Image size: ~180MB instead of 2.1GB. Build time: ~30 seconds instead of 10 minutes. No secrets baked in. Non-root user. Proper signal handling.

Related: Docker Cheat Sheet Β· Code Review Sql Query

πŸ“˜