Skip to main content
SEMastery
DevOpsbeginner

Using .NET Aspire With the Docker Publisher: A Beginner Guide

Learn how to turn a .NET Aspire app into a ready-to-run docker-compose.yaml with one command using the Docker publisher. Simple, step-by-step guide.

11 min readUpdated December 1, 2025

Imagine your mother is cooking for a big family dinner. She has the rice cooker, the dal pot, the curry pan, and the chai kettle all going at once. In her head, she knows which one to start first, which one needs medium heat, and which one must wait. Now imagine she has to write all of that down on one sheet of paper so that any relative can walk into the kitchen and cook the exact same dinner without asking her a single question.

That single sheet of paper is what a docker-compose file is. It lists every pot (container), the heat (settings), and the order. And the .NET Aspire Docker publisher is the helper that writes that sheet of paper for you, automatically, from the code you already wrote.

In this guide you will learn what the Docker publisher is, why it is so useful, and how to use it step by step. We will keep the words simple and the steps small.

What problem are we solving?

When you build an app with .NET Aspire, you describe all your services in one place called the AppHost. You say "I want a web API, a Postgres database, and a Redis cache." Aspire then runs all of these together on your machine while you develop.

But your laptop is not the internet. One day you want to put this app on a real server. A very common way to run apps on a server is Docker Compose — that one sheet of paper we talked about. The problem is that writing a correct docker-compose.yaml by hand is fiddly. You must match ports, passwords, image names, and the order of startup. One small typo and nothing works.

The Docker publisher fixes this. You already told Aspire everything in C#. So Aspire can read that and write the Compose file for you.

From code to a running server

Write AppHost in C#
Run aspire publish
Get docker-compose.yaml + .env
docker compose up on server

Steps

1

Write AppHost in C#

Describe services once

2

Run aspire publish

One command

3

Get files

Compose + env file

4

docker compose up

App runs on server

The publisher turns your C# app model into deployable files

A quick picture of how it fits together

Before we touch code, let us see the big shape of things. You have your code on the left. Aspire sits in the middle as a translator. Docker Compose sits on the right as the thing that actually runs your containers.

How Aspire connects your code to Docker Compose

Notice that Aspire produces two files. The docker-compose.yaml holds the structure. The .env file holds the secret-ish values like passwords and image names. Keeping them apart is good practice. You can share the Compose file freely but keep the env file private.

Step 1: Install the Aspire CLI

The publisher is driven by a command line tool called the Aspire CLI. If you do not have it yet, install it with one line. You only do this once on your machine.

// This is a shell command, shown here for reference.
// Run it in your terminal, not inside a .cs file:
//
//   dotnet tool install --global aspire.cli
//
// On older preview builds you may need the --prerelease flag.

After it installs, check that it works by running aspire --version in your terminal. If you see a version number, you are ready.

Current versions of .NET 10 (which is LTS) and Aspire ship a stable Aspire CLI, so you usually do not need preview flags anymore. If you are on an older setup, the prerelease flag shown above still works.

Step 2: Add the Docker package to your AppHost

Your AppHost needs to know about Docker. Open a terminal in your AppHost folder and add the package.

// Shell command (run in the AppHost project folder):
//
//   aspire add docker
//
// This adds the NuGet package "Aspire.Hosting.Docker"
// to your AppHost project file.

This brings in the methods we are about to use. Behind the scenes it edits your .csproj to reference Aspire.Hosting.Docker.

Step 3: Tell Aspire you want a Docker Compose environment

Now we write a tiny bit of C#. Open your AppHost.cs (sometimes called Program.cs in the AppHost). You add one line to declare a Docker Compose environment.

var builder = DistributedApplication.CreateBuilder(args);
 
// This single line turns on the Docker publisher.
var compose = builder.AddDockerComposeEnvironment("compose");
 
// Your normal services, described as usual:
var cache = builder.AddRedis("cache");
 
var db = builder.AddPostgres("postgres")
    .AddDatabase("appdb");
 
builder.AddProject<Projects.Api>("api")
    .WithReference(cache)
    .WithReference(db);
 
builder.Build().Run();

That AddDockerComposeEnvironment("compose") call is the magic switch. The name "compose" is just a label. Everything else is the same Aspire code you would write anyway. You did not have to learn a new way to describe your app. You only added one line to say "I also want to publish this as Docker Compose."

The services in our small example app

Step 4: Run the publish command

Now the fun part. In the AppHost folder, run:

// Shell command:
//
//   aspire publish
//
// Or point at the project directly:
//
//   aspire publish -p docker-compose --project ./AppHost

Aspire thinks for a few seconds. It builds the container images for your projects. Then it writes the files. When it finishes you will find a new folder with these inside:

  • docker-compose.yaml — the structure of all your services
  • .env — the values, like the Postgres password and image names

Open the docker-compose.yaml and you will see services for your API, Redis, and Postgres, all wired together with the right ports and references. You did not type a single line of YAML.

What aspire publish does under the hood

Read app model
Build images
Resolve ports & secrets
Write YAML + env

Steps

1

Read app model

Your C# graph

2

Build images

For each project

3

Resolve ports & secrets

Match references

4

Write YAML + env

Final files

Each stage happens automatically, in order

Step 5: Run it like any Docker Compose app

Now you are back on familiar ground. Move the generated files to your server (or stay on your laptop to test). Then run the normal Docker command:

// Shell command:
//
//   docker compose up -d
//
// -d means "detached" so it runs in the background.

Docker reads the docker-compose.yaml, reads the .env, pulls or uses the built images, and starts everything in the right order. Your whole Aspire app is now running as plain containers. Any server with Docker can run it, even a cheap cloud box.

Publish is not the same as deploy

This trips up a lot of beginners, so let us be clear with a small table.

CommandWhat it doesDoes it start anything?
aspire publishCreates the files (YAML, env, images)No
aspire deployCreates the files and applies them to a targetYes

Think of publish as printing the recipe sheet. Think of deploy as printing the sheet and cooking the dinner. For your first time, publish is safer because you can read the files and check them before anything runs.

Publish stops at files; deploy goes all the way

Customising what gets generated

The default output is good, but sometimes you want to change a small thing. Aspire gives you neat hooks for this. For example, you might want to rename a service or set a restart policy so a container comes back after a crash.

builder.AddRedis("cache")
    .PublishAsDockerComposeService((resource, service) =>
    {
        service.Name = "redis";
        service.ContainerName = "my-redis";
        service.Restart = "unless-stopped";
        service.Labels.Add("com.example.team", "backend");
    });

Here PublishAsDockerComposeService lets you reach into the generated service and adjust it. The Restart = "unless-stopped" line is handy: it tells Docker to keep the container alive unless you stop it on purpose.

You can also tweak the env file. Say you want a friendly description for a value so the next person understands it.

builder.AddDockerComposeEnvironment("compose")
    .ConfigureEnvFile(env =>
    {
        env["DATA_DIR"].Description = "Folder where app data is stored";
        env["DATA_DIR"].DefaultValue = "./data";
    });

And if you need to touch the whole Compose file at once, there is a hook for that too.

builder.AddDockerComposeEnvironment("compose")
    .ConfigureComposeFile(composeFile =>
    {
        composeFile.Name = "my-app";
        var api = composeFile.Services["api"];
        api.PullPolicy = "always";
    });

These hooks mean you almost never have to hand-edit the YAML. You keep everything in C#, where it is checked by the compiler and saved in source control.

Pushing images to a registry

The Compose file points at container images. On your laptop those images live locally. But your server cannot see your laptop. So you push the images to a container registry — a shared shelf in the cloud, like GitHub Container Registry or Docker Hub.

var registry = builder.AddContainerRegistry(
    "ghcr",
    "ghcr.io",
    "your-username/your-repo");
 
builder.AddProject<Projects.Api>("api")
    .WithContainerRegistry(registry);

After this, the publisher knows the full image name to write into the Compose file. To actually build and upload the images, you run a push command.

// Shell command:
//
//   aspire do push
//
// Builds your images and uploads them to the registry above.

Now any server that can reach the registry can pull the images and start your app.

Getting images to a server

Build image locally
Push to registry
Server pulls image
Compose starts it

Steps

1

Build image locally

On your machine

2

Push to registry

aspire do push

3

Server pulls image

From the cloud shelf

4

Compose starts it

docker compose up

Local images travel through a registry to the server

A side-by-side comparison

People sometimes ask: why not just write the Compose file myself? Here is an honest comparison.

TaskBy handWith the Docker publisher
Match service portsYou track them yourselfDone for you
Wire passwords and connection stringsCopy-paste, easy to breakGenerated and linked
Keep code and YAML in syncTwo files to updateOne source of truth in C#
Onboarding a teammateRead a long YAMLRead clear C# they already know

The publisher does not lock you in. The output is plain, normal Docker Compose. If you ever stop using Aspire, the YAML still works on its own. That is a comforting safety net.

A few things to watch out for

A couple of small bumps that beginners hit:

  • Docker must be running. The publisher builds images, so the Docker engine has to be alive on your machine. If you see an error about the Docker daemon, just start Docker Desktop.
  • The .env file holds secrets. Do not commit it to a public repo. Add it to your .gitignore. Treat passwords with care.
  • Ports can clash. If port 8080 is already used on your server, change it in the C# config before publishing, not after, so your source stays the single source of truth.
  • Preview features move fast. Aspire is improving quickly. If a method name changed since you read this, check the official docs linked below.

When should you use this?

The Docker publisher shines when you want a simple, cheap, portable deployment. A single virtual machine with Docker installed is enough. It is great for small teams, side projects, internal tools, and demos. If later you grow into Kubernetes or Azure Container Apps, Aspire has publishers for those too, and you reuse most of the same AppHost code. You are not throwing work away.

References and further reading

Quick recap

  • A docker-compose.yaml is one sheet that describes all your containers; the Docker publisher writes it for you.
  • Install the Aspire CLI, then run aspire add docker in your AppHost.
  • Add one line: builder.AddDockerComposeEnvironment("compose").
  • Run aspire publish to get a docker-compose.yaml and a .env file.
  • Run docker compose up -d to start everything on any server with Docker.
  • publish only writes files; deploy writes files and starts them.
  • Use PublishAsDockerComposeService, ConfigureEnvFile, and ConfigureComposeFile to fine-tune the output without editing YAML by hand.
  • Push images to a registry with aspire do push so your server can pull them.
  • Keep the .env file private and make sure Docker is running before you publish.

Related Posts