Docker streamlines how teams build and run applications by packaging code and dependencies into lightweight, portable containers. Understanding how to write a basic Dockerfile is the fastest way to start using Docker effectively in everyday projects.
This guide walks through practical patterns and examples that help you create reliable images quickly, without unnecessary complexity or guesswork.
| Instruction | Description | Impact on Image | Best Practice |
|---|---|---|---|
| FROM | Sets the base image for subsequent instructions | Defines the starting point and size of your image | Use official, minimal, and specific-tagged bases |
| WORKDIR | Defines the working directory inside the container | Creates a predictable path for later commands | Create and switch to a dedicated directory early |
| COPY | Copies files from your host into the image | Adds layers; misuse can bloat the image | Leverage .dockerignore and copy only needed files |
| RUN | Executes commands during build | Creates new layers and affects image size | Combine commands and clean up in the same RUN |
Writing a Basic Dockerfile from Scratch
Start with a Minimal Base Image
Choosing a small base image reduces vulnerabilities and speeds up builds and deployments. Alpine images are popular for their tiny footprint, but you should match the runtime needs of your application rather than defaulting to the smallest option available.
Set a Stable Working Directory
Use WORKDIR to establish a consistent path inside the container. This instruction creates directories if they do not exist and makes later commands predictable and easier to read.
Placing WORKDIR near the top of the Dockerfile reduces repetition and helps other developers immediately understand where the application lives on the filesystem.
Optimizing Build Performance and Caching
Layer Caching Strategies
Docker caches each instruction as a layer so that unchanged steps are reused in later builds. Installing dependencies before copying source code allows Docker to skip expensive reinstallation when only application code changes.
Minimize Image Bloat
Removing temporary files and cleaning package manager caches in the same RUN instruction keeps layers lean. Each RUN line creates a new layer, so combining commands and cleanup prevents unnecessary image growth.
Security and Maintainability Practices
Use Explicit Base Image Tags
Avoid relying on the default latest tag in production environments. Pinning a specific version or digest improves reproducibility and reduces surprises when base images are updated upstream.
Reduce Privileges at Runtime
Even when building as root, you can create a nonroot user and switch to it before runtime with USER. Running as a nonroot user limits the impact of potential container escapes and aligns with security best practices.
Essential Dockerfile Practices
- Choose a small, official base image and pin its version
- Define WORKDIR early to establish a consistent path inside the container
- Copy dependency files first and install dependencies before source changes
- Combine RUN instructions and clean caches to keep images lean
- Use multi-stage builds to separate build-time tools from runtime images
- Run services as a nonroot user in production containers
- Pin base image tags and periodically update dependencies securely
FAQ
Reader questions
How do I copy only the files I need in a basic Dockerfile?
Create a .dockerignore file that lists node_modules, .git, *.log, and other unnecessary artifacts, then copy only essential files such as package.json and your application source into the image.
Should I install build tools in the same image as my application?
For production, prefer multi-stage builds where you install compilers and build dependencies in an intermediate stage and copy only the final artifacts into a smaller runtime image.
What is the best base image for a simple Python web service?
Use an official, version-pinned Python slim image, set WORKDIR, copy requirements first to leverage caching, install dependencies, and then copy the rest of the application code.
How can I make my Docker container run as a nonroot user?
Add a nonroot user with useradd, switch to it using USER before your runtime command, and ensure files are owned by that user so the container does not require elevated privileges.