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.
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
Steps
Write AppHost in C#
Describe services once
Run aspire publish
One command
Get files
Compose + env file
docker compose up
App runs on server
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.
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."
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 ./AppHostAspire 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
Steps
Read app model
Your C# graph
Build images
For each project
Resolve ports & secrets
Match references
Write YAML + env
Final files
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.
| Command | What it does | Does it start anything? |
|---|---|---|
aspire publish | Creates the files (YAML, env, images) | No |
aspire deploy | Creates the files and applies them to a target | Yes |
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.
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
Steps
Build image locally
On your machine
Push to registry
aspire do push
Server pulls image
From the cloud shelf
Compose starts it
docker compose up
A side-by-side comparison
People sometimes ask: why not just write the Compose file myself? Here is an honest comparison.
| Task | By hand | With the Docker publisher |
|---|---|---|
| Match service ports | You track them yourself | Done for you |
| Wire passwords and connection strings | Copy-paste, easy to break | Generated and linked |
| Keep code and YAML in sync | Two files to update | One source of truth in C# |
| Onboarding a teammate | Read a long YAML | Read 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
.envfile 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
- Aspire Docker integration (official docs) — the source of truth for the methods used here.
- Aspire 9.2 is now available with new ways to deploy (.NET Blog) — background on the publish and deploy model.
- Using .NET Aspire with the Docker Publisher (Milan Jovanovic) — a clear community walkthrough.
- Converting a docker-compose file to .NET Aspire (Andrew Lock) — helpful if you are coming from the other direction.
Quick recap
- A
docker-compose.yamlis one sheet that describes all your containers; the Docker publisher writes it for you. - Install the Aspire CLI, then run
aspire add dockerin your AppHost. - Add one line:
builder.AddDockerComposeEnvironment("compose"). - Run
aspire publishto get adocker-compose.yamland a.envfile. - Run
docker compose up -dto start everything on any server with Docker. publishonly writes files;deploywrites files and starts them.- Use
PublishAsDockerComposeService,ConfigureEnvFile, andConfigureComposeFileto fine-tune the output without editing YAML by hand. - Push images to a registry with
aspire do pushso your server can pull them. - Keep the
.envfile private and make sure Docker is running before you publish.
Related Posts
Getting Started With .NET Aspire 13: Building and Deploying an App
A beginner-friendly guide to .NET Aspire 13: build a small app with PostgreSQL and Redis, watch it run on the dashboard, then deploy with Docker Compose.
.NET Aspire: A Game Changer for Cloud-Native Development
A beginner-friendly guide to .NET Aspire, the cloud-native stack that orchestrates your services, databases, and dashboards with one simple command.
How .NET Aspire Simplifies Service Discovery for Your Apps
Learn how .NET Aspire service discovery lets your services find each other by name, with no hardcoded URLs, ports, or environment headaches.
Containerize Your .NET Applications Without a Dockerfile
Learn how to build container images for your .NET apps using the SDK and dotnet publish, with no Dockerfile needed. Beginner-friendly guide for .NET 10.
How to Build a CI/CD Pipeline With GitHub Actions and .NET
A beginner-friendly guide to building a CI/CD pipeline for .NET with GitHub Actions: build, test, cache, publish, and deploy your app automatically.
Standalone Aspire Dashboard Setup for Distributed .NET Applications
Learn to run the standalone Aspire dashboard in Docker and view traces, logs, and metrics from your distributed .NET apps over OTLP, step by step.