One command brings up the entire stack — databases, cache, object storage, the API, and both React apps — wired together with service discovery, health gating, and a single dashboard:
dotnet run --project src/Host/FSH.Starter.AppHostThe composition lives in src/Host/FSH.Starter.AppHost/AppHost.cs. This page
documents what it starts and why it’s wired the way it is.
What spins up by default
| Resource | Aspire name | What it is | Lifetime |
|---|---|---|---|
| PostgreSQL | postgres | Primary database server; hosts the fsh-db database | Persistent (data volume) |
| pgAdmin | (sidecar) | Web UI on :5050, auto-discovers every database on the server | Persistent |
| Valkey | redis | Cache + Hangfire store (Valkey — a Redis-compatible, BSD-licensed Redis fork; resource name stays redis) | Persistent (data volume) |
| RedisInsight | (sidecar) | Key browser auto-connected to the Valkey instance for inspecting cache keys in dev | Persistent |
| MinIO | minio | S3-compatible object storage, :9000 (API) / :9001 (console) | Persistent (data volume) |
| MinIO init | minio-init | One-shot: creates the fsh-uploads bucket + download policy, then exits | Run-once |
| DB migrator | fsh-db-migrator | One-shot: applies migrations across the tenant catalog + every tenant’s module DBs, then exits | Run-once |
| API | fsh-api | The ASP.NET Core API (net10.0) | Long-running |
| Admin app | fsh-admin | Operator React + Vite SPA on :5173 | Long-running |
| Dashboard app | fsh-dashboard | Tenant React + Vite SPA on :5174 (with SSE live feed) | Long-running |
The Aspire dashboard opens automatically and shows every resource’s state,
logs, traces, and endpoints. The API’s Scalar UI is at /scalar.
Design decisions
Persistent infra, run-once jobs
Postgres, Valkey, and MinIO use ContainerLifetime.Persistent with named data
volumes. Your data, pgAdmin layout, and uploaded files survive dotnet run
restarts, so the inner loop stays fast. The migrator and MinIO bootstrap are
run-once — they do their job and exit rather than linger as “unhealthy.”
MinIO is S3, so dev matches prod
Object storage uses the same Storage__Provider = "s3" code path locally as in
production — only ServiceUrl and ForcePathStyle differ. The Files module’s
presigned-URL upload flow (browser → storage directly, bytes never proxied
through the API) is exercised end-to-end in dev. MinIO is configured to accept
browser PUTs from the admin (:5173) and dashboard (:5174) origins via
MINIO_API_CORS_ALLOW_ORIGIN.
The migrator is the production deploy step too
fsh-db-migrator is the same FSH.Starter.DbMigrator project you run as an
explicit step in production (dotnet run --project ... -- apply). The database
is never migrated at API startup — locally or in the cloud. One mechanism,
two contexts.
Valkey for cache + Hangfire, with a RedisInsight browser
The cache/store engine is Valkey (a Redis-compatible, BSD-licensed Redis
fork), pulled as valkey/valkey:8-alpine. It speaks the Redis protocol (RESP),
so the .NET client (StackExchange.Redis) and every CachingOptions:Redis
config key are unchanged — and the Aspire resource keeps the name redis in
code. Aspire also auto-wires a RedisInsight browser sidecar connected to the
Valkey instance, so you can inspect cache keys, TTLs, and the SignalR backplane
in dev with no manual configuration.
Redis over plain TCP
Frontends target the API’s HTTPS endpoint
Both React apps get VITE_API_BASE_URL pointing at the API’s https endpoint,
not http. The API uses UseHttpsRedirection(), so a call to the http endpoint
307-redirects to https — and browsers strip the Authorization header on
cross-origin redirects (a different scheme/port is cross-origin per the Fetch
spec). Hitting https directly preserves the bearer token on every request. The
Vite apps run un-proxied on fixed ports (5173 / 5174) so HMR works and the
origins line up with the MinIO CORS allow-list.
The React apps are optional
The //#if (frontend) directives around the two AddJavaScriptApp(...) calls
are dotnet new template conditionals. Scaffold with the backend-only option
and the AppHost omits both React apps entirely.
Ports
| Service | Port |
|---|---|
| API | 7030 (https) · 5030 (http) |
| Admin app | 5173 |
| Dashboard app | 5174 |
| Postgres | 5432 |
| pgAdmin | 5050 |
| Valkey | 6379 |
| MinIO | 9000 (API) · 9001 (console) |
From local to cloud
The local topology maps almost one-to-one onto AWS: Postgres → RDS, Valkey → ElastiCache, MinIO → S3, the API container → ECS Fargate, and the two React apps → S3 + CloudFront. See Deploy to AWS with Terraform.