# `docker/` — containerised overview dashboard Runtime-only image, the HistorianGateway pattern: publish on the host (which has the Gitea feed credentials), copy the output into `mcr.microsoft.com/dotnet/aspnet:10.0`. Docker never restores. ```bash # from ZB.MOM.WW.Overview/ dotnet publish src/ZB.MOM.WW.Overview/ZB.MOM.WW.Overview.csproj \ -c Release -o docker/publish -p:UseAppHost=false cd docker && docker compose up -d --build open http://localhost:5320 ``` `docker/publish/` is build output and is gitignored. ## Why the container, and not just `dotnet run` The dev rigs publish only some node ports to the host: | Fleet | Health endpoint | Reachable from the Mac host? | |---|---|---| | ScadaBridge central-a/b | container `:5000` | yes — `localhost:9001` / `:9002` | | ScadaBridge site-\* (6) | container `:8084` | **no** — not published | | OtOpcUa central-1/2 | container `:9000` | **no** — only Traefik `:9200`, load-balanced across the pair | | OtOpcUa site-\* (4) | container `:8080` | **no** — not published | | HistorianGateway | container `:5220` | yes — `localhost:5220` | | MxAccessGateway | windev `:5130` | yes, with the VPN up | So a host-run dashboard can only see part of the fleet. This stack joins the three rig networks (`otopcua-dev_default`, `scadabridge-net`, `zb-historiangw_default`, all declared `external`) and addresses every node by container DNS name, which is how it sees all of them. Ports above were established by probing the running rigs on 2026-07-24, **not** read off the compose files — the two disagree. In particular OtOpcUa's site nodes serve health on `:8080`, not the `:9000` their compose anchor implies, because their explicit Kestrel listeners for LocalDb sync re-bind the primary HTTP port. Start the rigs before this stack. The networks are `external`, so bringing the dashboard up or down never disturbs a running rig. ## Registry shape Bound from the `Overview` section; `appsettings.Docker.json` is the worked example. ```jsonc "Overview": { "PollIntervalSeconds": 10, // sweep cadence; per-app and per-instance overrides allowed "TimeoutSeconds": 3, // per-request timeout "StaleAfterSeconds": 45, // must exceed the poll interval, or every card reads Stale "Applications": [ { "Name": "ScadaBridge", "ManagementLabel": "CentralUI", // label on each instance's management link "Instances": [ { "Name": "central-a", "Group": "central", // one Akka cluster = one group; leader + split-brain // detection are scoped to it. Omit for standalone apps. "BaseUrl": "http://host:5000", // /health/ready and /health/active are probed under this "ManagementUrl": "http://localhost:9001/", // followed by the OPERATOR'S browser, so it // must be host-reachable, not container DNS "HasActiveRole": true // false for single-instance gateways: no Active badge, // and /health/active is never probed } ] } ] } ``` `appsettings.json` ships the tuning defaults with an **empty** `Applications` list, so a deployment that supplies no registry fails at startup naming the missing key rather than booting into an empty dashboard. ## Configuration traps - **Never set `ASPNETCORE_URLS` alongside `Kestrel__Endpoints__*`.** Declaring both puts Kestrel into explicit-endpoints mode and one of them is silently discarded. - No `env_file`, no secrets, no login. The dashboard reads anonymous health endpoints and holds no credentials. That is a requirement of the design, not an omission.