Docker � �10 min read

Deploying a .NET App with Docker Compose

Docker makes it easy to package a .NET application and its dependencies into a consistent, portable environment. Docker Compose takes this a step further by allowing you to define and run your application and supporting services from a single configuration file. In this article, we’ll look at how to deploy a .NET application using Docker Compose.

Admin
Admin
.NET & IoT Developer
Deploying a .NET App with Docker Compose

What Is Docker Compose?

Docker Compose is a tool for defining and running multi-container applications. Instead of manually creating and configuring each container, you can describe the application's services, networking, ports, volumes and other settings in a YAML file.

For a .NET application, this could mean having one container running the ASP.NET Core application and another running a database such as SQL Server or PostgreSQL. Docker Compose creates the necessary network between the containers, allowing them to communicate with one another.

This makes Compose particularly useful when an application consists of more than one component. Rather than having to remember a collection of Docker commands, the entire application environment can be started with a single command.

Preparing the .NET Application

For this example, we’ll assume that we have a simple ASP.NET Core application called MyDockerApp.

The application can be created using the .NET CLI with the following command:

dotnet new web -n MyDockerApp

Once the project has been created, it can be tested normally using:

dotnet run

Before introducing Docker Compose, it is useful to make sure the application works correctly on the development machine. Docker should be used to package and deploy an application that already works rather than introducing another layer of complexity while trying to diagnose application problems.

Creating the Dockerfile

The first step in containerising the application is to create a Dockerfile in the root of the project.

A multi-stage Docker build is a good approach for .NET applications because the .NET SDK is required to build the application but isn't normally required in the final production container.

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

WORKDIR /src

COPY ["MyDockerApp.csproj", "."]
RUN dotnet restore "MyDockerApp.csproj"

COPY . .
RUN dotnet publish "MyDockerApp.csproj" -c Release -o /app/publish --no-restore

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

WORKDIR /app

COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "MyDockerApp.dll"]

The first stage uses the .NET SDK image to restore, build and publish the application. The second stage uses the smaller ASP.NET Core runtime image and copies only the published application into it.

This approach keeps the final image smaller and avoids shipping development and build tools that the application doesn't need at runtime. Microsoft's .NET container guidance recommends separating build and runtime concerns in this way.

Creating the Compose File

With the Dockerfile in place, we can create a compose.yaml file in the same directory as the project.

A simple Compose configuration might look like this:

services:
  web:
    build:
      context: .
    ports:
      - "8080:8080"

The services section defines the individual components that make up the application.

In this example, the service is called web. The build section tells Docker Compose to build an image using the Dockerfile in the current directory.

The ports section maps port 8080 on the host machine to port 8080 inside the container.

Modern Docker Compose uses the Compose Specification, so a version property is no longer required in the Compose file. The older version 2.x and 3.x formats have been consolidated into the Compose Specification.

Building and Starting the Application

With the Dockerfile and compose.yaml in place, the application can be built and started using:

docker compose up --build

The --build option tells Docker Compose to rebuild the image before starting the containers.

Once the container has started, the application should be available at:

http://localhost:8080

The advantage here is that Docker Compose handles the creation and startup of the container for us. If the image has already been built and there have been no changes requiring a rebuild, the application can subsequently be started with:

docker compose up

Docker documents docker compose up as the standard way to create and start the services defined by a Compose file.

Running the Application in the Background

When deploying an application, you will often want the containers to continue running after closing the terminal.

Docker Compose provides the -d option for detached mode:

docker compose up -d

The command returns control to the terminal while the containers continue running in the background.

You can check the current state of the services with:

docker compose ps

This is useful for quickly checking whether the application container is running and which ports have been exposed.

Viewing Application Logs

When something goes wrong with a containerised application, logs are usually the first place to look.

Docker Compose makes it easy to view the logs for the application:

docker compose logs

You can also follow the logs as they are generated:

docker compose logs -f

For a larger application containing several services, you can request the logs for a particular service:

docker compose logs web

This is particularly useful when an application depends on other containers such as a database.

Adding a Database

One of the biggest advantages of Docker Compose becomes apparent when an application requires additional services.

For example, suppose our .NET application uses PostgreSQL. Instead of installing PostgreSQL directly onto the development or deployment machine, it can be defined as another Compose service.

The compose.yaml could be expanded to:

services:
  web:
    build:
      context: .
    ports:
      - "8080:8080"
    depends_on:
      - db

  db:
    image: postgres:18
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: example

The application and database are now separate containers managed by Docker Compose.

The depends_on setting tells Compose that the web service depends on the database service. Compose also creates a network for the application, allowing services to communicate using their service names. This means the .NET application can use db as the database hostname rather than trying to locate the database using an IP address.

Persisting Database Data

Containers are designed to be disposable, so important data should not normally be stored only inside a container's writable filesystem.

Docker volumes can be used to persist database data independently of the database container.

For example:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: example
    volumes:
      - db-data:/var/lib/postgresql

volumes:
  db-data:

The named volume called db-data allows the database data to survive if the database container is removed and recreated.

This separation between containers and persistent data is an important part of designing reliable containerised applications.

Configuring the .NET Application

Configuration should generally be supplied to the container rather than being hard-coded into the application.

Docker Compose allows environment variables to be specified for a service.

For example:

services:
  web:
    build:
      context: .
    ports:
      - "8080:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Production
      ConnectionStrings__DefaultConnection: "Host=db;Database=myapp;Username=myapp;Password=example"

ASP.NET Core converts the double underscore in an environment variable name into a configuration hierarchy separator. This means ConnectionStrings__DefaultConnection corresponds to the ConnectionStrings:DefaultConnection configuration value used by the application.

For real deployments, passwords and other sensitive information should not be committed directly into the Compose file. Docker Compose supports secrets, which can be used to keep sensitive values separate from the main configuration.

Stopping the Application

When the application is no longer required, the containers can be stopped using:

docker compose down

This removes the containers and networks created by Compose but leaves named volumes in place by default.

If you also want to remove the volumes, you can use:

docker compose down -v

Care should be taken with this command when a database is involved because removing its volume can permanently remove the stored database data.

Deploying to a Server

The same Docker Compose configuration can be used as part of a deployment process on a suitable server.

The server needs Docker and Docker Compose support. Docker Desktop includes Docker Compose on Windows, macOS and Linux, while Docker Compose can also be installed as a plugin on Linux systems.

The application image can either be built directly on the server or, more commonly, built as part of a deployment pipeline and pushed to a container registry.

For a straightforward deployment where the source code is available on the server, the process might be as simple as:

docker compose build
docker compose up -d

For larger projects, a CI/CD pipeline can build the Docker image, run tests, push the image to a registry and then deploy the new version to the target server.

HTTPS and Production Deployments

When deploying an ASP.NET Core application for real users, HTTPS should be configured appropriately.

There are several ways of handling HTTPS with Docker. One option is to terminate HTTPS at a reverse proxy such as Nginx or another web server and forward requests to the ASP.NET Core container.

Another option is to configure HTTPS directly within ASP.NET Core.

For local development, ASP.NET Core provides development certificates, and Microsoft documents how these can be mounted into a container when using Docker Compose. Production deployments should use a certificate issued by a trusted certificate authority.

For many production environments, terminating HTTPS at a reverse proxy is a cleaner approach because the reverse proxy can also handle certificates, HTTP-to-HTTPS redirection and other web-server responsibilities.

Keeping the Production Image Small

One of the important considerations when deploying .NET applications with Docker is the size of the final image.

The SDK image contains everything required to build a .NET application, including compilers and development tools. The ASP.NET Core runtime image contains the components required to run an ASP.NET Core application.

Using a multi-stage Dockerfile means that the final container doesn't need to contain the SDK or the application's source code.

This makes the resulting image smaller and reduces the amount of software that needs to be present in the production environment.

It also means that deploying a new version becomes a matter of replacing the container with a new one based on the updated image.

Using Docker Compose for More Than Deployment

Although Docker Compose is often associated with local development, it can be useful throughout the application lifecycle.

A Compose file can describe the services required for development, testing, staging and production, with different configuration files or profiles used where appropriate.

For example, a development environment might contain the .NET application, SQL Server and a mail-testing service. A production environment might instead use an external database and a reverse proxy.

The benefit is that the relationships between the application's services can be described as code rather than relying on manually configured machines.

Conclusion

Docker Compose provides a straightforward way to package and deploy a .NET application alongside the services it depends on.

For a simple ASP.NET Core application, a Dockerfile and a small compose.yaml file are enough to build and run the application in a consistent environment. As the application grows, Docker Compose can manage additional services such as databases, caches and supporting applications.

The real benefit is consistency. Instead of documenting a long list of installation and configuration steps, the application's environment can be described in code and started with a small number of Docker commands.

For .NET developers, combining multi-stage Docker builds with Docker Compose provides a practical foundation for moving applications from a development machine towards repeatable deployments.

Share:

Become a member

Get the latest news right in your inbox. It's free and you can unsubscribe at any time. We hate spam as much as we do, so we never spam!

Read next

Multi-Stage Docker Builds for .NET

Multi-stage Docker builds provide a cleaner and more efficient way to package .NET applications. By separating the build environment from the final runtime image, you can keep development tools out of production, reduce image size and create more focused containers. In this article, we’ll look at how multi-stage Docker builds work and how to use them with modern .NET applications.

Admin 28-Sep-2026

The Bedroom Coder — retro computers, modern .NET, and late-night experiments.

Navigation

Contact

Want to talk retro tech or modern coding? I'd love to hear your thoughts.

© 2026 The Bedroom Coder. All rights reserved.