Skip to content
DocsTry Aspire
DocsTry

Set up Azure Container Registry in the AppHost

Azure Container Registry logo

This article is the reference for the Aspire Azure Container Registry Hosting integration. It enumerates the AppHost APIs — with examples for both AppHost.cs and apphost.mts — that you use to model an Azure Container Registry resource in your AppHost project.

If you’re new to the Azure Container Registry integration, start with the Get started with Azure Container Registry integrations guide. For how the registry is consumed at deployment time — including the loginServer output — see Connect to Azure Container Registry.

To start building an Aspire app that uses Azure Container Registry, install the 📦 Aspire.Hosting.Azure.ContainerRegistry NuGet package:

Terminal
aspire add Aspire.Hosting.Azure.ContainerRegistry

Learn more about aspire add in the command reference.

This updates your aspire.config.json with the Azure Container Registry hosting integration package:

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

Once you’ve installed the hosting integration in your AppHost project, add an Azure Container Registry resource and wire it to a compute environment:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const acr = await builder.addAzureContainerRegistry("my-acr");
const env = await builder.addAzureContainerAppEnvironment("env");
await env.withAzureContainerRegistry(acr);
await builder.build().run();
  1. The registry resource is modeled as an AzureContainerRegistryResource in the Aspire resource graph.

  2. Calling WithAzureContainerRegistry (or withAzureContainerRegistry) on the compute environment attaches the registry and provisions the necessary role assignments and managed-identity bindings so the environment can pull images securely.

  3. At deployment time, the generated Bicep provisions the registry and outputs its name and loginServer for use by the deployment pipeline.

Get the registry from a compute environment

Section titled “Get the registry from a compute environment”

To retrieve the registry that is associated with a compute environment — for example, to add purge tasks to the auto-provisioned default registry — use GetAzureContainerRegistry (or getAzureContainerRegistry):

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const env = await builder.addAzureContainerAppEnvironment("env");
// Retrieve the default auto-provisioned registry
const registry = await env.getAzureContainerRegistry();

This returns an AzureContainerRegistryResource builder that you can use for further configuration, such as scheduling image cleanup with purge tasks.

To reference a registry your team already manages rather than provisioning a new one, use publishAsExisting, runAsExisting, or asExisting:

Use publishAsExisting to reference an existing registry only in publish (deployment) mode:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const registryName = await builder.addParameter("registryName");
const rgName = await builder.addParameter("rgName");
const acr = await builder.addAzureContainerRegistry("my-acr");
await acr.publishAsExisting(registryName, { resourceGroup: rgName });
const env = await builder.addAzureContainerAppEnvironment("env");
await env.withAzureContainerRegistry(acr);
await builder.build().run();

Use asExisting to reference an existing registry in both run and publish modes:

apphost.mts
const acr = await builder.addAzureContainerRegistry("my-acr");
await acr.asExisting(registryName, { resourceGroup: rgName });

Use WithRoleAssignments (or withRoleAssignments) to grant an Azure resource fine-grained access to the registry. The ContainerRegistryBuiltInRole enum (TypeScript: AzureContainerRegistryRole) covers the standard ACR roles:

apphost.mts
import { createBuilder, AzureContainerRegistryRole } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const acr = await builder.addAzureContainerRegistry("my-acr");
// Grant the 'api' project permission to push images
const api = await builder.addProject("api", "../Api/Api.csproj");
await api.withRoleAssignments(acr, [AzureContainerRegistryRole.AcrPush]);
await builder.build().run();

Available roles include: AcrPull, AcrPush, AcrDelete, AcrImageSigner, AcrQuarantineReader, and AcrQuarantineWriter.

Over time, container registries accumulate old images that consume storage. Use WithPurgeTask (or withPurgeTask) to add a scheduled ACR purge task that automatically removes old or unused images:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const acr = await builder.addAzureContainerRegistry("my-acr");
await acr.withPurgeTask("0 1 * * *", {
ago: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
keep: 5,
});
await builder.build().run();

The preceding code creates a purge task that runs daily at 1:00 AM, removing images older than 7 days while keeping the 5 most recent images per repository. The C# ago parameter accepts a TimeSpan; the TypeScript ago option accepts milliseconds.

Add multiple purge tasks to target different repositories with different schedules:

Multiple purge tasks
const acr = await builder.addAzureContainerRegistry("my-acr");
await acr.withPurgeTask("0 1 * * *", { filter: "app1:.*", keep: 3 });
await acr.withPurgeTask("0 2 * * 0", {
filter: "app2:.*",
ago: 30 * 24 * 60 * 60 * 1000,
keep: 10,
});

Each purge task is given a unique name automatically. Use the taskName option to specify a custom task name.

To make changes that go beyond what the built-in APIs expose, configure the underlying Bicep infrastructure directly via ConfigureInfrastructure (or configureInfrastructure):

apphost.mts
const acr = await builder.addAzureContainerRegistry("my-acr");
await acr.configureInfrastructure(async (infra) => {
// Use the Bicep infrastructure object to customize the registry
});

If you’re new to Bicep, it’s a domain-specific language for defining Azure resources. With Aspire, you don’t need to write Bicep by hand — the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Container Registry resource, the following Bicep is generated:

my-acr.module.bicep
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
resource my_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
name: take('myacr${uniqueString(resourceGroup().id)}', 50)
location: location
sku: {
name: 'Basic'
}
tags: {
'aspire-resource-name': 'my-acr'
}
}
output name string = my_acr.name
output loginServer string = my_acr.properties.loginServer

The generated Bicep outputs the registry name and loginServer (the registry endpoint used for image push/pull). These outputs are referenced by the compute environment’s Bicep and by your deployment pipeline.

When you attach a registry to an Azure Container Apps environment, the environment’s generated Bicep references the registry outputs and creates the required AcrPull role assignment on the managed identity:

env.module.bicep (excerpt)
param my_acr_outputs_name string
resource my_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = {
name: my_acr_outputs_name
}
resource my_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(my_acr.id, env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
properties: {
principalId: env_mi.properties.principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
principalType: 'ServicePrincipal'
}
scope: my_acr
}

The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly are overwritten, so make changes through the C# provisioning APIs (or configureInfrastructure) to ensure they’re reflected in the generated files.