Dockerfile Explained: Every Line of a Simple Dockerfile
A Dockerfile is the recipe Docker follows to build an image. A line-by-line walkthrough of a real Dockerfile — FROM, WORKDIR, COPY, RUN, ENV, EXPOSE, CMD — why the order matters for caching, .dockerignore, and the beginner mistakes that make images slow, huge or insecure.
A Dockerfile is a text file of instructions Docker follows to build an image — a packaged copy of your app with everything it needs to run. (What is Docker?, Docker image vs container)
Here's a simple, sensible Dockerfile for a Node.js app, explained line by line.
FROM node:24-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "server.js"]
FROM — the starting point
FROM node:24-slim
Every image builds on another. node:24-slim is an official image with Node.js 24 on a small Debian Linux. For Python you'd use python:3.13-slim.
- Pin a version (
24-slim), notlatest, so builds don't change unexpectedly. -slimand-alpinevariants are much smaller than the default. Alpine is smallest but uses a different C library, which occasionally breaks packages with native code.
WORKDIR — where things happen
WORKDIR /app
Sets the folder inside the image for the following commands (and creates it). Like cd, but it sticks.
COPY the dependency list first
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
Why copy these two files separately, before the rest? Caching.
Docker caches each step. If a step's inputs haven't changed, it reuses the cached result. Your dependencies change rarely; your code changes constantly. Copying just the package files first means npm ci is re-run only when dependencies change. Copy everything first and every code change triggers a full reinstall.
npm ci installs exactly what's in the lock file; --omit=dev skips development-only packages. (package-lock.json explained)
RUN — execute a command at build time
RUN runs a command while building the image — installing packages, compiling, creating folders. Each RUN creates a new layer. (Docker image layers)
COPY the rest
COPY . .
Copies your project into /app. Combined with a .dockerignore file, so junk and secrets stay out:
node_modules
.git
.env
dist
*.log
Without it, you copy your local node_modules (wrong OS, wrong architecture) and possibly your .env with production secrets into the image. (exec format error)
ENV — environment variables
ENV NODE_ENV=production
Sets a variable available at build and run time. Don't put secrets here — they're stored in the image for anyone who has it. Pass secrets at run time instead (docker run -e, Compose .env). (Environment variables explained)
EXPOSE — documentation
EXPOSE 3000
Says which port the app listens on. It doesn't actually publish the port — you still do that with -p 3000:3000. Your app must listen on 0.0.0.0, not localhost, to be reachable from outside the container.
USER — don't run as root
USER node
By default containers run as root. The official Node image includes a node user; switching to it limits the damage if the app is compromised. (Container hardening)
CMD — what runs when the container starts
CMD ["node", "server.js"]
The command the container runs. Use the JSON array form so the process receives shutdown signals properly. (PID 1 in containers)
RUN = at build time. CMD = at start time. Mixing them up is a classic beginner bug.
Build and run
docker build -t myapp .
docker run -p 3000:3000 --env-file .env myapp
Going further
- Multi-stage builds — build in one stage (with dev dependencies and compilers), copy only the output into a small final stage. Much smaller images. (Reduce Docker image size)
- A fuller production example for Node with health checks. (Production Dockerfile for Node.js)
EasySpawn can build and run your app from its Dockerfile on your own server — or run it without one — with HTTPS, a database and backups around it. See how it works or join the waitlist.
Related: What Is Docker? · Docker Image vs Container · Writing a Production Dockerfile for Node.js · How to Reduce Docker Image Size
Keep reading
"exec format error" in Docker: ARM vs x86 Images Explained
exec format error almost always means a container image built for one CPU architecture (ARM, like Apple Silicon Macs) is running on another (x86/amd64 servers), or vice versa. How to check, build for the right platform with --platform and buildx, and the other cause: scripts without a shebang or with Windows line endings.
What Is Vercel? What It Does, What It Costs You, and When to Use Something Else
Vercel is a hosting platform built around frontend frameworks, especially Next.js: push to GitHub and your site is live. What it actually does, what serverless functions mean for your app, where the limits are, and when a regular server is a better fit.