← Back to Articles         
Dockerfile – Complete Guide with Detailed Practical Examples

Dockerfile – Complete Guide with Detailed Practical Examples

Published on 04 Nov 2026 24 min read Docker
Docker Fundamentals

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:

  1. What is a Dockerfile?

  2. Why do we need a Dockerfile?

  3. Dockerfile architecture and working process.

  4. Dockerfile instructions.

  5. Detailed explanation of each instruction.

  6. Complete ASP.NET Core Dockerfile.

  7. Multi-stage Dockerfile.

  8. Docker build process and caching.

  9. .dockerignore.

  10. 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

FROM

Specifies the base image

WORKDIR

Sets the working directory

COPY

Copies files into the image

ADD

Copies files and supports additional functionality

RUN

Executes commands during image building

CMD

Defines the default command when a container starts

ENTRYPOINT

Defines the main executable of the container

EXPOSE

Documents the application's listening port

ENV

Defines environment variables

ARG

Defines build-time variables

USER

Specifies the user for subsequent instructions and runtime

VOLUME

Declares a mount point

LABEL

Adds image metadata

HEALTHCHECK

Defines a container health-check command

SHELL

Changes the default shell used by shell-form instructions

STOPSIGNAL

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

Ubuntu operating system

alpine

Lightweight Linux distribution

nginx

Nginx web server

python

Python runtime

node

Node.js runtime

mcr.microsoft.com/dotnet/aspnet:8.0

ASP.NET Core runtime

mcr.microsoft.com/dotnet/sdk:8.0

.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:

  1. Automatically extracting supported local tar archives.

  2. 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 --entrypoint

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

--interval=30s

Check every 30 seconds

--timeout=5s

Wait up to 5 seconds

--retries=3

Mark unhealthy after three consecutive failures

CMD

Command to execute

/health

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\Dockerfile specifies the Dockerfile location.

  • . specifies D:\Projects as 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:

  • bin contains compiled output.

  • obj contains intermediate build files.

  • .vs contains 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

  1. Reduces build context size.

  2. Improves build performance.

  3. Prevents unnecessary files from entering the image.

  4. Reduces the risk of accidentally copying sensitive local files.

  5. 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 .txt extension.

  • No .dockerfile extension 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

FROM

Loads .NET 8 SDK image

WORKDIR

Creates/sets /app

COPY

Copies source files into /app

RUN

Publishes the application

EXPOSE

Documents port 8080

ENV

Configures ASP.NET Core HTTP port

ENTRYPOINT

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

docker build

Build Docker Image

-t

Assign image name and tag

dockerdemo-api

Image name

v1

Image version

.

Current directory as build context

Docker will:

  1. Load the Dockerfile.

  2. Pull the required .NET SDK image if needed.

  3. Restore NuGet packages.

  4. Compile and publish your application.

  5. Pull the .NET runtime image if needed.

  6. Copy published files.

  7. 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 .dockerignore

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 .csproj files

Missing .dockerignore

Unnecessary files copied

Create .dockerignore

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

Dockerfile

compose.yaml

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

 

 
← Back to Articles