Skip to content
StarLive demo →
ReferenceChecked against main · 31 Oct 20265 min read

Local services & ports

Every container and process the AppointMe Aspire AppHost and compose.yaml start for local .NET 10 development, with images, tags, ports, credentials, volumes and URLs.

Containers
SQL Server, Keycloak, Mailpit
Processes
The API and the Vite dev server
Defined in
AppointMe.Aspire/Program.cs, compose.yaml
Same in both
Images, host ports, credentials

The AppHost in src/AppointMe.Aspire/Program.cs starts three containers and two processes. compose.yaml starts the same three containers on the same host ports, with the same images and credentials, and leaves the API and the frontend to you. This page lists what each one runs, where to reach it and where its data lives; to start them, see the Quick start.

The app is at https://localhost:5173. Everything else is a tool for you, not for users.

Service URL Notes
API https://localhost:7233, http://localhost:5179 The https launch profile. Aspire always uses it. With dotnet run, pass --launch-profile https.
Aspire dashboard https://localhost:17082 Aspire only. Also http://localhost:15181, OTLP on https://localhost:21061, resource service on https://localhost:22014.
Frontend (the app) https://localhost:5173 Vite+ dev server. Proxies /api, /signin-oidc and /admin to the API.
Hangfire dashboard https://localhost:5173/admin/jobs Super admins only (Authentication:SuperAdmins)
Keycloak https://localhost:8082 Admin console, and the appointme realm at /realms/appointme
Mailpit web UI http://localhost:8026 Every email Keycloak sends
OpenAPI document https://localhost:7233/openapi/v1.json Development only
SQL Server localhost,60740 TDS, not HTTP

Under Aspire, the API and frontend ports come from launch profiles and endpoint settings, not from Docker. The API’s ports are in src/AppointMe.Api/Properties/launchSettings.json, the dashboard’s in src/AppointMe.Aspire/Properties/launchSettings.json. The AppHost pins the frontend to 5173 because the Keycloak redirect URIs and Frontend:BaseUrl expect exactly that origin.

Three images, with the same tags under Aspire and in compose.yaml. Dependabot doesn’t update container tags, so bump both by hand.

Service Image Host port → container port Aspire resource Compose service / container
Keycloak quay.io/keycloak/keycloak:26.6 8082 → 8443 under Compose keycloak keycloak / appointme-keycloak
Mailpit axllent/mailpit:v1.32.0 8026 → 8025 (web UI), 1026 → 1025 (SMTP) Mailpit mailpit / appointme-mailpit
SQL Server mcr.microsoft.com/mssql/server:2025-CU10-ubuntu-24.04 60740 → 1433 appointme-sql, database resource AppointMeSql sqlserver / appointme-sql

Under Aspire, Keycloak’s image is the default of Aspire.Hosting.Keycloak 13.6.4-preview.1.26523.11, which is the same 26.6 tag; it moves when that package does. The AppHost names the host port 8082 and serves HTTPS on it when the dev certificate is trusted.

src/AppointMe.Aspire/Program.csView source ↗
var sqlServer = builder
.AddSqlServer("appointme-sql", passwordParameter)
.WithImage("mssql/server:2025-CU10-ubuntu-24.04")
.WithHostPort(60740)
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);

Aspire runs the API and the frontend as local processes, not containers. Under Compose you start both yourself.

Process Aspire resource Started by Reaches
API appointme-api AddProject<AppointMe_Api>(..., launchProfileName: "https") SQL Server, Keycloak (OIDC and the admin API)
Frontend appointme-frontend AddViteApp(...), which runs npm install --no-save and then the dev script The API, through the dev-server proxy

The frontend serves TLS with vite-plugin-mkcert, which makes its own local certificate. The API and Keycloak use the ASP.NET Core dev certificate.

Every value here is a committed local default. Never reuse one in a deployed environment.

What Value Set in
Demo user (realm appointme) demo@appointme.dev / AppointMe1 appointme-realm.json, and Demo:User in appsettings.Development.json
Keycloak admin (master realm) admin / admin The username and password parameters in Program.cs; KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD in compose.yaml
Keycloak client appointme-api A client secret appointme-realm.json, and KeycloakAdmin:ClientSecret
Keycloak client appointme-frontend A public client; the API still sends Authentication:Keycloak:FrontendClientSecret appsettings.Development.json
SQL Server sa / Password1 The sqlPassword parameter in Program.cs; MSSQL_SA_PASSWORD in compose.yaml

The API’s connection string when it runs outside Aspire:

src/AppointMe.Api/appsettings.Development.jsonView source ↗
"ConnectionStrings": {
"AppointMeSql": "Server=localhost,60740;Database=AppointMe;User ID=sa;Password=Password1;TrustServerCertificate=True"
},

Under Aspire, WithReference(database) injects its own AppointMeSql connection string to the same container and database. The API’s tables live in the schemas identity, organizations, crm, booking, wolverine and hangfire of the AppointMe database.

SQL Server and Keycloak keep their data in named volumes. Mailpit keeps nothing once its container is gone.

Service Under Aspire Under Compose To reset
Keycloak Volume AppointMe-Keycloak, persistent container Volume keycloak-data (appointme_keycloak-data) Remove the container and the volume. Needed to re-import an edited realm.
Mailpit No volume, persistent container No volume Remove the container
SQL Server A data volume named by Aspire (WithDataVolume()), persistent container Volume sqlserver-data (appointme_sqlserver-data) Remove the container and the volume. Migrations recreate the schema on the next start.

Aspire’s persistent containers keep running after you stop the AppHost, and the next run reuses them. Under Compose, docker compose stop keeps everything, docker compose down removes the containers but keeps the volumes, and docker compose down -v removes both.

Containers talk to each other by name. The API and the frontend get their targets from Aspire, or from committed config when Aspire isn’t running.

From → to Under Aspire Under Compose
API → Keycloak Authentication__Keycloak__Authority and KeycloakAdmin__BaseUrl, built from Keycloak’s endpoint https://localhost:8082 in appsettings.Development.json
API → SQL Server The AppointMeSql connection string from WithReference(database) The connection string above
Frontend → API services__appointme-api__https__0, which vite.config.ts prefers VITE_API_PROXY_TARGET=https://localhost:7233 in .env.development
Keycloak → Mailpit Host Mailpit, port 1025 (the realm’s smtpServer) The same host, through the network alias Mailpit on the mailpit service

Only Keycloak sends email. The API has no SMTP client, so Mailpit’s host port 1026 is free for your own tools.

Why do Aspire and Compose share every port?1 min · optional

appsettings.Development.json, .env.development and the realm’s redirect URIs all name fixed local addresses. Keeping the host ports identical means one committed config works under both, and you can switch between them without editing a file.

The cost is that you can’t run both at once: Aspire’s persistent containers and the Compose containers claim the same host ports, so stop one set before starting the other.