How to Build and Run a .NET App in a Docker Container

Docker containers offer an isolated and portable environment, enabling developers to seamlessly deploy their .NET applications across various environments, from development machines to testing servers and production infrastructure. This versatility has made them a cornerstone in facilitating .NET development services. In this blog post, we’ll guide you through the complete process of constructing and executing a sample .NET Core console application within a Docker container.

Creating a .NET Core Console App

The first step is to create a basic .NET Core console application that we will later containerize. Open up your terminal or command prompt and run the following commands:

dotnet new console -o SampleApp
cd SampleApp

This will generate a new .NET Core console app project in a directory called SampleApp. Open up the generated SampleApp.csproj file and update the TargetFramework property to target .NET Core 3.1:

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

Next, open up the Program.cs file and add a simple “Hello World” message:

using System;

namespace SampleApp
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello World!");
    }
  }
}

Now you have a basic .NET Core console app that prints “Hello World” to the console when run.

Creating the Dockerfile

The next step is to create a Dockerfile that contains the instructions for building a Docker image for this application. Create an empty file called Dockerfile in the SampleApp directory.

The first line of the Dockerfile specifies which base image our Docker image will be built on top of. For .NET Core apps, the official Microsoft Docker images provide the .NET Core runtime and are a good choice as a base image:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env

The build stage will copy the source code into the container, restore dependencies, compile the code, and publish the output:

WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o out

After building, we use a second Docker image as a runtime image for the published app files. This keeps the final image lightweight:

FROM mcr.microsoft.com/dotnet/core/runtime:3.1
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "SampleApp.dll"]

The full Dockerfile:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/core/runtime:3.1
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "SampleApp.dll"]

Building the Docker Image

Now that we have the Dockerfile ready, we can build the Docker image. Navigate to the folder containing the Dockerfile and run:

docker build -t sampleapp .

This will build a Docker image called “sampleapp” using the Dockerfile in the current directory. Docker will layer the image, restoring dependencies, publishing, and ultimately producing a Docker image containing our .NET application.

Running the Docker Container

To run the Docker container from the newly built image, run:

docker run -it sampleapp

This will start a new container from the sampleapp image, launching the application’s entrypoint which runs dotnet SampleApp.dll. You should see the “Hello World!” output in your terminal.

To run the container in detached mode in the background:

docker run -d --name sampleappcontainer sampleapp

Now the container will run in the background. You can stop, start, or inspect the container using the container name:

docker stop sampleappcontainer
docker start sampleappcontainer
docker logs sampleappcontainer

You have now successfully built and run a .NET Core app inside a Docker container. With Docker, your application is packaged into a standard, portable unit that can be deployed anywhere Docker runs without dependencies on the underlying infrastructure.

Adding Configuration

Often applications require configuration values to be set at runtime. With Docker, these values can be passed into the container as environment variables.

Open Program.cs and modify the main method to read an environment variable:

static void Main(string[] args)
{
  var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
  Console.WriteLine($"Connection string is {connectionString}"); 
}

When running the container, pass the value using -e:

docker run -e ConnectionString="server=localhost;database=app;user=sa;password=Passw0rd" sampleappcontainer

Now the app reads the connection string from the environment.

You can also define default configuration values in appsettings.json and override them at runtime. For example:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

Reference the settings file in Program.cs:

static void Main(string[] args)
{
  var configuration = new ConfigurationBuilder() 
    .AddJsonFile("appsettings.json")
    .Build();

  var logLevel = configuration["Logging:LogLevel:Default"];

  // log using logLevel
}

Then override LogLevel when running the container:

docker run -e Logging__LogLevel__Default="Debug" sampleappcontainer

Now the app reads the connection string from the environment.

You can also define default configuration values in appsettings.json and override them at runtime. For example:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }

Reference the settings file in Program.cs:

static void Main(string[] args)
{
  var configuration = new ConfigurationBuilder() 
    .AddJsonFile("appsettings.json")
    .Build();

  var logLevel = configuration["Logging:LogLevel:Default"];

  // log using logLevel
}

Then override LogLevel when running the container:

docker run -e Logging__LogLevel__Default="Debug" sampleappcontainer

These examples demonstrate how Docker enables flexible configuration of applications at runtime through environment variables.

Docker Compose

For multi-container applications, Docker Compose allows defining and running multiple application containers and their dependencies in a single configuration file.

Create a docker-compose.yml:

version: '3'

services:

  app:
    image: sampleapp
    ports:
      - "8080:80"

  database: 
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - MYSQL_DATABASE=app
    ports:
      - "3306:3306"

Now instead of launching individual containers, use Docker Compose:

docker-compose up

This will start both the app and database containers together in the background. The database is only accessible to the app container internally on its container network.

Docker Compose is very useful for testing multi-container setups locally before deployment to production environments. Entire development environments with full application stacks including dependencies can be reproduced consistently.

Building for Production

When deploying to production, you should consider container security and optimizations for performance and size. Here are some best practices:

  • Use multi-stage builds to produce a slimmer runtime image from your build artifacts
  • Scan images for vulnerabilities with tools like Anchore or Trivy
  • Sign and verify images trust with signatures
  • Minimize exposed ports and use network policies for security
  • Consider a content trust/registries like Docker Trusted Registry
  • Use a CI/CD tool like Docker/Kubernetes for automated builds and deployments

For production deployments to a Kubernetes cluster:

  • Define deployment, services and other resources in Kubernetes manifests
  • Build optimized container images with specific tags for each environment
  • Automate rolling deployments of new versions
  • Configure resource limits, auto-scaling and health checks
  • Implement monitoring, logging and alerting integrations

Containerizing .NET applications with Docker delivers substantial benefits in terms of portability, reproducibility, and scalability across Kubernetes or Docker-powered infrastructure. This approach, coupled with DevOps best practices, unleashes the complete potential for automated and resilient application deployments, making it an enticing proposition for businesses looking to hire .NET developers proficient in leveraging Docker for efficient application deployment strategies.

How to Build and Run a .NET App in a Docker Container was last updated March 2nd, 2024 by Stephen Fleming