Skip to content
DocsTry Aspire
DocsTry

Docker integration

Docker logo

The Aspire Docker hosting integration enables you to deploy your Aspire applications using Docker Compose. This integration models Docker Compose environments as compute resources that can host your application services. When you use this integration, Aspire generates Docker Compose files that define all the services, networks, and volumes needed to run your application in a containerized environment. It supports:

  • Generating Docker Compose files from your app model for deployment
  • Orchestrating multiple services, including an Aspire dashboard for telemetry visualization
  • Configuring environment variables and service dependencies
  • Customizing generated Docker Compose files, .env files, dashboards, and service definitions
  • Managing container networking and service discovery
  • Building images from existing or programmatically generated Dockerfiles

To start building an Aspire app that uses Docker Compose, install the 📦 Aspire.Hosting.Docker NuGet package:

Terminal
aspire add docker

This updates your aspire.config.json with the Docker hosting integration package:

aspire.config.json
{
"packages": {
"Aspire.Hosting.Docker": "13.5.3"
}
}

The following example demonstrates how to add a Docker Compose environment to your app model:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addDockerComposeEnvironment(name: string): DockerComposeEnvironmentResource

Adds a Docker Compose environment to the application model.

addDockerComposeEnvironment
("compose");
const
const cache: RedisResource
cache
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addRedis(name: string, options?: {
port?: number;
password?: string | ParameterResource;
}): RedisResource (+1 overload)

Adds a Redis container to the application model.

addRedis
("cache");
await
const cache: RedisResource
cache
.
ContainerResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): RedisResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("redis");
});
const
const api: ProjectResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
launchProfileOrOptions?: ProjectResourceOptions;
}): ProjectResource (+1 overload)

Adds a .NET project resource

addProject
("api", "../Api/Api.csproj");
await
const api: ProjectResource
api
.
ProjectResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ProjectResource (+1 overload)

Adds a reference to another resource

withReference
(
const cache: RedisResource
cache
);
await
const api: ProjectResource
api
.
ProjectResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): ProjectResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("api");
});
const
const web: ProjectResource
web
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
launchProfileOrOptions?: ProjectResourceOptions;
}): ProjectResource (+1 overload)

Adds a .NET project resource

addProject
("web", "../Web/Web.csproj");
await
const web: ProjectResource
web
.
ProjectResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ProjectResource (+1 overload)

Adds a reference to another resource

withReference
(
const cache: RedisResource
cache
);
await
const web: ProjectResource
web
.
ProjectResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ProjectResource (+1 overload)

Adds a reference to another resource

withReference
(
const api: ProjectResource
api
);
await
const web: ProjectResource
web
.
ProjectResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): ProjectResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("web");
});
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

The preceding code:

  • Creates a Docker Compose environment named compose
  • Adds a Redis cache service that will be included in the Docker Compose deployment
  • Adds an API service project that will be containerized and included in the deployment
  • Adds a web application that references both the cache and API service

When a Docker Compose environment is present, all resources are automatically published as Docker Compose services — no additional opt-in is required.

Add Docker Compose environment resource with properties

Section titled “Add Docker Compose environment resource with properties”

You can configure various properties of the Docker Compose environment using the WithProperties method:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const compose = await builder.addDockerComposeEnvironment("compose");
await compose.withProperties(async (environment) => {
await environment.dashboardEnabled.set(true);
});
await builder.build().run();

The DashboardEnabled property determines whether to include an Aspire dashboard for telemetry visualization in this environment.

Add Docker Compose environment resource with compose file

Section titled “Add Docker Compose environment resource with compose file”

You can customize the generated Docker Compose file using the ConfigureComposeFile method:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const compose = await builder.addDockerComposeEnvironment("compose");
await compose.configureComposeFile(async (composeFile) => {
await composeFile.name.set("my-app");
const api = await composeFile.services.get("api");
await api.pullPolicy.set("always");
});
await builder.build().run();

The ConfigureComposeFile callback runs after Aspire generates the Docker Compose model and before the docker-compose.yml file is written.

Add Aspire dashboard resource to environment

Section titled “Add Aspire dashboard resource to environment”

The Docker hosting integration includes an Aspire dashboard for telemetry visualization. You can configure or disable it using the WithDashboard method:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const compose = await builder.addDockerComposeEnvironment("compose");
await compose.configureDashboard(async (dashboard) => {
await dashboard.withHostPort({ port: 8080 });
await dashboard.withForwardedHeaders({ enabled: true });
});
await compose.withDashboard({ enabled: false });
await builder.build().run();

The WithHostPort method configures the port used to access the Aspire dashboard from a browser. The WithForwardedHeaders method enables forwarded headers processing when the dashboard is accessed through a reverse proxy or load balancer.

Use GetHostAddressExpression to get the Docker Compose host name that another service should use for an endpoint. In Docker Compose deployments, this expression resolves to the generated service name on the Compose network.

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const compose = await builder.addDockerComposeEnvironment("compose");
const api = await builder.addContainer("api", "nginx:alpine");
await api.withHttpEndpoint({ name: "http", targetPort: 80 });
const apiEndpoint = await api.getEndpoint("http");
const apiHost = await compose.getHostAddressExpression(apiEndpoint);

For a complete guide on the Docker Compose deployment workflow — publishing artifacts, preparing environments, deploying, and cleaning up — see Deploy to Docker Compose.

The Docker hosting integration captures environment variables from your app model and includes them in a .env file. This ensures that all configuration is properly passed to the containerized services.

For advanced scenarios, use ConfigureEnvFile to customize the generated .env file:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const api = await builder.addContainer("api", "nginx:alpine");
await api.withBindMount("/host/path/data", "/container/data");
const compose = await builder.addDockerComposeEnvironment("compose");
await compose.configureEnvFile(async (envVars) => {
const bindMount = await envVars.get("API_BINDMOUNT_0");
await bindMount.description.set("Customized bind mount source");
await bindMount.defaultValue.set("./data");
});
await builder.build().run();

This is useful when you need to add custom environment variables to the generated .env file or modify how environment variables are captured.

To customize the generated Docker Compose service for a specific resource, use the PublishAsDockerComposeService method. This is optional — all resources are automatically included in the Docker Compose output. Use this method only when you need to modify the generated service definition:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
await builder.addDockerComposeEnvironment("compose");
const containerName = await builder.addParameter("container-name");
const cache = await builder.addContainer("cache", "redis:latest");
await cache.publishAsDockerComposeService(async (composeService, service) => {
await service.containerName.set(
await containerName.asEnvironmentPlaceholder(composeService)
);
await service.labels.set("com.example.team", "backend");
await service.restart.set("unless-stopped");
});
await builder.build().run();

The configure callback receives the DockerComposeServiceResource and the generated Service object, allowing you to modify properties like labels, restart policy, container name, or other Docker Compose service settings. Use AsEnvironmentPlaceholder for values that should be written as Compose environment variable placeholders in the generated YAML and .env files.

Use AddDockerfileBuilder to create a container resource from a Dockerfile generated by AppHost code. Use WithDockerfileBuilder when you want to apply the generated Dockerfile to an existing container resource.

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
import type { DockerfileBuilderCallbackContext } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => {
const dockerfile = await context.builder();
await dockerfile
.from("node:22-alpine", { stageName: "build" })
.workDir("/app")
.copy("package*.json", "./")
.run("npm ci")
.copy(".", ".")
.run("npm run build");
await dockerfile
.from("nginx:alpine", { stageName: "runtime" })
.copyFrom("build", "/app/dist", "/usr/share/nginx/html")
.expose(80);
};
await builder.addDockerfileBuilder("frontend", "../frontend", configureDockerfile, {
stage: "runtime",
});
await builder.build().run();

Container resources support an ImagePullPolicy that controls when the container runtime pulls an image. The following policies are available:

PolicyDescription
DefaultUses the container runtime’s default behavior.
AlwaysAlways pulls the image, even if it already exists locally.
MissingPulls the image only if it doesn’t already exist locally.
NeverNever pulls the image from a registry. The image must already exist locally.

Use the WithImagePullPolicy extension method to set the policy on a container resource:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
,
type ImagePullPolicy = "Default" | "Always" | "Missing" | "Never"
const ImagePullPolicy: {
readonly Default: "Default";
readonly Always: "Always";
readonly Missing: "Missing";
readonly Never: "Never";
}

Enum Aspire.Hosting.ApplicationModel.ImagePullPolicy

ImagePullPolicy
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const container: ContainerResource
container
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainer(name: string, image: AddContainerOptions): ContainerResource

Adds a container resource to the application.

addContainer
("mycontainer", {
AddContainerOptions.image?: string | undefined
image
: "myimage",
AddContainerOptions.tag?: string | undefined
tag
: "latest" });
await
const container: ContainerResource
container
.
ContainerResource.withImagePullPolicy(pullPolicy: ImagePullPolicy): ContainerResource

Sets the pull policy for the container resource.

withImagePullPolicy
(
const ImagePullPolicy: {
readonly Default: "Default";
readonly Always: "Always";
readonly Missing: "Missing";
readonly Never: "Never";
}

Enum Aspire.Hosting.ApplicationModel.ImagePullPolicy

ImagePullPolicy
.
type Always: "Always"
Always
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

The Never policy is useful when working with locally-built images that shouldn’t be pulled from a registry. For example, when using a Dockerfile-based resource that you build and tag locally:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
,
type ImagePullPolicy = "Default" | "Always" | "Missing" | "Never"
const ImagePullPolicy: {
readonly Default: "Default";
readonly Always: "Always";
readonly Missing: "Missing";
readonly Never: "Never";
}

Enum Aspire.Hosting.ApplicationModel.ImagePullPolicy

ImagePullPolicy
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const app: ContainerResource
app
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainer(name: string, image: AddContainerOptions): ContainerResource

Adds a container resource to the application.

addContainer
("myapp", {
AddContainerOptions.image?: string | undefined
image
: "my-local-image",
AddContainerOptions.tag?: string | undefined
tag
: "dev" });
await
const app: ContainerResource
app
.
ContainerResource.withImagePullPolicy(pullPolicy: ImagePullPolicy): ContainerResource

Sets the pull policy for the container resource.

withImagePullPolicy
(
const ImagePullPolicy: {
readonly Default: "Default";
readonly Always: "Always";
readonly Missing: "Missing";
readonly Never: "Never";
}

Enum Aspire.Hosting.ApplicationModel.ImagePullPolicy

ImagePullPolicy
.
type Never: "Never"
Never
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

When you publish resources to a Docker Compose environment, the ImagePullPolicy set with WithImagePullPolicy is automatically mapped to the Docker Compose pull_policy field on the generated service:

ImagePullPolicyDocker Compose pull_policy
Alwaysalways
Missingmissing
Nevernever
Default(omitted — uses runtime default)

When deploying containers, you can customize how container images are named and tagged when pushed to a registry. This is useful when you need to:

  • Use different image names for different environments
  • Apply custom tagging strategies (e.g., semantic versioning, Git commit hashes)
  • Push to different registries or namespaces

Use WithRemoteImageName and WithRemoteImageTag to customize the image reference:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const api: ProjectResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
launchProfileOrOptions?: ProjectResourceOptions;
}): ProjectResource (+1 overload)

Adds a .NET project resource

addProject
("api", "../Api/Api.csproj");
await
const api: ProjectResource
api
.
ProjectResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): ProjectResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("api");
});
await
const api: ProjectResource
api
.
ProjectResource.withRemoteImageName(remoteImageName: string): ProjectResource

Sets the remote image name (without registry endpoint or tag) for container push operations.

withRemoteImageName
("myorg/myapi");
await
const api: ProjectResource
api
.
ProjectResource.withRemoteImageTag(remoteImageTag: string): ProjectResource

Sets the remote image tag for container push operations.

withRemoteImageTag
("v1.0.0");

For more complex scenarios, use WithImagePushOptions to register a callback that can dynamically configure push options:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const api = await builder.addProject("api", "../Api/Api.csproj");
await api
.publishAsDockerComposeService(async (resource, service) => {
await service.name.set("api");
})
.withImagePushOptions(async (context) => {
const imageName = context.resource.getResourceName().toLowerCase();
context.options.setRemoteImageName(`myorg/${imageName}`);
context.options.setRemoteImageTag("latest");
});

For asynchronous operations (such as retrieving configuration from external sources), use the async overload:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const api = await builder.addProject("api", "../Api/Api.csproj");
await api
.publishAsDockerComposeService(async (resource, service) => {
await service.name.set("api");
})
.withImagePushOptions(async (context) => {
const config = await loadImageConfiguration();
context.options.setRemoteImageName(config.imageName);
context.options.setRemoteImageTag(config.imageTag);
});
async function loadImageConfiguration() {
return {
imageName: "myorg/api",
imageTag: "latest",
};
}

Multiple callbacks can be registered on the same resource, and they will be invoked in the order they were added.

You can configure your Aspire application to push container images to registries like GitHub Container Registry (GHCR), Docker Hub, or private registries using the AddContainerRegistry method.

Use the AddContainerRegistry method to define a container registry and the WithContainerRegistry method to associate resources with that registry:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
// Add a container registry
const
const registry: ContainerRegistryResource
registry
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainerRegistry(name: string, endpoint: string | ParameterResource, options?: {
repository?: string | ParameterResource;
}): ContainerRegistryResource (+1 overload)

Adds a container registry resource

addContainerRegistry
(
"ghcr",
"ghcr.io",
{
repository?: string | ParameterResource | undefined
repository
: "your-github-username/your-repo" }
);
// Associate resources with the registry
const
const api: ProjectResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
launchProfileOrOptions?: ProjectResourceOptions;
}): ProjectResource (+1 overload)

Adds a .NET project resource

addProject
("api", "../Api/Api.csproj");
await
const api: ProjectResource
api
.
ProjectResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): ProjectResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("api");
});
await
const api: ProjectResource
api
.
ProjectResource.withContainerRegistry(registry: IResource): ProjectResource

Configures the resource to use the specified container registry for container image operations.

withContainerRegistry
(
const registry: ContainerRegistryResource
registry
);

For GitHub Container Registry, the registry endpoint is ghcr.io and the repository path typically follows the pattern owner/repository.

Using parameters for registry configuration

Section titled “Using parameters for registry configuration”

For more flexible configuration, especially in CI/CD pipelines, you can use parameters with environment variables:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
// Define parameters from configuration (reads from environment variables)
const
const registryEndpoint: ParameterResource
registryEndpoint
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addParameterFromConfiguration(name: string, configurationKey: string, options?: {
secret?: boolean;
}): ParameterResource (+1 overload)

Adds a parameter resource to the application, with a value coming from configuration.

addParameterFromConfiguration
(
"registryEndpoint",
"REGISTRY_ENDPOINT"
);
const
const registryRepository: ParameterResource
registryRepository
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addParameterFromConfiguration(name: string, configurationKey: string, options?: {
secret?: boolean;
}): ParameterResource (+1 overload)

Adds a parameter resource to the application, with a value coming from configuration.

addParameterFromConfiguration
(
"registryRepository",
"REGISTRY_REPOSITORY"
);
// Add registry with parameters
const
const registry: ContainerRegistryResource
registry
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainerRegistry(name: string, endpoint: string | ParameterResource, repository?: string | ParameterResource): ContainerRegistryResource (+1 overload)

Adds a container registry resource

addContainerRegistry
(
"my-registry",
const registryEndpoint: ParameterResource
registryEndpoint
,
const registryRepository: ParameterResource
registryRepository
);
const
const api: ProjectResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
launchProfileOrOptions?: ProjectResourceOptions;
}): ProjectResource (+1 overload)

Adds a .NET project resource

addProject
("api", "../Api/Api.csproj");
await
const api: ProjectResource
api
.
ProjectResource.publishAsDockerComposeService(configure: (arg1: DockerComposeServiceResource, arg2: Service) => Promise<void>): ProjectResource

Publishes the specified resource as a Docker Compose service.

publishAsDockerComposeService
(async (
resource: DockerComposeServiceResource
resource
,
service: Service
service
) => {
await
service: Service
service
.
Service.name: PropertyAccessor<string>

Gets or sets the name of the Docker Compose member.

name
.
function set(value: string): Promise<void> (+1 overload)
set
("api");
});
await
const api: ProjectResource
api
.
ProjectResource.withContainerRegistry(registry: IResource): ProjectResource

Configures the resource to use the specified container registry for container image operations.

withContainerRegistry
(
const registry: ContainerRegistryResource
registry
);

You can then provide parameter values via environment variables:

Terminal window
export REGISTRY_ENDPOINT=ghcr.io
export REGISTRY_REPOSITORY=your-github-username/your-repo

After configuring your container registry, use the aspire do push command to build and push your container images:

Terminal window
aspire do push

This command:

  • Builds container images for all resources configured with a container registry
  • Tags the images with the appropriate registry path
  • Pushes the images to the specified registry

Before running this command, ensure you are authenticated to your container registry. For GitHub Container Registry:

Terminal window
echo $GITHUB_TOKEN | docker login ghcr.io -u your-github-username --password-stdin

Here’s an example GitHub Actions workflow that builds and pushes images to GHCR:

.github/workflows/build-and-push.yml
name: Build and Push Images
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install Aspire CLI
run: |
curl -sSL https://aspire.dev/install.sh | bash
echo "$HOME/.aspire/bin" >> $GITHUB_PATH
- name: Output Aspire CLI version
run: aspire --version
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push images
env:
REGISTRY_ENDPOINT: ghcr.io
REGISTRY_REPOSITORY: ${{ github.repository }}
run: aspire do push

This workflow:

  • Checks out your code
  • Sets up .NET and installs the Aspire CLI
  • Authenticates to GHCR using the built-in GITHUB_TOKEN
  • Builds and pushes container images using aspire do push