.NET Docker Containers: Package and Run Your App

Learn .NET Docker Containers: Package and Run Your App with practical examples, explanations, best practices, and real-world coding techniques.

Docker containers provide a consistent way to package and run .NET applications across development machines, testing environments, CI/CD pipelines, and production servers. A container image includes the published application and the environment it needs to execute, reducing differences between environments and making deployments easier to reproduce.

For example, an ASP.NET Core Web API might work on a developer's computer but fail on a production server because the required runtime is missing or the environment is configured differently. Building and running the application in a Docker container helps standardize its execution environment. Containers are not virtual machines: they share the host operating system's kernel while isolating application processes and their supporting resources.

1. Why Use Docker with .NET?

Docker is particularly useful when an application must run consistently across multiple environments or when different services have different runtime requirements. Instead of configuring every server manually, a team can build an image and deploy that image to compatible container platforms.

  • Consistent environments: Package application files and runtime dependencies into a reusable image.
  • Portable deployment: Run the same image on compatible developer machines, servers, and container platforms.
  • CI/CD integration: Automate image builds, tests, tagging, and deployment through a pipeline.
  • Resource isolation: Configure container-specific resource limits, networking, and process boundaries.
  • Independent services: Package APIs, background workers, and other application components separately when the architecture requires it.

Containers are packaging and deployment units, not a replacement for application architecture. A well-designed application still needs appropriate configuration, monitoring, security, error handling, and dependency management.

2. Understand .NET SDK and Runtime Images

Official .NET container images are available from Microsoft's container image repository. Two important image types are the SDK image and the ASP.NET Core runtime image.

  • SDK image: Includes the .NET SDK and development tools needed to restore packages, compile source code, and publish applications.
  • ASP.NET Core runtime image: Includes the runtime components needed to execute published ASP.NET Core applications, without the full SDK toolset.
  • .NET runtime image: Suitable for compatible applications that use the base .NET runtime without requiring the ASP.NET Core shared framework.

A production Dockerfile commonly uses a multi-stage build. The build stage uses the SDK to compile and publish the application. The final stage copies only the published output into a smaller runtime image. This avoids carrying compilers, build tools, and unnecessary intermediate files into the final image.

Choose image versions that match the application's target framework and deployment requirements. For example, a project targeting net10.0 can use compatible .NET 10 SDK and ASP.NET Core runtime images. If the project targets another framework, select matching supported image versions instead of copying tags blindly.

3. Create an ASP.NET Core Project

Start by creating a Web API using the .NET CLI. The following commands create a project named TodoApi and build it locally before you introduce Docker.

dotnet new webapi -n TodoApi
cd TodoApi
dotnet restore
dotnet build
dotnet run

Verify that the API starts successfully before containerizing it. This separates application problems from container configuration problems and makes later troubleshooting easier.

In the following example, assume the repository contains a TodoApi project directory and the Dockerfile is stored inside that directory. The Docker build context will be the repository root, which lets Docker access the project files during the build.

4. Write a Multi-Stage Dockerfile

A Dockerfile contains instructions for creating an image. The following example targets .NET 10 and separates the SDK build stage from the runtime stage. Adjust the project path, project filename, framework, and image versions to match your application.

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

COPY ["TodoApi/TodoApi.csproj", "TodoApi/"]
RUN dotnet restore "TodoApi/TodoApi.csproj"

COPY . .
WORKDIR /src/TodoApi
RUN dotnet publish "TodoApi.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app

COPY --from=build /app/publish .

ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080

USER $APP_UID
ENTRYPOINT ["dotnet", "TodoApi.dll"]

The first stage restores the project's packages and publishes the application. Copying the project file before the remaining source files can improve build caching: Docker may reuse the package restore layer when source code changes but project dependencies do not. The second stage starts from the ASP.NET Core runtime image and copies the published application into it.

The ENTRYPOINT instruction defines the application process that starts when the container runs. The port configuration makes the ASP.NET Core application listen on port 8080. EXPOSE documents the intended container port, but it does not publish that port to the host by itself. The USER $APP_UID setting uses the non-root application user provided by supported official .NET images. Confirm that the selected base image defines this variable before using the instruction.

If your repository contains a solution with several projects, the Docker build context must include every required project and referenced file. Update the COPY paths and restore command accordingly. A Dockerfile copied from another repository will not work unchanged if its project layout differs.

5. Add a .dockerignore File

A .dockerignore file excludes unnecessary files from the Docker build context. This can reduce build time, prevent local build artifacts from overwriting container build outputs, and help keep sensitive development files out of the build context.

**/bin/
**/obj/
.git/
.vs/
.vscode/
*.user
*.suo
.env
.env.*

Place this file at the root of the build context. Review exclusions carefully: required source files, project files, certificates deliberately provided for a specific build, or configuration inputs must not be excluded unintentionally. A .dockerignore file also does not replace secret management; never copy credentials into an image.

6. Build and Run the Container

From the repository root, build an image using the Dockerfile path and assign it a descriptive name. Then start a container and map host port 8080 to container port 8080.

# Build the image from the repository root
docker build -t todo-api -f TodoApi/Dockerfile .

# Run the container
docker run --rm -p 8080:8080 todo-api

# List running containers in another terminal
docker ps

# View application logs
docker logs <container-id>

After the container starts, send an HTTP request to http://localhost:8080 using an API route supported by your application. A newly generated API may expose different routes depending on its template and configuration, so do not assume the root URL or a particular endpoint exists. The --rm option removes the container after it stops; it does not delete the image.

If the application starts but cannot be reached, verify the host-to-container port mapping, the port on which ASP.NET Core is listening, and the container logs. Remember that EXPOSE alone does not create a host port mapping.

7. Configure Environment Variables and Secrets

Container images should be reusable across development, testing, staging, and production. Environment-specific values such as database connection settings, service URLs, and feature flags should be supplied at deployment time rather than embedded in the image.

For example, Docker supports passing non-secret settings with the -e option. Applications can read these values through the standard .NET configuration system, provided their configuration keys and binding are correct.

docker run --rm   -p 8080:8080   -e ASPNETCORE_ENVIRONMENT=Production   todo-api

Do not pass real credentials in commands that may be saved in shell history or exposed through process inspection. For sensitive configuration, use an appropriate secrets manager or secure deployment-platform integration. Environment variables are a configuration mechanism, not an encrypted secret store. See the .NET configuration guide for more about application settings and secret handling.

8. Docker Networking and Service Communication

A container's localhost refers to that container itself, not automatically to another container or to the host machine. This distinction becomes important when your API connects to PostgreSQL, Redis, or another service running in a separate container.

When multiple services belong to the same Docker network, configure applications to connect using the appropriate service or container DNS name and internal port. For example, an API container communicating with a database container on the same user-defined network can usually use the database service name as its host. The connection string must still use the correct database port, credentials, and database name.

For applications with several services, Docker Compose can define services, networks, environment settings, and dependencies in a YAML file. Compose is useful for local development and can also support other controlled container workflows, although production deployments may use a different orchestration platform.

9. Security and Production Best Practices

  • Use multi-stage builds: Keep SDK tools and intermediate source artifacts out of the final runtime image when they are not needed.
  • Run as a non-root user: Use a suitable non-root identity and ensure the application can access only the files and resources it needs.
  • Keep images updated: Rebuild and redeploy regularly to receive supported runtime and operating-system security fixes.
  • Pin image versions: Use explicit SDK and runtime tags rather than floating tags such as latest. For stricter reproducibility, pin image digests and establish a process for reviewing and adopting security updates.
  • Protect credentials: Never bake production passwords, access tokens, or private keys into Dockerfiles or image layers.
  • Minimize the image: Copy only required published files into the final stage and select an appropriate supported runtime image.
  • Configure health and monitoring: Use platform-supported health checks, structured logs, and application telemetry according to the deployment environment.
  • Set resource limits: Apply suitable memory and CPU limits where supported, and verify that the application behaves correctly under those limits.
  • Test the published image: Run integration and smoke tests against the container image that will actually be deployed.

For a secure release process, combine container image versioning with automated testing, vulnerability scanning, a trusted image registry, and an intentional deployment and rollback strategy. These steps complement the wider practices described in the .NET deployment guide.

10. Common Docker Problems and Troubleshooting

Docker failures often result from differences between the build context, the published application, and the runtime environment. If docker build cannot find a project file, verify the build context and every COPY path. If the build reports a target framework or package error, check the project's target framework and the SDK image version.

If the container exits immediately, inspect the output with docker logs. A missing assembly, invalid configuration, unavailable database, or incorrect entry point can cause startup to fail. If the process runs but HTTP requests fail, check the application listening port and the host port mapping. If a dependency is unreachable, confirm that both containers are attached to the intended network and that the connection string uses the correct hostname.

Avoid fixing every failure by switching to a privileged user or disabling security controls. Identify the failing layer first, then correct the Dockerfile, application configuration, permissions, or network settings as appropriate.

11. Summary

Docker makes .NET applications easier to package, test, and deploy consistently. The central workflow is to create a Dockerfile, build the application with an SDK image, publish it, copy the result into a runtime image, and start a container with the required ports and configuration. A multi-stage build keeps the final image focused on executing the application rather than compiling it.

Start with one ASP.NET Core API and verify that it works both locally and inside a container. Then add environment-specific configuration, appropriate logging, non-root execution, image versioning, and automated tests. As your system grows, explore Docker Compose for local multi-service development and integrate container builds into your CI/CD pipeline. Pair these practices with .NET application architecture and .NET best practices to create reliable applications that are easier to operate in production.

Code Examples

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

COPY ["TodoApi/TodoApi.csproj", "TodoApi/"]
RUN dotnet restore "TodoApi/TodoApi.csproj"

COPY . .
WORKDIR /src/TodoApi
RUN dotnet publish "TodoApi.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app

COPY --from=build /app/publish .

ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080

USER $APP_UID
ENTRYPOINT ["dotnet", "TodoApi.dll"]
    
# Run from the repository root
docker build -t todo-api -f TodoApi/Dockerfile .

# Start the container and map the application port
docker run --rm -p 8080:8080 todo-api

# In another terminal, inspect running containers
docker ps

# Inspect application output
docker logs <container-id>

# Stop a running container
docker stop <container-id>
    
**/bin/
**/obj/
.git/
.vs/
.vscode/
*.user
*.suo
.env
.env.*
    

Pro Tip

Use a multi-stage Dockerfile with matching SDK and runtime versions. Keep credentials outside the image, run the application as a non-root user, and rebuild images regularly to adopt security updates.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.