Local dev stack#
A docker compose file provides CommandCenter, the Azure Service Bus emulator, and Azurite for local development. The Agent and bmo (Orchestrator) run natively on your Windows machine and reach the stack via published localhost ports.
External SQL Server is not containerised — you connect to the same server you use for acceptance/production. This is intentional and cannot be changed.
Prerequisites#
| Tool | Minimum version | Notes |
|---|---|---|
| Docker Desktop | 4.x | Linux containers mode |
| .NET SDK | 10.0 | For running Agent and bmo natively |
| Azure CLI | any | Used by the azurite-init container |
| SQL access | — | Your Windows account needs read/write on CommandCenter on the configured SQL instance |
First-time setup#
1. Create your local secrets file.
Edit local-dev\.env and fill in:
- CC_SQL_ACCEPTATIE — connection string to the Acceptatie SQL instance
- CC_SQL_PRODUCTIE — connection string to the Productie SQL instance
- BMO_API_KEY — any non-empty string (used as the shared API key between bmo/Agent and CommandCenter)
The .env file is gitignored and never committed.
2. Set your development environment variable (add to your PowerShell profile so it persists):
With this set, all three apps automatically pick up appsettings.Development.json overrides that point at the local stack instead of real Azure.
3. Update appsettings.Development.json for the Agent with your BMO_API_KEY and any local port adjustments:
The file at src\Benefits.Agent\appsettings.Development.json is already checked in with the correct Azurite and Service Bus emulator connection strings. You only need to add your Authentication.ApiKey value if you're calling the Agent directly from bmo in dev:
Starting the stack#
Expected: four services (azurite, azurite-init, sb-sql-edge, servicebus-emulator, commandcenter) all healthy or completed. CommandCenter logs should show:
- Subscribing to deploy.replies.v1 / command-center
- Subscribing to agent.state.v1 / command-center
First run is slower#
The sb-sql-edge container initialises a SQL Edge database (~30 s) before the Service Bus emulator starts. Subsequent up calls reuse the sb-data volume and are fast.
Running Agent and bmo natively#
After the stack is up:
# Agent (new terminal, stays running)
dotnet run --project src/Benefits.Agent
# bmo (uses DOTNET_ENVIRONMENT=Development automatically after Register-BMO.ps1)
.\Register-BMO.ps1
bmo query versions Acceptatie
bmo query installed Acceptatie --from fleet
The Agent's appsettings.Development.json overrides:
- Service Bus → emulator at localhost:5672
- Blob snapshots → Azurite agent-snapshots container at localhost:10000
- Package store → Azurite deployments container at localhost:10000
The Orchestrator's appsettings.Development.json overrides:
- CommandCenter → http://localhost:8090 with ApiKey auth
- Service Bus → emulator at localhost:5672
- Blob snapshots → Azurite at localhost:10000
- Package store → Azurite at localhost:10000
Verifying the end-to-end flow#
# CommandCenter SQL query through HTTP
curl -H "X-Api-Key: dev-local-api-key" http://localhost:8090/api/customers?env=Acceptatie
# Fleet snapshot (Agent publishes state on start; CC consumes via SB)
bmo query installed Acceptatie --from fleet
# Package store round-trip
bmo package push .\some-package.zip
bmo package list
Stopping and cleaning up#
# Stop containers, keep volumes (fast restart next time)
docker compose -f docker-compose.dev.yml down
# Full wipe including Service Bus SQL Edge volume
docker compose -f docker-compose.dev.yml down -v
Port map#
| Port | Service | Protocol |
|---|---|---|
| 8090 | CommandCenter | HTTP (localhost only) |
| 5672 | Service Bus emulator | AMQP |
| 10000 | Azurite Blob | HTTP |
| 10001 | Azurite Queue | HTTP |
| 10002 | Azurite Table | HTTP |
All ports bind to 127.0.0.1 only — the stack is invisible to other machines on the network.
Troubleshooting#
servicebus-emulator keeps restarting
The SQL Edge sidecar takes 20–30 s on first init. The emulator healthcheck retries 20 times with 5 s gaps. Wait for sb-sql-edge to become healthy first: docker compose -f docker-compose.dev.yml ps.
commandcenter exits with a SQL connection error
Check local-dev/.env — the connection string must reach your SQL server from inside Docker. If your SQL server uses Windows auth (Integrated Security=True) you need to switch to SQL auth for the container because Windows creds cannot be forwarded into Linux containers. Use User ID=<user>;Password=<pw> instead.
API version 2026-02-06 not supported in integration tests
This is a pre-existing test issue unrelated to the dev stack. Integration tests already pin BlobClientOptions.ServiceVersion.V2025_05_05. The dev stack uses azurite:3.35 for the same reason.
bmo can't reach CommandCenter
Ensure DOTNET_ENVIRONMENT=Development is set in your terminal before running bmo. Check that local-dev/.env has the BMO_API_KEY value matching what you set in appsettings.Development.json.
Building the CommandCenter image#
docker compose -f docker-compose.dev.yml up -d rebuilds the image automatically when the build context changes. To produce a deliberately versioned image — for sharing, for docker run testing, or to cut a release — use scripts/build-commandcenter-image.ps1.
The script derives MINVER_VERSION from git describe --tags --dirty (with any leading v stripped) and forwards it as the MINVER_VERSION build-arg so the stamped AssemblyInformationalVersion matches what git reports.
Dev build (default)#
Produces two local tags pointing at the same image:
bmo-mgmt-commandcenter:latest— used bydocker-compose.dev.ymlbmo-mgmt-commandcenter:<version>— convenient fordocker run
Use -NoCache to force a clean rebuild, -Version 1.2.3 to override the computed version, and -WhatIf to trace the docker commands without running them.
Push to a registry#
$env:BCS_REGISTRY = 'bcsbenefits.azurecr.io'
pwsh ./scripts/build-commandcenter-image.ps1 -Mode Push
Runs the Dev build, tags the result as $Registry/bcs-benefits/commandcenter:<version> and :latest, then docker push for both. The script does not run docker login for you — authenticate first (az acr login -n bcsbenefits for Azure Container Registry, or docker login <registry> otherwise). If the push fails the script prints the appropriate login hint.
You can also pass the registry explicitly: -Registry bcsbenefits.azurecr.io. The BCS_REGISTRY env var only matters if you'd rather not type it every time.
Release build#
Pre-flight refuses to proceed unless:
git status --porcelainis empty (no uncommitted changes), and- The computed version is exactly a tag (no
-dirty, no-N-gxxxxxxxcommit-count suffix).
After confirming, it rebuilds with --no-cache --pull, tags + pushes both <version> and latest, and prints the resulting manifest digest via docker buildx imagetools inspect so you can record it alongside the release notes. -Force skips both the preflight and the confirmation prompt.