# Aspire AppHost .NET Aspire orchestrates the entire Bitwarden server local development environment — supporting infrastructure and the Bitwarden application services — from a single command, replacing the manual docker-compose workflow. ## Prerequisites | Requirement | Notes | |------------------------|----------------------------------------------------------------------------------------------------------------------------------| | .NET SDK 10 | Required by Aspire | | Docker Desktop | Runs the supporting infrastructure containers | | PowerShell (`pwsh`) | Used by migration and secrets scripts | | Completed server setup | `dev/secrets.json` must exist — follow the [setup guide](https://contributing.bitwarden.com/getting-started/server/guide/) first | ## Quick Start ```bash cd AppHost dotnet run ``` The Aspire dashboard opens automatically in your browser. All resources start in dependency order; the services wait for the database and secrets setup to finish before launching. ## What Gets Started | Resource | Type | Purpose | |---------------------|---------------------------|---------------------------------------------------------------------------| | `setup-secrets` | Executable | Runs `dev/setup_secrets.ps1` — applies `dev/secrets.json` to all projects | | `mssql` | SQL Server 2022 container | Persistent data volume, port 1433 | | `run-db-migrations` | Executable | Runs `dev/migrate.ps1` against `vault_dev` (or `self_host_dev`) | | `azurite` | Azure Storage emulator | Blob :10000 · Queue :10001 · Table :10002 · persistent data volume | | `azurite-setup` | Executable | Runs `dev/setup_azurite.ps1` after Azurite is ready | | `mailcatcher` | Container | SMTP :10250 · Web UI :1080 | | `redis` | Container | Redis with AOF persistence, port 6379 | | `idp` | SimpleSAMLphp container | SAML IdP for SSO testing (**explicit start** from the dashboard) | | `admin` | .NET project | Admin portal | | `api` | .NET project | Main API (waits for Azurite) | | `billing` | .NET project | Billing service | | `events` | .NET project | Events service (waits for Azurite) | | `eventsProcessor` | .NET project | Events processor (waits for Azurite) | | `icons` | .NET project | Icons service | | `identity` | .NET project | Identity / auth service | | `notifications` | .NET project | Notifications service (waits for Azurite) | | `scim` | .NET project | SCIM provisioning service | | `sso` | .NET project | SSO service | ## Configuration All configuration lives in `appsettings.Development.json`(see [Security](#security--do-not-commit-local-settings-or-tokens)). ### Service Ports Each service's `BasePort` is pre-filled in `appsettings.Development.json` to match the port defined in each service's own `Properties/launchSettings.json`. No action is needed unless a port conflicts with something else on your machine — in that case override it via user secrets: ```bash dotnet user-secrets set "Services:api:BasePort" "4001" ``` ### Database Password ```bash dotnet user-secrets set "Database:Password" "" ``` ### Full Configuration Reference | Key | Default | Description | |-----------------------------|------------------------------------|--------------------------------------------------------------------------------------| | `SelfHost` | `false` | Switch to self-hosted mode (see [Self-Hosted Mode](#self-hosted-mode)) | | `ClientsPath` | `../../clients/apps` | Path to the `clients` repo's `apps/` directory (see [Git Worktrees](#git-worktrees)) | | `WorkingDirectory` | `../dev` | Directory where dev scripts are resolved | | `Services::BasePort` | see `appsettings.Development.json` | HTTP port for each service; pre-filled to match each service's `launchSettings.json` | | `Database:Image` | `mssql/server:2022-latest` | Docker image for SQL Server | | `Database:Port` | `1433` | Host port mapped to the SQL Server container | | `Database:Password` | _(empty)_ | SA password for the SQL Server container | | `Database:SelfHostPassword` | _(empty)_ | SA password used in self-hosted mode | | `Scripts:DbMigration` | `migrate.ps1` | Migration script filename (relative to `WorkingDirectory`) | | `Scripts:AzuriteSetup` | `setup_azurite.ps1` | Azurite setup script filename | | `Scripts:SecretsSetup` | `setup_secrets.ps1` | Secrets setup script filename | | `MailCatcher:Image` | `sj26/mailcatcher:latest` | Docker image for MailCatcher | | `MailCatcher:SmtpPort` | `10250` | Host SMTP port | | `MailCatcher:WebPort` | `1080` | MailCatcher web UI port | | `NgrokAuthToken` | _(empty)_ | ngrok auth token (used only when ngrok plugin is enabled) | | `WebFrontend:Port` | `8080` | Web frontend port | | `WebFrontend:Url` | `https://bitwarden.test:8080` | Web frontend URL shown in the dashboard | ## Optional Features ### Web Frontend Runs the web client alongside the server services. Requires the Bitwarden [clients](https://github.com/bitwarden/clients) repo cloned as a sibling to `server`. 1. If the clients repo is not at `../../clients/apps`, override the path: ```bash dotnet user-secrets set "ClientsPath" "" ``` 2. Run `dotnet run` as normal. The `web-frontend` resource starts with **explicit start** — open the Aspire dashboard and start it manually when you're ready. ### Ngrok (Billing Webhook Tunneling) Exposes the billing service through a public ngrok tunnel, useful for testing Stripe webhooks locally. 1. Create an `AppHost.csproj.user` file next to `AppHost.csproj` (it is covered by `.gitignore`): ```xml true ``` 2. Set your ngrok auth token: ```bash dotnet user-secrets set "NgrokAuthToken" "" ``` 3. The `billing-webhook-ngrok-endpoint` resource starts with **explicit start** — launch it from the Aspire dashboard when you need the tunnel active. ## Dynamic Additional Projects Load an extra project into the orchestration without touching source files — useful for temporary integrations or in-progress work: ```bash # Add a project dotnet user-secrets set "AdditionalProjects::Path" "" # Optionally wire it as a reference into an existing service dotnet user-secrets set "AdditionalProjects::ReferencedBy:0" "api" ``` Replace `` with any identifier you choose. Multiple `ReferencedBy` entries are indexed (`0`, `1`, `2`, …). ## Self-Hosted Mode Switch to a self-hosted database configuration: ```bash dotnet user-secrets set "SelfHost" "true" dotnet user-secrets set "Database:SelfHostPassword" "" ``` In self-hosted mode: - The database name changes from `vault_dev` to `self_host_dev` - The migration script receives the `-self-hosted` flag - Each service's effective port becomes `BasePort + 1` ## Aspire Dashboard The dashboard opens automatically when you run the AppHost. You can also navigate to it directly: | Profile | URL | |-----------------|---------------------------| | HTTPS (default) | `https://localhost:17271` | | HTTP | `http://localhost:15055` | The dashboard shows live resource status, structured logs, distributed traces, and environment variables for every resource. ## Security — Do Not Commit Local Settings or Tokens > **Warning:** Never commit local configuration values or secrets to the repository. - `appsettings.Development.json` is checked in with intentionally empty defaults. Local overrides belong in **user secrets**, not in that file — edits to it will appear in `git diff` and risk accidental commit. - `Database:Password`, `Database:SelfHostPassword`, and `NgrokAuthToken` are sensitive — always store them with `dotnet user-secrets`, never in any `appsettings.*.json` file. - User secrets are stored outside the repo in your OS profile, keyed by the `UserSecretsId` in `AppHost.csproj`, and are never tracked by git. - If you create an `appsettings.local.json`, add it to `.gitignore` before writing any values to it. ## Git Worktrees Path-based settings resolve relative to where you run `dotnet run`. If your worktree lives in a different location than the main checkout, those paths won't resolve correctly. Use absolute paths for any such setting: - **`ClientsPath`** defaults to `../../clients/apps` — override if your worktree is not alongside the `clients` repo: ```bash dotnet user-secrets set "ClientsPath" "" ``` - **`AdditionalProjects::Path`** — use an absolute path when adding projects via user secrets in a worktree: ```bash dotnet user-secrets set "AdditionalProjects::Path" "" ``` ## Troubleshooting | Symptom | Fix | |----------------------------------|---------------------------------------------------------------------------------------| | Secrets not applied to services | Re-run `setup-secrets` from the Aspire dashboard, or verify `dev/secrets.json` exists | | SQL Server container won't start | Confirm Docker Desktop is running and port 1433 is free | | Migrations fail immediately | Ensure `pwsh` (PowerShell) is on your `$PATH` | | Port conflicts on startup | Set the conflicting `Services::BasePort` to a free port via user secrets | | Services stuck waiting | Check the dashboard logs for `setup-secrets` or `run-db-migrations` errors |