Skip to content

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.

Copy-Item local-dev\.env.example local-dev\.env

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):

$env:ASPNETCORE_ENVIRONMENT = "Development"
$env:DOTNET_ENVIRONMENT     = "Development"

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:

{
  "Authentication": {
    "ApiKey": "dev-local-api-key"
  }
}

Starting the stack#

docker compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev.yml ps

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)#

pwsh ./scripts/build-commandcenter-image.ps1

Produces two local tags pointing at the same image:

  • bmo-mgmt-commandcenter:latest — used by docker-compose.dev.yml
  • bmo-mgmt-commandcenter:<version> — convenient for docker 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#

pwsh ./scripts/build-commandcenter-image.ps1 -Mode Release

Pre-flight refuses to proceed unless:

  1. git status --porcelain is empty (no uncommitted changes), and
  2. The computed version is exactly a tag (no -dirty, no -N-gxxxxxxx commit-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.