What Is a Dockerfile and How to Write One
SkillVeris Team
Cloud & Security Team

A Dockerfile is a text file of instructions that Docker follows step by step to build a container image.
In this guide, you'll learn:
- Each instruction creates a cached layer, so ordering them well makes rebuilds dramatically faster.
- Core instructions are FROM, WORKDIR, COPY, RUN, EXPOSE, and CMD, run top to bottom.
- Copy dependency files and install before copying your source to get the most out of build caching.
- Multi-stage builds keep the final image small by leaving build tools behind.
1What Is a Dockerfile?
A Dockerfile is a plain text file containing a sequence of instructions that Docker executes in order to assemble a container image. Think of it as a repeatable recipe: it starts from a base image, adds your code and dependencies, and specifies how the container should run.
Because the build is scripted, anyone with the Dockerfile can produce the exact same image. That reproducibility is the whole point — the environment stops being something you set up by hand and becomes something you build from code.
2How a Build Works
When you run docker build, Docker reads the Dockerfile top to bottom, executing each instruction and saving the result as a layer. Stacked together, those layers form the final image.
- Each instruction produces a new read-only layer on top of the previous one.
- Docker caches each layer and reuses it if nothing that affects it has changed.
- If a layer changes, that layer and every layer after it must be rebuilt.
- The final image is the stack of all layers plus a thin writable layer at runtime.
🔑Key Idea
Layer caching is the single biggest lever on build speed. Order instructions so the things that rarely change come first and the things that change often (your source code) come last.
3Core Instructions
A handful of instructions appear in nearly every Dockerfile. Learn these and you can read and write most of them.
- FROM = the base image to build on (e.g. python:3.12-slim).
- WORKDIR = set the working directory for later instructions.
- COPY = copy files from your machine into the image.
- RUN = execute a command at build time (install dependencies).
- EXPOSE = document the port the app listens on.
- CMD = the default command to run when the container starts.
CMD vs RUN
RUN executes during the build to create the image — for example installing packages. CMD does not run at build time; it defines what happens when the finished container starts. Mixing them up is a common beginner error.
4A Complete Example
Here is a practical Dockerfile for a Python web app. Notice the ordering: dependencies are installed before the source is copied, so editing your code does not bust the dependency cache.
- FROM python:3.12-slim
- WORKDIR /app
- COPY requirements.txt .
- RUN pip install --no-cache-dir -r requirements.txt
- COPY . .
- EXPOSE 8000
- CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
💡Pro Tip
Copy requirements.txt and install dependencies before copying the rest of your code. Since dependencies change less often than source, Docker reuses the cached install on most rebuilds, saving significant time.
5Making the Most of Layer Caching
Build caching is what separates a two-second rebuild from a two-minute one. The rule is simple: put stable instructions high and volatile ones low.
When you change your source code, Docker only needs to rebuild from the COPY . . step onward — the base image and installed dependencies above it are reused from cache. Reverse that order and every code edit reinstalls all dependencies.
- Put FROM and dependency installation near the top.
- Copy only the dependency manifest first, then install.
- Copy the full source afterward, since it changes most often.
- Combine related RUN commands to reduce layer count where sensible.
6Multi-Stage Builds
A multi-stage build uses more than one FROM to separate the build environment from the final runtime image. You compile or install in a build stage, then copy only the finished artifacts into a slim final stage.
The result is a much smaller image because compilers, build tools, and dev dependencies are left behind. Smaller images pull faster, start quicker, and have a smaller attack surface.
- FROM node:20 AS build
- WORKDIR /app
- COPY . .
- RUN npm ci && npm run build
- FROM nginx:alpine
- COPY --from=build /app/dist /usr/share/nginx/html
7Best Practices
A few habits produce Dockerfiles that build fast and run safely.
- Use small base images like -slim or -alpine variants where possible.
- Add a .dockerignore so build context stays small and secrets are excluded.
- Pin image tags (python:3.12-slim, not python:latest) for reproducible builds.
- Run as a non-root user with a USER instruction for better security.
- Use multi-stage builds to keep the final image lean.
⚠️Watch Out
Never bake secrets like API keys or passwords into a Dockerfile with COPY or ENV. They persist in the image layers and can be extracted by anyone who pulls it. Pass secrets at runtime instead.
8Common Mistakes to Avoid
Most Dockerfile problems come from a short list of habits.
- Copying all source before installing dependencies, so every edit rebuilds everything.
- Using a full base image when a slim variant would do, bloating the image.
- Forgetting a .dockerignore, sending node_modules and secrets into the build context.
- Baking secrets into layers with ENV or COPY where they can be recovered.
9Key Takeaways
The essentials of writing a Dockerfile come down to a few points.
- A Dockerfile is a scripted recipe that builds a reproducible container image.
- Each instruction is a cached layer; order stable steps first, volatile ones last.
- Core instructions are FROM, WORKDIR, COPY, RUN, EXPOSE, and CMD.
- Install dependencies before copying source to maximise cache reuse.
- Use multi-stage builds, slim base images, and a non-root user for small, secure images.
10Frequently Asked Questions
Q: What is the difference between an image and a container? A: An image is the built, read-only template produced from a Dockerfile. A container is a running instance of that image. One image can start many containers, just as one class can create many objects.
Q: What is the difference between CMD and RUN? A: RUN executes during the build to create the image, such as installing packages. CMD defines the default command that runs when the container starts. RUN shapes the image; CMD shapes runtime behaviour.
Q: Why is my Docker build so slow? A: Usually because instructions are ordered badly. If you copy all source before installing dependencies, any code change invalidates the cache and reinstalls everything. Copy the dependency manifest and install first.
Q: What is a multi-stage build? A: It uses multiple FROM stages so you can build in one environment and copy only the finished artifacts into a slim final image, leaving compilers and dev tools behind for a smaller, safer result.
Related Reading
Get The Print Version
Download a PDF of this article for offline reading.
About the Publisher
SkillVeris Team
Cloud & Security Team
Our cloud and security experts break down complex infrastructure topics into practical, beginner-friendly guides.
View all postsRelated Posts
Never miss an update
Get the latest tutorials and guides delivered to your inbox.
No spam. Unsubscribe anytime.