Watch Aspire live streams문서Aspire 사용해 보기
Watch Aspire live streams문서사용해 보기

Example app lifecycle workflow

이 콘텐츠는 아직 번역되지 않았습니다.

This guide is a worked example of one CI/CD workflow for Aspire. It uses GitHub Actions to build and publish release artifacts, GitHub Container Registry to store container images, and Docker Compose to run the published output later.

Use this page when you want a concrete end-to-end example. For workflow guidance that applies across CI systems and deployment targets, see CI/CD overview.

For workflow-first guidance, see CI/CD overview. For Docker Compose deployment details, see Deploy to Docker Compose.

This example moves through four phases:

  1. Inner-loop development - Local development and debugging with aspire run
  2. Local deployment validation - Containerized deployment to your defined compute environment(s) with aspire deploy
  3. Publish release artifacts - Automated build and publish steps in GitHub Actions
  4. Deploy the published artifacts - Runtime deployment with Docker Compose

Each phase uses the same AppHost configuration, but each phase answers a different question: local correctness, containerized validation, release automation, or runtime deployment.

The following example uses a C# AppHost, but the same workflow shape applies to TypeScript AppHosts.

Consider this example. You have a distributed application that consists of a Blazor web project that relies on a SQL Server database with a persistent data volume as well as a persistent writable file volume to capture user file uploads. You want to distribute your Blazor app as a Docker container image via the GitHub Container Registry. You need the Aspire.Hosting.Docker and Aspire.Hosting.SqlServer integrations.

apphost.mts
import {
type ContainerLifetime = "Session" | "Persistent"
const ContainerLifetime: {
readonly Session: "Session";
readonly Persistent: "Persistent";
}

Enum Aspire.Hosting.ApplicationModel.ContainerLifetime

ContainerLifetime
,
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 compose: DockerComposeEnvironmentResource
compose
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addDockerComposeEnvironment(name: string): DockerComposeEnvironmentResource

Adds a Docker Compose environment to the application model.

addDockerComposeEnvironment
('volumemount-env');
await
const compose: DockerComposeEnvironmentResource
compose
.
DockerComposeEnvironmentResource.withProperties(configure: (obj: DockerComposeEnvironmentResource) => Promise<void>): DockerComposeEnvironmentResource

Allows setting the properties of a Docker Compose environment resource.

withProperties
(async (
env: DockerComposeEnvironmentResource
env
) => {
env: DockerComposeEnvironmentResource
env
.
DockerComposeEnvironmentResource.setDashboardEnabled(value: boolean): DockerComposeEnvironmentResource

Determines whether to include an Aspire dashboard for telemetry visualization in this environment.

setDashboardEnabled
(true);
})
.
DockerComposeEnvironmentResource.configureComposeFile(configure: (obj: ComposeFile) => Promise<void>): DockerComposeEnvironmentResource

Configures the Docker Compose file for the environment resource.

configureComposeFile
(async (
composeFile: ComposeFile
composeFile
) => {
await
composeFile: ComposeFile
composeFile
.
ComposeFile.addVolume(name: string, options?: {
driver?: string;
configure?: (volume: Volume) => Promise<void>;
}): Promise<Volume> (+1 overload)

Adds a top-level Docker Compose volume

addVolume
('volumemount-blazor-uploads', {
driver?: string | undefined
driver
: 'local',
});
});
const
const endpoint: ParameterResource
endpoint
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addParameter(name: string, options?: {
value?: string;
publishValueAsDefault?: boolean;
secret?: boolean;
}): ParameterResource (+1 overload)

Adds a parameter resource

addParameter
('registry-endpoint');
const
const repository: ParameterResource
repository
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addParameter(name: string, options?: {
value?: string;
publishValueAsDefault?: boolean;
secret?: boolean;
}): ParameterResource (+1 overload)

Adds a parameter resource

addParameter
('registry-repository');
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
('container-registry',
const endpoint: ParameterResource
endpoint
, {
repository?: string | ParameterResource | undefined
repository
,
});
const
const sqlPassword: ParameterResource
sqlPassword
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addParameter(name: string, options?: {
value?: string;
publishValueAsDefault?: boolean;
secret?: boolean;
}): ParameterResource (+1 overload)

Adds a parameter resource

addParameter
('sqlserver-password', {
secret?: boolean | undefined
secret
: true,
});
const
const sqlServer: SqlServerServerResource
sqlServer
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSqlServer(name: string, options?: {
password?: string | ParameterResource;
port?: number;
}): SqlServerServerResource (+1 overload)

Adds a SQL Server resource to the application model. A container is used for local development.

addSqlServer
('sqlserver', {
password?: string | ParameterResource | undefined
password
:
const sqlPassword: ParameterResource
sqlPassword
})
.
SqlServerServerResource.withDataVolume(name?: string, isReadOnly?: boolean): SqlServerServerResource (+1 overload)

Adds a named volume for the data folder to a SQL Server resource.

withDataVolume
('volumemount-sqlserver-data')
.
ContainerResource.withLifetime(lifetime: ContainerLifetime): SqlServerServerResource

Sets the lifetime behavior of the container resource.

withLifetime
(
const ContainerLifetime: {
readonly Session: "Session";
readonly Persistent: "Persistent";
}

Enum Aspire.Hosting.ApplicationModel.ContainerLifetime

ContainerLifetime
.
type Persistent: "Persistent"
Persistent
);
const
const sqlDatabase: SqlServerDatabaseResource
sqlDatabase
= await
const sqlServer: SqlServerServerResource
sqlServer
.
SqlServerServerResource.addDatabase(name: string, options?: {
databaseName?: string;
} | undefined): SqlServerDatabaseResource (+1 overload)

Adds a SQL Server database resource

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

Adds a .NET project resource

addProject
(
'blazorweb',
'../VolumeMount.BlazorWeb/VolumeMount.BlazorWeb.csproj'
);
await
const blazorWeb: ProjectResource
blazorWeb
.
ProjectResource.withExternalHttpEndpoints(): ProjectResource

Marks existing http or https endpoints on a resource as external.

withExternalHttpEndpoints
()
.
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 sqlDatabase: SqlServerDatabaseResource
sqlDatabase
)
.
ProjectResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): ProjectResource

Waits for another resource to be ready

waitFor
(
const sqlDatabase: SqlServerDatabaseResource
sqlDatabase
)
.
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.addVolume(source: string, target: string, options?: {
isReadOnly?: boolean;
}): Promise<Volume> (+1 overload)

Adds a Docker Compose volume mount to a service

addVolume
(
'volumemount-blazor-uploads',
'/app/wwwroot/uploads'
);
await
service: Service
service
.
Service.user: PropertyAccessor<string>

Specifies the user that the container will run as. The value can be set to a numeric UID, a string for the username, or a combination of both (e.g., "UID:GID").

user
.
function set(value: string): Promise<void> (+1 overload)
set
('root');
await
service: Service
service
.
Service.command: PropertyAccessor<List<string>>

Represents the command to override the default command specified in the image's Dockerfile. This property allows specifying how the container should run by defining an executable and its arguments.

command
.
function set(value: List<string>): Promise<void> (+1 overload)
set
([
'/bin/sh',
'-c',
"chown -R app:app /app/wwwroot/uploads && chmod -R 755 /app/wwwroot/uploads && exec su app -c 'dotnet /app/VolumeMount.BlazorWeb.dll'",
]);
});
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

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

Runs the distributed application

run
();

The aspire run command starts your Aspire application in development mode. This is the inner-loop development experience where you write code, test changes, and debug your application locally.

When you run aspire run:

  1. Aspire dashboard launches - A web-based dashboard starts, and its URL (often an HTTPS login URL like https://localhost:<port>/login?...) is printed to the console.
  2. Resources start - All resources defined in your AppHost are orchestrated.
  3. Live debugging - You can attach debuggers, set breakpoints, and modify code with hot reload.
  4. Telemetry & logs - Dashboard provides real-time logs, metrics, and distributed traces.

This command searches the current directory structure for AppHost projects to build and run:

Aspire CLI
aspire run

The console will display the dashboard URL with a login token:

Aspire CLI
Dashboard: https://localhost:17244/login?t=9db79f2885dae24ee06c6ef10290b8b2
Logs: /home/vscode/.aspire/cli/logs/apphost-5932-2025-08-25-18-37-31.log
Press CTRL+C to stop the apphost and exit.

In the example above, when resources start with the run command:

  • SQL Server container starts in Docker with persistent volume
  • Web project runs as a local process (not containerized)
  • Database is automatically created and migrated (containerized)

Phase 2: Validate a containerized deployment

Section titled “Phase 2: Validate a containerized deployment”

The aspire deploy command creates a fully containerized deployment of your application in the compute environment(s) you define. This simulates a production-like environment on your local machine. In this example, local containers and volumes are created on Docker Desktop using the Docker Integration. It requires all parameters to be set.

When you run aspire deploy Aspire will:

  1. Build and push container images for projects
  2. Generate docker-compose.yaml in ./aspire-output/ directory
  3. Start all containers using Docker Compose
  4. Create and mount persistent volumes

In this example, the following gets deployed:

Containers:

  • aspire-volumemount-env - Docker Compose stack
  • sqlserver - SQL Server with persistent data volume
  • blazorweb - Blazor Web app with persistent file uploads volume
  • volumemount-env-dashboard - Monitoring dashboard

Volumes:

  • volumemount-sqlserver-data - Stores database files (.mdf, .ldf)
  • volumemount-blazor-uploads - Stores user-uploaded images

You can login to your GitHub Container Registry before deploying.

Terminal window
export GITHUB_TOKEN=<YOUR PERSONAL ACCESS TOKEN>
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
aspire deploy

Phase 3: Publish release artifacts in GitHub Actions

Section titled “Phase 3: Publish release artifacts in GitHub Actions”

You can create a workflow that automates the process of building and pushing the image, and publishing deployment artifacts using the Aspire CLI in a CI/CD pipeline. The Aspire CLI can build your app, push images, and emit deployment artifacts as a one-way handoff. This allows you to deploy the app later via standard Docker Compose.

In this example, the workflow runs on every push to main, does a checkout, and then performs these steps:

  1. Setup Environment - Install required SDKs
  2. Install Aspire CLI - Install the Aspire CLI
  3. Build and Push Container Images - Build app and push image to GitHub Container Registry with aspire do push
  4. Publish Docker Compose Artifacts - Generate deployment files with aspire publish
  5. Upload Artifacts - Store deployment files for download
# Required for C# AppHost projects
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install Aspire CLI
run: |
echo "Installing Aspire CLI from install script..."
curl -sSL https://aspire.dev/install.sh | bash
echo "$HOME/.aspire/bin" >> $GITHUB_PATH

Step 3. Build App, Create & Push Image to GHCR

Section titled “Step 3. Build App, Create & Push Image to GHCR”
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push images with Aspire
env:
Parameters__registry_endpoint: ghcr.io
Parameters__registry_repository: your-org/your-repo
run: aspire do push

The aspire do push command does the following:

  • Analyzes your AppHost configuration
  • Restores dependencies and builds the project
  • Builds Docker container images for project resources
  • Tags images with configured registry endpoint and repository
  • Pushes images to GitHub Container Registry (ghcr.io)
  • Uses parameters defined in AppHost.cs:
    • Environment Parameters__registry_endpoint maps to registry-endpoint parameter
    • Environment Parameters__registry_repository maps to registry-repository parameter
- name: Prepare Docker Compose with Aspire
run: |
aspire publish \
--project VolumeMount.AppHost/VolumeMount.AppHost.csproj \
--output-path ./aspire-output

The aspire publish command does the following:

  • Analyzes your AppHost configuration
  • Generates docker-compose.yaml file with all service definitions
  • Creates .env template file for environment variables
  • Packages configuration needed for deployment
  • Outputs artifacts to ./aspire-output/ directory
  • 디렉터리aspire-output/ - docker-compose.yaml Service definitions for all containers
  • .env Template for required environment variables
- name: Upload Aspire artifacts
uses: actions/upload-artifact@v4
with:
name: aspire-deployment-files
path: ./aspire-output/
retention-days: 30
include-hidden-files: true

In this example, artifacts are available for download from the Actions workflow run for 30 days. Hidden files are included so that the .env file is also available in the artifacts.

After the workflow completes, you have everything needed for production deployment:

  1. Download Artifacts from GitHub Actions workflow run:

    • docker-compose.yaml - Complete service definitions
    • .env - Environment variable template
  2. Configure Environment Variables in .env. For example:

    Terminal window
    BLAZORWEB_IMAGE=ghcr.io/bethmassi/volumemount/blazorweb:latest
    BLAZORWEB_PORT=8080
    SQLSERVER_PASSWORD=YourSecurePassword
  3. Deploy with Docker Compose:

    Terminal window
    docker compose up -d
  4. Verify Deployment:

    Terminal window
    docker compose ps
    docker compose logs -f
PhaseCommandPurposeEnvironmentAppDatabase
Developmentaspire runInner-loop coding & debuggingLocal machineLocal processContainer
Local Deployaspire deployTest containerized app locallyRegistered compute environment (i.e. Docker Desktop)ContainerContainer
ReleaseCI/CD workflow (i.e. GitHub Actions)Publish to staging/ productionCloud/ServerContainerContainer

The AppHost is the single source of truth for your application architecture. Each phase above uses the exact same AppHost configuration. This eliminates configuration drift between development and deployment. It defines things your distributed application needs like:

  • Services & Dependencies - Projects, containers, and their relationships
  • Configuration - Connection strings, secrets, and parameters
  • Volumes - Persistent storage for databases and files
  • Networking - Endpoints, ports, and service communication
  • Deployment - Container registry, image tags, and publish settings

For more information, see AppHost configuration.