Containers have become the standard way to build, package, and deploy modern applications, and Docker is one of the most widely used containerization platforms. At the heart of every Docker image is a Dockerfile (A simple text file that contains the instructions needed to create a Docker image automatically).
Instead of manually installing dependencies and configuring your application every time, you can define the entire setup process in
In this guide, you'll learn what a Dockerfile is, why it's important, its basic syntax, the most commonly used instructions, and the best practices for writing efficient and maintainable Dockerfiles. By the end of this article, you'll be able to create your own Dockerfiles and build optimized Docker images with confidence.
What is Dockerfile?
A Dockerfile is a text file that contains a series of instructions for building a Docker image. It automates the image creation process by specifying the base image, application code, dependencies, environment variables, and commands needed to configure the application.
Instead of manually setting up an environment every time, developers define the entire build process in a Dockerfile. Docker reads the instructions in the file sequentially and creates an image layer by layer.
Why Use a Dockerfile?
A Dockerfile provides several advantages:- Automation: Eliminates manual configuration by automating image creation.
- Consistency: Ensures the same environment is created every time, regardless of the host system.
- Version Control: Since a Dockerfile is plain text, it can be stored in a version control system such as Git.
- Reproducibility: Enables anyone to recreate the exact same Docker image using the Dockerfile.
- Maintainability: Makes application configuration easier to understand, update, and document.
Basic Structure of a Dockerfile
A Dockerfile consists of a sequence of instructions, with each instruction creating a new layer in the Docker image.
Example: ASP.NET
# Use the official .NET runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# Set the working directory
WORKDIR /app
# Copy the published application
COPY ./publish .
# Expose application port
EXPOSE 8080
# Run the application
ENTRYPOINT ["dotnet", "MyApp.dll"]
Example: Node.js
# Use the official Node.js image
FROM node:22
# Set the working directory
WORKDIR /app
# Copy dependency files
COPY package*.json .
# Install dependencies
RUN npm install
# Copy application source code
COPY . .
# Expose application port
EXPOSE 3000
# Start the application
CMD ["npm", "start"]
Example: Python
# Use an official Python image
FROM python:3.12
# Set the working directory
WORKDIR /app
# Copy dependency file
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application source code
COPY . .
# Expose application port
EXPOSE 5000
# Define the default command
CMD ["python", "app.py"]
Docker executes these instructions from top to bottom to build the final image.
Common Dockerfile Instructions
FROMThe FROM instruction specifies the base image from which the new image is built. It is typically the first instruction in every Dockerfile.
FROM mcr.microsoft.com
or
ubuntu:24.04
or
FROM node:22
WORKDIR
The WORKDIR instruction sets the working directory inside the container. Any subsequent commands are executed from this directory.
WORKDIR /app
COPY
The COPY instruction copies files or directories from the host machine into the Docker image.
COPY . .
This copies all files from the current directory on the host to the current working directory inside the image.
Another example:
COPY app.py /app/
ADD
The ADD instruction is similar to COPY but provides additional functionality, such as automatically extracting compressed archives and downloading files from URLs.
ADD app.tar.gz /app/
For most use cases, COPY is preferred because it is simpler and more predictable.
The RUN instruction executes commands during the image build process.
RUN dotnet build
Each RUN instruction creates a new image layer.
The ENV instruction defines environment variables that are available inside the container.
ENV APP_ENV=production
Application code can access these variables during execution.
EXPOSEThe EXPOSE instruction documents the network port the application listens on.
EXPOSE 8080
This does not publish the port automatically; port mapping is configured when the container is started.
CMDThe CMD instruction specifies the default command executed when a container starts.
CMD ["dotnet", "MyApp.dll"]
Only one CMD instruction should be used in a Dockerfile. If multiple CMD instructions are present, only the last one takes effect.
The ENTRYPOINT instruction defines the primary executable for the container. Unlike CMD, it is not easily overridden.
ENTRYPOINT ["dotnet"]
CMD ["MyApp.dll"]
In this example, Docker executes:
dotnet MyApp.dll
The CMD instruction supplies default arguments to the ENTRYPOINT.
The LABEL instruction adds metadata to an image.
LABEL maintainer="admin@example.com"
LABEL version="1.0"
Labels can store information such as the image version, author, project name, or description.
Building a Docker Image from a Dockerfile
After creating a Dockerfile, build the image using:
docker build -t myapp:1.0 .
Where:
dockerbuild builds the image.-tassigns a name and tag to the image.myapp:1.0is the image name and version..specifies the build context (current directory).
Running the Docker Image
Once the image is built, create a container using:
docker run -d -p 8080:8080 myapp:1.0
This command:
- Runs the container in detached mode (
-d). - Maps port 8080 on the host to port
8080in the container. - Starts a container from the
myapp:1.0image.
Dockerfile Best Practices
Follow these recommendations when writing Dockerfiles:- Use official and trusted base images.
- Choose lightweight base images (such as Alpine Linux) when appropriate.
- Combine related RUN commands to reduce the number of image layers.
- Place frequently changing instructions near the end of the Dockerfile to maximize build cache reuse.
- Use explicit image tags instead of relying on latest.
- Exclude unnecessary files with a .dockerignore file.
- Avoid storing secrets such as passwords or API keys in the Dockerfile.
- Keep images as small as possible by removing temporary files and caches after installation.
If you're working with legacy ASP.NET Framework 4.5 applications, the Docker workflow differs from modern .NET applications. Instead of building the application inside the container using the .NET SDK, you typically publish the application first and then package the published files into a Windows-based Docker image.
In my detailed post, "Build Docker Image from ASP.NET 4.5 Published Files", I walk through the complete process of creating a Docker image from an already published ASP.NET 4.5 application. The article covers preparing the published output, writing the Dockerfile, building the image, and running the application in a Windows container. It's a practical resource for developers who are containerizing existing ASP.NET Framework applications rather than migrating to ASP.NET Core.
You can read the complete guide here:Build Docker Image from ASP.NET 4.5 Published Files
Summary
A Dockerfile is a blueprint for building Docker images. It contains a sequence of instructions that define how an application and its environment should be assembled into a portable, reproducible image. By using Dockerfiles, developers can automate application deployment, ensure consistency across environments, and simplify maintenance.
Understanding common Dockerfile instructions such as FROM, RUN, COPY, WORKDIR, CMD, and ENTRYPOINT is essential for building efficient and maintainable containerized applications.
Thanks