A container is an isolated process that runs an application with its own user environment and dependencies.
You have .NET 10 SDK, SQL Server Developer, and a specific Windows setup installed. The production server runs Linux, has an old version of libraries, and restricted permissions. Result: Your API explodes when deployed.
Docker greatly reduces this problem.
Docker allows us to package our application together with the runtime, libraries, and execution configuration into a sealed box called a container. The container does not include a full operating system: it shares the host’s kernel, but it does bring the user environment needed by the app.
If the container starts on your machine, you have much greater guarantees that it will start the same way in production, as long as you use the correct image architecture.
Today we are going to write the “recipe” to create that box: the Dockerfile.
The Dockerfile
The Dockerfile is a text file (without extension) that tells Docker step by step how to build your application.
For .NET it is common to use multi-stage builds. Why?
- To compile the code you need the SDK, which includes many tools.
- To run the app you only need the ASP.NET Core Runtime image, which is considerably smaller.
We don’t want to take the compiler to production. So we will use one image to compile and then copy only the result to a lightweight image.
Creating the file
Create a file named Dockerfile in the root of your project (next to the .sln or main .csproj).
Step-by-step content
# STAGE 1: BUILD
# We use the official .NET 10 SDK image
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# 1. Copy the csproj and restore dependencies
# (We do this before copying all the code to leverage Docker's cache)
COPY ["MiApi/MiApi.csproj", "MiApi/"]
RUN dotnet restore "MiApi/MiApi.csproj"
# 2. Copy the rest of the code and compile
COPY . .
WORKDIR "/src/MiApi"
RUN dotnet build "MiApi.csproj" -c Release -o /app/build
# 3. Publish the final files
FROM build AS publish
RUN dotnet publish "MiApi.csproj" -c Release -o /app/publish /p:UseAppHost=false
# STAGE 2: RUNTIME
# We use the lightweight ASP.NET Core image (without build tools)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
# Expose the port (default in modern .NET images is 8080)
EXPOSE 8080
# Copy the compiled files from the previous stage (/app/publish)
COPY --from=publish /app/publish .
# Run as non-root user when the image supports it
USER $APP_UID
# Set the entry point
ENTRYPOINT ["dotnet", "MiApi.dll"]The forgotten file: .dockerignore
Just like you don’t upload the bin or obj folder to Git, you shouldn’t copy them to the container in Docker (it clutters and slows down the build).
Create a .dockerignore file next to the Dockerfile:
**/.dockerignore
**/.env
**/bin/
**/obj/
**/.vs/
**/.git/Building the image
Now that we have the recipe, let’s cook the dish. Open the terminal in the Dockerfile’s folder:
# -t: Tag (image name)
# . : The context (current folder)
docker build -t mi-api-web .Docker will download the base images, copy your code, compile, and generate an image named mi-api-web.
The first build will take longer. Subsequent builds can reuse layers that haven’t changed, like the package restoration.
Running the container
Moment of truth! Let’s start our API isolated from the world.
# -d: Detached (background)
# -p: Port (HostPort : ContainerPort)
# --name: Name to identify the container
docker run -d -p 5000:8080 --name mi-api-corriendo mi-api-webThe port mapping is very important:
- 8080: This is where your API listens INSIDE the container (default in modern .NET images).
- 5000: This is the port you want to access from YOUR browser.
Now, open http://localhost:5000/swagger (or your test endpoint).
There it is! Your API running inside an isolated Linux environment.
Visual Studio and Docker
I have shown you how to do it manually so you understand the mechanism. But Visual Studio does it for you.
- Right-click on the project and choose Add > Docker Support.
- Choose “Linux”.
Visual Studio will create the Dockerfile automatically and even configure a “Play with Docker” button so you can debug (set breakpoints) inside the container.
Environment variables in Docker
Remember the appsettings.json and the connection string? In Docker we don’t want to change the JSON file every time. We use Environment Variables.
We can inject configuration when running the container with -e:
docker run -d -p 5000:8080 \
-e "ConnectionStrings__DefaultConnection=Server=mi-server;Database=db..." \
-e "Logging__LogLevel__Default=Warning" \
mi-api-webIn environment variables we use __ instead of : because it works better across shells, operating systems, and containers. ASP.NET Core automatically translates it to configuration sections.