Local stack (Docker Compose)
Local development runs the full backend in containers. The configuration is split across two files, a pattern Visual Studio’s container tooling expects:
docker-compose.ymldeclares what runs: images, and build contexts for the five .NET images.docker-compose.override.ymldeclares how it runs: container names, ports, environment, volumes, health checks and start-up ordering.
docker compose up merges both automatically. Getting started lists every container and port.
Topology
Section titled “Topology”flowchart TB
subgraph apps["Application containers"]
gw["ocelot.apigateway :8010"]
cat["catalog.api :8000"]
bas["basket.api :8001"]
dis["discount.api :8002 to 8080"]
ord["ordering.api :8003"]
end
subgraph data["Data and messaging"]
mongo[("catalogdb: mongo")]
redis[("basketdb: redis:alpine")]
pg[("discountdb: postgres:14-alpine")]
sql[("orderdb: mssql 2022")]
mq{{"rabbitmq: 3-management-alpine"}}
ls[("localstack: S3")]
end
subgraph ops["Tooling"]
es[("elasticsearch 7.9.2")]
kb["kibana 7.9.2"]
pga["pgadmin"]
port["portainer"]
end
gw --> cat & bas & ord
bas --> dis
cat --> mongo & ls & mq
bas --> redis & mq
dis --> pg
ord --> sql & mq
kb --> es
Start-up ordering with health checks
Section titled “Start-up ordering with health checks”depends_on with condition: service_healthy makes APIs wait for real readiness, not just a started container:
| Dependency | Health check |
|---|---|
| MongoDB | mongosh ... --eval "db.adminCommand('ping').ok" |
| PostgreSQL | pg_isready -U admin -d postgres |
| SQL Server | grep -q 'SQL Server is now ready for client connections' /var/opt/mssql/log/errorlog |
| Elasticsearch | curl -f http://localhost:9200/_cluster/health?wait_for_status=yellow (120 s start period) |
| LocalStack | curl -f http://localhost:4566/_localstack/health |
Redis and RabbitMQ only use service_started. The services cover the remaining races themselves: Discount retries its migration 5 times, Ordering wraps its migration in a Polly back-off, and EF Core retries transient SQL errors.
The APIs themselves have no health checks, because no service maps a health endpoint.
Dockerfiles
Section titled “Dockerfiles”All five Dockerfiles follow the same multi-stage pattern, for example src/Services/Catalog/Catalog.API/Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS baseUSER app # non-root user shipped in the .NET imagesEXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS buildWORKDIR /repo# 1. restore layer: copy only project files, the SDK pin and central package versionsCOPY ["global.json", "Directory.Packages.props", "./"]COPY ["src/BuildingBlocks/Common.Logging/Common.Logging.csproj", "src/BuildingBlocks/Common.Logging/"]# ... every csproj in the service's project graph ...RUN dotnet restore "src/Services/Catalog/Catalog.API/Catalog.API.csproj"# 2. then the sourcesCOPY src/ src/RUN dotnet build ... --no-restore
FROM build AS publishRUN dotnet publish ... /p:UseAppHost=false --no-restore
FROM base AS finalCOPY --from=publish /app/publish .ENV ASPNETCORE_HTTP_PORTS=80ENTRYPOINT ["dotnet", "Catalog.API.dll"]The build context is the repository root, and the stages mirror the repository layout under /repo. Copying the full project graph, including Common.Mediator and EventBus.Messages, before dotnet restore means the restore layer stays cached until a .csproj or Directory.Packages.props changes. .dockerignore keeps bin/, obj/, node_modules and the frontends out of the context.
The final image runs as the non-root app user, but it listens on port 80. Binding to a port below 1024 as non-root depends on the runtime; moving to 8080 would remove that dependency.
LocalStack for S3
Section titled “LocalStack for S3”Catalog stores product images in S3. Locally, LocalStack emulates it on port 4566 (SERVICES=s3). Catalog’s Compose environment sets USE_LOCALSTACK=true, AWS_ENDPOINT_URL=http://localstack:4566, dummy credentials (test/test) and the bucket name ecommerce-product-images. Helper scripts create and verify the bucket and upload seed images:
The legacy Angular app’s product images are mounted read-only into the Catalog container at /app/images/products. That is where MigrateImagesToS3 reads from.