Dockerfile is one of the most important concepts in Docker. It is the foundation for creating custom Docker Images for applications such as ASP.NET Core Web API, Angular, React, Python and SQL-related services.
Since you are practising Docker using Windows and have an ASP.NET Core Web API project, let's understand Dockerfile with practical examples using your DockerDemo.API project.
We will cover:
-
What is a Dockerfile?
-
Why do we need a Dockerfile?
-
Dockerfile architecture and working process.
-
Dockerfile instructions.
-
Detailed explanation of each instruction.
-
Complete ASP.NET Core Dockerfile.
-
Multi-stage Dockerfile.
-
Docker build process and caching.
-
.dockerignore. -
Practical exercises and interview questions.
1. What is a Dockerfile?
A Dockerfile is a text file containing instructions that Docker uses to build a Docker Image.
In simple words:
You have developed an ASP.NET Core Web API application. You need to package your application along with its runtime requirements so that it can run inside a Docker Container.
Instead of manually installing everything inside a container, you write instructions in a Dockerfile.
Docker reads those instructions and creates an image.
Real-world example
Suppose you have an ASP.NET Core Web API application.
Your application requires:
-
.NET Runtime
-
Application DLL files
-
Configuration files
-
Required dependencies
-
Application startup command
You can define all these requirements in a Dockerfile.
ASP.NET Core Project
|
V
Dockerfile
|
V
Docker Build
|
V
Docker Image
|
V
Docker Container
|
V
ASP.NET Core Application
Running
Important:
-
Dockerfile = Instructions.
-
Docker Image = Packaged application environment.
-
Docker Container = Running instance of the image.
2. Why do we need a Dockerfile?
Imagine you developed an application on your Windows machine.
Your application works perfectly on your computer.
Now you want to deploy it to:
-
Another developer's computer.
-
A testing server.
-
A production server.
-
Azure.
-
AWS.
Each environment may have different software versions and configurations.
Without Docker, you may need to install and configure the required software manually.
With Dockerfile, you can define the environment once and build a reusable image.
Without Dockerfile
Developer Machine
|
+---- Install .NET Runtime
|
+---- Install Dependencies
|
+---- Copy Application Files
|
+---- Configure Environment
|
+---- Run Application
Testing Server
|
+---- Install .NET Runtime
|
+---- Install Dependencies
|
+---- Copy Application Files
|
+---- Configure Environment
|
+---- Run Application
The same setup process needs to be repeated.
With Dockerfile
Application Source Code
|
V
Dockerfile
|
V
Docker Image
|
+---------+---------+
| | |
V V V
Developer Testing Production
Container Container Container
You build the image once and can distribute it to different environments.
Advantages of Dockerfile
|
Advantage |
Explanation |
|---|---|
|
Automation |
Automates image creation |
|
Consistency |
Same defined environment across deployments |
|
Portability |
Images can run on compatible Docker environments |
|
Reusability |
Reuse the same Dockerfile for future builds |
|
Version control |
Store Dockerfile in Git |
|
Faster deployment |
Avoid repeating manual installation |
|
CI/CD integration |
Build images automatically in pipelines |
|
Documentation |
Describes application environment requirements |
3. How does a Dockerfile work?
Let's understand the complete working process.
Suppose you have this Dockerfile:
FROM ubuntu
WORKDIR /app
COPY message.txt .
CMD ["cat", "message.txt"]
What happens when you build an image?
Dockerfile
|
V
FROM ubuntu
|
V
Load Ubuntu Base Image
|
V
WORKDIR /app
|
V
Set Working Directory
|
V
COPY message.txt .
|
V
Copy File into Image
|
V
CMD ["cat", "message.txt"]
|
V
Create Docker Image
|
V
Run Container
|
V
Display message.txt
Docker processes the instructions in order during the image build.
However, not every instruction executes at build time. Some instructions define what should happen when the container runs.
We will understand this distinction shortly.
4. Dockerfile Basic Structure
A Dockerfile contains multiple instructions.
Example:
FROM ubuntu
WORKDIR /app
COPY message.txt .
RUN apt-get update
ENV APP_NAME=MyApplication
EXPOSE 8080
CMD ["echo", "Hello Docker"]
Let's understand the important instructions.
|
Instruction |
Purpose |
|---|---|
|
|
Specifies the base image |
|
|
Sets the working directory |
|
|
Copies files into the image |
|
|
Copies files and supports additional functionality |
|
|
Executes commands during image building |
|
|
Defines the default command when a container starts |
|
|
Defines the main executable of the container |
|
|
Documents the application's listening port |
|
|
Defines environment variables |
|
|
Defines build-time variables |
|
|
Specifies the user for subsequent instructions and runtime |
|
|
Declares a mount point |
|
|
Adds image metadata |
|
|
Defines a container health-check command |
|
|
Changes the default shell used by shell-form instructions |
|
|
Specifies the signal used to stop the container |
Now let's explore each important instruction.
5. Dockerfile Instructions – Detailed Explanation
5.1 FROM – Specify the Base Image
FROM specifies the base image used to build your Docker Image.
Every normal Dockerfile begins with a FROM instruction, except special scratch-based or parser-specific cases.
Syntax:
FROM image-name
Example:
FROM ubuntu
Here, Ubuntu is the base image.
Another example:
FROM nginx
Here, Nginx is the base image.
ASP.NET Core example
FROM mcr.microsoft.com/dotnet/aspnet:8.0
This provides the .NET 8 ASP.NET Core runtime environment.
Common base images
|
Base Image |
Purpose |
|---|---|
|
|
Ubuntu operating system |
|
|
Lightweight Linux distribution |
|
|
Nginx web server |
|
|
Python runtime |
|
|
Node.js runtime |
|
|
ASP.NET Core runtime |
|
|
.NET SDK |
Important: The SDK image contains development/build tools. The ASP.NET runtime image is intended for running published ASP.NET Core applications.
5.2 WORKDIR – Set Working Directory
WORKDIR specifies the working directory inside the image.
Syntax:
WORKDIR /app
Example:
FROM ubuntu
WORKDIR /app
Now subsequent relative paths are evaluated from /app.
Suppose you write:
WORKDIR /app
COPY message.txt .
The file will be copied to:
/app/message.txt
You can also change directories:
WORKDIR /app
WORKDIR /app/logs
The current working directory becomes:
/app/logs
Important: If the directory does not exist, Docker creates it.
Why do we need WORKDIR?
Without WORKDIR, you may need to specify absolute paths repeatedly.
Using WORKDIR makes your Dockerfile easier to understand and maintain.
5.3 COPY – Copy Files into the Image
COPY copies files or directories from the build context into the Docker Image.
Syntax:
COPY source destination
Example:
COPY message.txt /app/
This copies message.txt from your build context into /app/ inside the image.
Copy an entire folder
COPY ./Logs /app/Logs
Copy all files from the build context
COPY . .
The first . means the build-context root.
The second . means the current working directory inside the image.
For example:
WORKDIR /app
COPY . .
This copies the build-context contents into /app.
Important: COPY can only access files included in the build context. It cannot arbitrarily access files outside that context.
5.4 ADD – Copy Files with Additional Features
ADD is similar to COPY, but it supports additional functionality.
Syntax:
ADD source destination
Example:
ADD message.txt /app/
Additional features include:
-
Automatically extracting supported local tar archives.
-
Downloading remote files using supported HTTP(S) URLs.
Example:
ADD application.tar.gz /app/
Docker automatically extracts a supported local tar archive into /app.
COPY vs ADD
|
Feature |
COPY |
ADD |
|---|---|---|
|
Copy files |
Yes |
Yes |
|
Copy folders |
Yes |
Yes |
|
Extract supported local tar archives |
No |
Yes |
|
Download remote HTTP(S) files |
No |
Yes |
|
Recommended for normal file copying |
Yes |
No |
Recommendation: Prefer COPY for normal file and directory copying. Use ADD only when you specifically need its additional functionality.
5.5 RUN – Execute Commands During Image Building
RUN executes commands while building a Docker Image.
Syntax:
RUN command
Example:
FROM ubuntu
RUN apt-get update
Here, apt-get update executes during the image build.
Another example:
RUN apt-get update && apt-get install -y curl
This updates package information and installs curl.
Important concept
RUN executes during the image-building process, not every time the container starts.
docker build
|
V
RUN instruction executes
|
V
Image Layer Created
|
V
Image Completed
|
V
docker run
|
V
Container Starts
Real-world usage:
-
Installing Linux packages.
-
Installing application dependencies.
-
Compiling application code.
-
Publishing ASP.NET Core applications.
5.6 CMD – Default Command When Container Starts
CMD specifies the default command or arguments executed when a container starts.
Example:
FROM ubuntu
CMD ["echo", "Hello Docker"]
Build the image:
docker build -t cmd-demo .
Run:
docker run cmd-demo
Output:
Hello Docker
Important point
Unlike RUN, CMD is not executed during image building.
It defines the default runtime command.
You can override CMD when running the container.
For example:
docker run cmd-demo echo "Hello Windows"
Output:
Hello Windows
The command supplied to docker run replaces the image's default CMD when there is no overriding ENTRYPOINT behavior.
5.7 ENTRYPOINT – Define the Main Application
ENTRYPOINT specifies the executable that should run when a container starts.
Example:
FROM ubuntu
ENTRYPOINT ["echo", "Hello Docker"]
Build:
docker build -t entrypoint-demo .
Run:
docker run entrypoint-demo
Output:
Hello Docker
Unlike CMD, ENTRYPOINT is generally intended to define the main executable.
ENTRYPOINT vs CMD
|
Feature |
ENTRYPOINT |
CMD |
|---|---|---|
|
Purpose |
Main executable |
Default command or arguments |
|
Can be overridden |
Yes, using |
Yes, using runtime command |
|
Common usage |
Application startup |
Default arguments |
|
Works together |
Yes |
Yes |
Using ENTRYPOINT and CMD together
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["Hello Docker"]
Run:
docker run entrypoint-demo
Output:
Hello Docker
Now override CMD:
docker run entrypoint-demo "Hello Windows"
Output:
Hello Windows
Why?
-
ENTRYPOINT defines
echo. -
CMD supplies the default argument.
-
The runtime argument replaces CMD.
Shell Form vs Exec Form
You will commonly see two formats.
Shell form:
CMD echo Hello Docker
Exec form:
CMD ["echo", "Hello Docker"]
The exec form uses a JSON array and does not invoke a shell automatically.
Recommendation: Prefer exec form for ENTRYPOINT and CMD, especially for application containers, because it provides more predictable process and signal handling.
5.8 EXPOSE – Document the Application Port
EXPOSE documents the port that the application is expected to listen on inside the container.
Example:
EXPOSE 8080
This tells users that the application uses port 8080.
Important: EXPOSE does not publish the port to Windows.
To publish it:
docker run -d -p 5000:8080 myapi:v1
Now:
Windows Port: 5000
Container Port: 8080
5.9 ENV – Define Environment Variables
ENV defines environment variables available during subsequent build instructions and when the container runs.
Example:
ENV APP_NAME=MyApplication
You can use it inside the Dockerfile:
ENV APP_NAME=MyApplication
RUN echo $APP_NAME
You can also access it from the running container.
For example:
docker run -e APP_NAME=Production myimage
The -e option can override the default environment variable.
ASP.NET Core example
ENV ASPNETCORE_ENVIRONMENT=Production
This configures the ASP.NET Core environment.
Common values:
-
Development
-
Staging
-
Production
Security warning: Do not store passwords, API keys or other secrets directly in Dockerfile ENV instructions. They can become part of image configuration and may be exposed through image inspection.
5.10 ARG – Build-time Variables
ARG defines variables that can be used during the Docker image build.
Example:
ARG APP_VERSION=1.0
FROM ubuntu
RUN echo $APP_VERSION
Build:
docker build --build-arg APP_VERSION=2.0 -t myapp:v1 .
The build uses APP_VERSION=2.0.
ARG vs ENV
|
Feature |
ARG |
ENV |
|---|---|---|
|
Purpose |
Build-time variable |
Environment variable |
|
Available during build |
Yes |
Yes |
|
Available in running container by default |
No |
Yes |
|
Can be overridden during build |
Yes |
Yes, through Dockerfile changes or build mechanisms |
Important: ARG is not a safe way to pass secrets. Build arguments may be exposed in image history, metadata or build records. Use BuildKit secret mounts for build-time secrets.
5.11 USER – Specify the Container User
USER specifies which user should execute subsequent instructions and the application process by default.
Example:
FROM ubuntu
RUN useradd -m appuser
USER appuser
WORKDIR /app
Now subsequent commands and the default container process run as appuser, unless another USER instruction changes it.
Why do we need USER?
By default, many Linux-based containers run as root.
Running an application as root can create unnecessary security risks.
Best practice: Run applications using a non-root user whenever possible.
For ASP.NET Core, modern official .NET images support running as a non-root user, although the exact user and port configuration depends on the image version.
5.12 VOLUME – Declare a Mount Point
VOLUME declares a directory that is intended to hold externally stored data.
Example:
VOLUME ["/app/data"]
This indicates that /app/data is a volume mount point.
When running a container, Docker can provide an anonymous volume if no explicit mount is supplied.
Example:
docker run -d --name volume-demo myimage
You can also explicitly attach a Named Volume:
docker run -d --name volume-demo -v mydata:/app/data myimage
Important: VOLUME does not create a named volume called mydata. It declares a mount point. You provide the volume name when running the container or through Docker Compose.
5.13 LABEL – Add Metadata to an Image
LABEL adds metadata to a Docker Image.
Example:
LABEL author="Syed"
LABEL version="1.0"
LABEL description="ASP.NET Core Web API"
You can also write:
LABEL author="Syed" version="1.0" description="ASP.NET Core Web API"
Inspect the image:
docker image inspect myimage
Labels are useful for:
-
Author information.
-
Application version.
-
Build information.
-
Project identification.
-
CI/CD metadata.
5.14 HEALTHCHECK – Check Container Health
HEALTHCHECK defines a command that Docker periodically executes to determine whether the application is healthy.
Example:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8080/health || exit 1
Let's understand the parameters:
|
Parameter |
Meaning |
|---|---|
|
|
Check every 30 seconds |
|
|
Wait up to 5 seconds |
|
|
Mark unhealthy after three consecutive failures |
|
|
Command to execute |
|
|
Application health endpoint |
Possible health states:
-
Starting
-
Healthy
-
Unhealthy
Check using:
docker ps
Example:
STATUS
Up 2 minutes (healthy)
Important: Your application must provide the /health endpoint, and the health-check command must be available inside the image. For example, curl may not be installed in a minimal runtime image.
Also, Docker Healthcheck does not automatically restart an unhealthy container. It reports its health status.
5.15 SHELL – Specify the Default Shell
SHELL changes the default shell used by subsequent shell-form RUN, CMD and ENTRYPOINT instructions.
Example:
SHELL ["/bin/bash", "-c"]
RUN echo "Hello from Bash"
This is useful when you need Bash-specific commands instead of the default /bin/sh.
5.16 STOPSIGNAL – Specify the Stop Signal
STOPSIGNAL defines the signal Docker sends to the container's main process when stopping it.
Example:
STOPSIGNAL SIGTERM
When you execute:
docker stop mycontainer
Docker uses the configured stop signal.
This is useful when an application requires a particular signal for graceful shutdown.
5.17 ONBUILD – Trigger Instructions in Child Images
ONBUILD defines an instruction that executes later when another Dockerfile uses your image as its base image.
Example:
FROM ubuntu
ONBUILD COPY . /app
ONBUILD WORKDIR /app
When another Dockerfile uses this image:
FROM my-base-image
The ONBUILD instructions are triggered during the child image build.
This is an advanced instruction, mainly useful when creating reusable base images.
6. Build Context – Very Important Concept
Before building a Docker Image, you should understand the Build Context.
Build Context is the set of files and directories that Docker makes available to the image build process.
Consider your project:
D:\Docker Practice\DockerDemo\DockerDemo.API
|
+---- Controllers
|
+---- Models
|
+---- Program.cs
|
+---- appsettings.json
|
+---- DockerDemo.API.csproj
|
+---- Dockerfile
Open CMD inside this folder:
cd /d "D:\Docker Practice\DockerDemo\DockerDemo.API"
Build:
docker build -t dockerdemo-api:v1 .
What does the final dot mean?
docker build -t dockerdemo-api:v1 .
The dot represents the current directory as the build context.
Docker can access files inside that context when executing instructions such as COPY.
Build context diagram
Windows Project Folder
|
V
Build Context
|
+---- Dockerfile
|
+---- Program.cs
|
+---- Controllers
|
+---- Models
|
+---- appsettings.json
|
V
Docker Build
|
V
Docker Image
Can Docker COPY files outside the build context?
Suppose your project is:
D:\Projects
|
+---- Shared
|
+---- MyAPI
If you run Docker build from MyAPI using MyAPI as the build context, you cannot directly COPY files from D:\Projects\Shared.
The files must be inside the selected build context.
You can instead use the parent directory as the context and adjust the Dockerfile paths.
Example:
docker build -f MyAPI\Dockerfile -t myapi:v1 .
Run this from D:\Projects.
Here:
-
-f MyAPI\Dockerfilespecifies the Dockerfile location. -
.specifiesD:\Projectsas the build context.
This is particularly important in projects containing multiple class libraries.
7. What is .dockerignore?
.dockerignore is a text file that tells Docker which files and directories to exclude from the build context.
It is similar to .gitignore, but it is used by Docker during the build process.
Why do we need .dockerignore?
Imagine your ASP.NET Core project contains:
DockerDemo.API
|
+---- bin
|
+---- obj
|
+---- .vs
|
+---- Controllers
|
+---- Models
|
+---- Program.cs
|
+---- Dockerfile
Do you need to send all these folders to Docker?
No.
For example:
-
bincontains compiled output. -
objcontains intermediate build files. -
.vscontains Visual Studio-specific files.
They are generally unnecessary for a clean Docker build.
Example .dockerignore
Create a file named:
.dockerignore
Add:
bin/
obj/
.vs/
.git/
.gitignore
.vscode/
*.user
*.suo
*.log
Now Docker excludes matching files from the build context.
Benefits
-
Reduces build context size.
-
Improves build performance.
-
Prevents unnecessary files from entering the image.
-
Reduces the risk of accidentally copying sensitive local files.
-
Makes image builds cleaner and more predictable.
Important: .dockerignore does not remove files already present in a base image. It filters the build context sent to the builder.
8. Dockerfile Layers and Build Cache
This is an important topic for interviews and real-world development.
Docker builds an image using a sequence of instructions.
Many instructions create filesystem layers.
Docker can reuse previously built layers when their inputs and dependencies have not changed.
This is called Docker Build Cache.
Example Dockerfile
FROM ubuntu
WORKDIR /app
COPY message.txt .
RUN echo "Building application"
CMD ["cat", "message.txt"]
Consider this build process:
Instruction 1: FROM ubuntu
|
V
Instruction 2: WORKDIR /app
|
V
Instruction 3: COPY message.txt .
|
V
Instruction 4: RUN echo "Building application"
|
V
Instruction 5: CMD
|
V
Final Image
Each instruction contributes to the build result.
When you build the same Dockerfile again without changing relevant inputs, Docker can reuse cached results.
Example
First build:
docker build -t cache-demo:v1 .
Second build:
docker build -t cache-demo:v2 .
If nothing changed, many build steps will show:
CACHED
What happens when a file changes?
Suppose you modify message.txt.
Docker must rebuild the COPY instruction and the subsequent dependent steps.
This is why Dockerfile instruction order matters.
Best practice: Copy dependency files first
For ASP.NET Core, consider:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY DockerDemo.API.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
Why is this useful?
Imagine you modify only Program.cs.
The project file has not changed, so Docker can reuse the cached restore layer.
This can save build time.
9. Single-Stage vs Multi-Stage Dockerfile
This is one of the most important Dockerfile concepts for .NET developers.
9.1 Single-Stage Dockerfile
In a Single-Stage Dockerfile, you use one base image for building and running the application.
For example:
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY . .
RUN dotnet publish -c Release -o publish
ENTRYPOINT ["dotnet", "publish/DockerDemo.API.dll"]
What happens?
.NET SDK Image
|
V
Copy Source Code
|
V
Build Application
|
V
Publish Application
|
V
Run Application
The problem?
The SDK image contains development and build tools that are unnecessary for running the published application.
This can increase the final image size and attack surface.
9.2 Multi-Stage Dockerfile
A Multi-Stage Dockerfile uses multiple FROM instructions.
Each stage can perform a separate task.
For example:
-
Build Stage: Compile the application.
-
Publish Stage: Generate published output.
-
Final Stage: Run the published application using the runtime image.
Architecture
Dockerfile
|
+--------+--------+
| |
V V
Build Stage Final Stage
.NET SDK .NET Runtime
| |
V |
Compile Code |
| |
V |
Publish Files ---------+
|
V
Final Image
|
V
Docker Container
The final image contains the published application and runtime dependencies, rather than the full SDK build environment.
Advantages of Multi-Stage Builds
|
Advantage |
Explanation |
|---|---|
|
Smaller image |
Avoids including unnecessary SDK files |
|
Better security |
Reduces unnecessary tools in the runtime image |
|
Separation |
Build and runtime responsibilities are separated |
|
Cleaner deployment |
Only required application files are copied |
|
Maintainability |
Easier to manage application build stages |
Recommendation: Use Multi-Stage Dockerfiles for production ASP.NET Core applications.
10. Complete Practical Example – ASP.NET Core Web API Dockerfile
Now let's create a real Dockerfile for your DockerDemo.API project.
We will use .NET 8 in this example. If your project targets .NET 9 or .NET 10, use matching SDK and runtime image versions.
Step 1: Open your ASP.NET Core project
Your project path:
D:\Docker Practice\DockerDemo\DockerDemo.API
Project structure:
DockerDemo.API
|
+---- Controllers
|
+---- Models
|
+---- Properties
|
+---- Program.cs
|
+---- appsettings.json
|
+---- DockerDemo.API.csproj
Step 2: Create a Dockerfile
Inside DockerDemo.API, create a new file named:
Dockerfile
Important:
-
No
.txtextension. -
No
.dockerfileextension is required. -
The conventional name is exactly
Dockerfile. -
It should be in your project directory for this example.
Step 3: Add Dockerfile instructions
Let's first understand a simple Single-Stage Dockerfile.
# Step 1: Use .NET SDK image
FROM mcr.microsoft.com/dotnet/sdk:8.0
# Step 2: Set working directory
WORKDIR /app
# Step 3: Copy project files
COPY . .
# Step 4: Publish application
RUN dotnet publish -c Release -o out
# Step 5: Expose application port
EXPOSE 8080
# Step 6: Configure ASP.NET Core port
ENV ASPNETCORE_HTTP_PORTS=8080
# Step 7: Start application
ENTRYPOINT ["dotnet", "out/DockerDemo.API.dll"]
Understand each instruction
|
Instruction |
Explanation |
|---|---|
|
|
Loads .NET 8 SDK image |
|
|
Creates/sets |
|
|
Copies source files into |
|
|
Publishes the application |
|
|
Documents port 8080 |
|
|
Configures ASP.NET Core HTTP port |
|
|
Starts the published application |
This Dockerfile works, but it includes the SDK in the final image.
For production, we should use Multi-Stage Builds.
11. Complete Multi-Stage Dockerfile for ASP.NET Core
This is the recommended approach.
Create or replace your Dockerfile with:
# ==========================================
# STAGE 1: BUILD
# ==========================================
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy project file
COPY ["DockerDemo.API.csproj", "./"]
# Restore NuGet packages
RUN dotnet restore "DockerDemo.API.csproj"
# Copy remaining source code
COPY . .
# Publish application
RUN dotnet publish "DockerDemo.API.csproj" \
-c Release \
-o /app/publish \
/p:UseAppHost=false
# ==========================================
# STAGE 2: FINAL RUNTIME
# ==========================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
# Copy published application from build stage
COPY --from=build /app/publish .
# Configure application port
ENV ASPNETCORE_HTTP_PORTS=8080
# Document container port
EXPOSE 8080
# Start application
ENTRYPOINT ["dotnet", "DockerDemo.API.dll"]
Understand the complete execution flow
Dockerfile
|
V
BUILD STAGE
.NET SDK Image
|
V
Copy .csproj File
|
V
dotnet restore
|
V
Copy Source Code
|
V
dotnet publish
|
V
Published Files
|
V
FINAL STAGE
.NET Runtime Image
|
V
COPY --from=build
|
V
Final Docker Image
|
V
Docker Container
|
V
ASP.NET Core API
Why are we using two different images?
|
Build Stage |
Final Stage |
|---|---|
|
.NET SDK image |
.NET ASP.NET runtime image |
|
Compiles application |
Runs application |
|
Contains build tools |
Does not contain full SDK |
|
Restores NuGet packages |
Contains published application |
|
Produces published output |
Executes application |
Important: Only the published output is copied from the build stage to the final stage.
The SDK and intermediate build files are not included in the final stage.
Step 4: Create .dockerignore
Inside the same project directory, create:
.dockerignore
Add:
bin/
obj/
.vs/
.git/
.vscode/
*.user
*.suo
*.log
This prevents unnecessary development files from being included in the build context.
Step 5: Build the Docker Image
Open CMD:
cd /d "D:\Docker Practice\DockerDemo\DockerDemo.API"
Execute:
docker build -t dockerdemo-api:v1 .
Understand the command:
|
Part |
Purpose |
|---|---|
|
|
Build Docker Image |
|
|
Assign image name and tag |
|
|
Image name |
|
|
Image version |
|
|
Current directory as build context |
Docker will:
-
Load the Dockerfile.
-
Pull the required .NET SDK image if needed.
-
Restore NuGet packages.
-
Compile and publish your application.
-
Pull the .NET runtime image if needed.
-
Copy published files.
-
Generate the final Docker Image.
Step 6: Verify the Docker Image
docker images
Expected:
REPOSITORY TAG IMAGE ID
dockerdemo-api v1 abc123
Step 7: Run the Container
docker run -d --name dockerdemo-container -p 5000:8080 dockerdemo-api:v1
Here:
-
Windows port: 5000
-
Container port: 8080
Step 8: Access Your API
Open your browser or Postman:
http://localhost:5000
If you have a controller endpoint:
http://localhost:5000/api/products
Your ASP.NET Core Web API should respond.
Step 9: Check Application Logs
docker logs dockerdemo-container
Follow logs:
docker logs -f dockerdemo-container
Step 10: Stop and Remove the Container
docker stop dockerdemo-container
docker rm dockerdemo-container
Your Docker Image remains available.
12. Dockerfile with Multiple Projects – Important for Clean Architecture
Your Portfolio Platform architecture contains multiple projects:
Syed.PortfolioPlatform
|
+---- Syed.PortfolioPlatform.API
|
+---- Syed.PortfolioPlatform.Application
|
+---- Syed.PortfolioPlatform.Domain
|
+---- Syed.PortfolioPlatform.Infrastructure
Suppose your API references the other class libraries.
In this case, you cannot simply copy only the API .csproj file and restore it.
You must also copy the referenced project files and preserve their relative folder structure.
For example, your Dockerfile could start like this:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Syed.PortfolioPlatform.API/Syed.PortfolioPlatform.API.csproj", "Syed.PortfolioPlatform.API/"]
COPY ["Syed.PortfolioPlatform.Application/Syed.PortfolioPlatform.Application.csproj", "Syed.PortfolioPlatform.Application/"]
COPY ["Syed.PortfolioPlatform.Domain/Syed.PortfolioPlatform.Domain.csproj", "Syed.PortfolioPlatform.Domain/"]
COPY ["Syed.PortfolioPlatform.Infrastructure/Syed.PortfolioPlatform.Infrastructure.csproj", "Syed.PortfolioPlatform.Infrastructure/"]
RUN dotnet restore "Syed.PortfolioPlatform.API/Syed.PortfolioPlatform.API.csproj"
COPY . .
RUN dotnet publish "Syed.PortfolioPlatform.API/Syed.PortfolioPlatform.API.csproj" \
-c Release \
-o /app/publish \
/p:UseAppHost=false
Then add your runtime stage:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Syed.PortfolioPlatform.API.dll"]
For this example, run the build command from the solution's parent directory:
docker build -f Syed.PortfolioPlatform.API\Dockerfile -t portfolio-api:v1 .
This is particularly important when building applications that use Clean Architecture, Repository Pattern, Unit of Work and multiple class libraries.
13. Dockerfile Best Practices
These are important for real-world projects.
|
No. |
Best Practice |
Reason |
|---|---|---|
|
1 |
Use Multi-Stage Builds |
Reduce final image size |
|
2 |
Use a suitable official base image |
Reliability and maintainability |
|
3 |
Use |
Reduce build context |
|
4 |
Copy project files before source files |
Improve build caching |
|
5 |
Prefer COPY over ADD |
Predictable file copying |
|
6 |
Use non-root users |
Improve security |
|
7 |
Avoid storing secrets in Dockerfile |
Prevent secret exposure |
|
8 |
Use specific image tags |
Improve reproducibility |
|
9 |
Keep images small |
Reduce storage and transfer costs |
|
10 |
Use HEALTHCHECK where appropriate |
Monitor application health |
|
11 |
Combine related RUN commands where appropriate |
Avoid unnecessary layers |
|
12 |
Review base-image updates |
Receive security fixes |
Common Dockerfile mistakes
|
Mistake |
Problem |
Solution |
|---|---|---|
|
Missing FROM |
No base image |
Add FROM |
|
Wrong COPY path |
Build cannot find files |
Check build context |
|
Wrong DLL name |
Container fails to start |
Verify published DLL |
|
Missing runtime image |
Application cannot run |
Use matching ASP.NET runtime |
|
Wrong container port |
API is inaccessible |
Verify application listening port |
|
Missing project references |
dotnet restore fails |
Copy referenced |
|
Missing .dockerignore |
Unnecessary files copied |
Create |
|
Running as root unnecessarily |
Increased security risk |
Configure non-root user |
14. Dockerfile vs Docker Compose
These two concepts are frequently confused.
They solve different problems.
|
Feature |
Dockerfile |
Docker Compose |
|---|---|---|
|
Purpose |
Build Docker Images |
Manage multiple containers |
|
File name |
|
|
|
Format |
Docker instructions |
YAML |
|
Defines |
Application environment and build |
Services, networks, volumes, ports |
|
Builds images |
Yes |
Can build using Dockerfile |
|
Runs containers |
Not directly |
Yes |
Example architecture
Docker Compose
|
+---------+---------+
| |
V V
ASP.NET Core SQL Server
| |
V V
Dockerfile Docker Image
|
V
Docker Image
Docker Compose can use your Dockerfile to build the API image and then run the API and SQL Server containers together.
15. Important Dockerfile Interview Questions
These are the most important questions for Docker interviews.
|
No. |
Interview Question |
Severity |
|---|---|---|
|
1 |
What is a Dockerfile and why do we need it? |
High |
|
2 |
What is the difference between Dockerfile, Docker Image and Container? |
High |
|
3 |
What are the commonly used Dockerfile instructions? |
High |
|
4 |
What is the difference between RUN and CMD? |
High |
|
5 |
What is the difference between CMD and ENTRYPOINT? |
High |
|
6 |
What is the difference between COPY and ADD? |
High |
|
7 |
What is the purpose of FROM? |
High |
|
8 |
What is WORKDIR? |
Medium |
|
9 |
What is the difference between ARG and ENV? |
High |
|
10 |
What is EXPOSE in Dockerfile? |
High |
|
11 |
What is the difference between EXPOSE and -p? |
High |
|
12 |
What is .dockerignore? |
High |
|
13 |
What is Docker Build Context? |
High |
|
14 |
What are Docker Image Layers? |
High |
|
15 |
What is Docker Build Cache? |
High |
|
16 |
What is a Multi-Stage Dockerfile? |
High |
|
17 |
What is the difference between Single-Stage and Multi-Stage Builds? |
High |
|
18 |
What is HEALTHCHECK? |
Medium |
|
19 |
Why should we avoid running containers as root? |
High |
|
20 |
How do you create a Docker Image from a Dockerfile? |
High |
16. Final Revision – Dockerfile Execution Flow
Remember this complete workflow:
ASP.NET Core Source Code
|
V
Dockerfile
|
V
docker build
|
V
Docker Image
|
V
docker run
|
V
Docker Container
|
V
ASP.NET Core API
|
V
Access using Browser
Most important instructions to remember
|
Instruction |
Remember |
|---|---|
|
FROM |
Base Image |
|
WORKDIR |
Working Directory |
|
COPY |
Copy Files |
|
RUN |
Build-time Command |
|
CMD |
Default Runtime Command |
|
ENTRYPOINT |
Main Application Executable |
|
EXPOSE |
Document Container Port |
|
ENV |
Runtime Environment Variable |
|
ARG |
Build-time Variable |
|
USER |
Run as Specific User |
|
VOLUME |
Declare Mount Point |
|
HEALTHCHECK |
Monitor Application Health |