# docker-cluster

Primary local Docker deployment — the full ScadaBridge reference topology used for day-to-day development and integration testing. Runs on the developer workstation under Docker / OrbStack.

- **Deployment ID:** `docker-cluster`
- **Transport.SourceEnvironment:** `docker-cluster`
- **Source directory:** [`docker/`](../docker/)
- **Container-name prefix:** `scadabridge-`
- **Host-port range:** `90XX`

## Topology

```
              ┌───────────────────┐
              │  Traefik LB :9000 │  ◄── CLI / Browser
              │  Dashboard :8180  │
              └────────┬──────────┘
                       │ routes to active node
┌──────────────────────┼──────────────────────────────┐
│                  Central Cluster                    │
│  central-node-a (UI :9001, Akka :9011)              │
│  central-node-b (UI :9002, Akka :9012)              │
└───────────┬─────────────────────────────────────────┘
            │ Akka.NET remoting + gRPC streaming
            ├──────────────────┬──────────────────┐
            ▼                  ▼                  ▼
   Site-A (Test Plant A) Site-B (Test Plant B) Site-C (Test Plant C)
   Akka 9021/9022        Akka 9031/9032        Akka 9041/9042
   gRPC 9023/9024        gRPC 9033/9034        gRPC 9043/9044
```

Two-node active/standby everywhere. Central runs the Blazor UI, Template Engine, Deployment Manager, Notification Outbox, Site Call Audit, Audit Log, and Inbound API. Each site runs the Site Runtime, Data Connection Layer, Store-and-Forward, and Site Event Logging, with a per-node gRPC streaming server (port 8083) that central nodes subscribe to for real-time attribute/alarm streams.

## Nodes

| Node | Container Name | Host Web | Host Akka | Host gRPC |
|------|----------------|----------|-----------|-----------|
| Traefik LB | `scadabridge-traefik` | 9000 | — | — |
| Central A | `scadabridge-central-a` | 9001 | 9011 | — |
| Central B | `scadabridge-central-b` | 9002 | 9012 | — |
| Site-A A | `scadabridge-site-a-a` | — | 9021 | 9023 |
| Site-A B | `scadabridge-site-a-b` | — | 9022 | 9024 |
| Site-B A | `scadabridge-site-b-a` | — | 9031 | 9033 |
| Site-B B | `scadabridge-site-b-b` | — | 9032 | 9034 |
| Site-C A | `scadabridge-site-c-a` | — | 9041 | 9043 |
| Site-C B | `scadabridge-site-c-b` | — | 9042 | 9044 |

Port pattern: `90X1`/`90X2` (Akka), `90X3`/`90X4` (gRPC), where X = 0 (central), 2 (site-a), 3 (site-b), 4 (site-c).

## Sites

| Site Identifier | Central UI Name |
|-----------------|-----------------|
| `site-a` | Test Plant A |
| `site-b` | Test Plant B |
| `site-c` | Test Plant C |

## Infrastructure Dependencies

All from `infra/docker-compose.yml`, attached via the shared `scadabridge-net` bridge network:

| Service | Container | Host Port | Purpose |
|---------|-----------|-----------|---------|
| MS SQL 2022 | `scadabridge-mssql` | 1433 | `ScadaBridgeConfig`, `ScadaBridgeMachineData` |
| LDAP (GLAuth) | `scadabridge-ldap` | 3893 | Authentication |
| SMTP (Mailpit) | `scadabridge-smtp` | 1025 / 8025 | Notification capture |
| OPC UA | `scadabridge-opcua` | 50000 / 8080 | Simulated device tags |
| REST API | `scadabridge-restapi` | 5200 | External REST integration testing |

## Databases

| Database | Owner |
|----------|-------|
| `ScadaBridgeConfig` | Configuration Database (#17) |
| `ScadaBridgeMachineData` | Machine data (template/instance runtime data) |

## Commands

```bash
# Start infra first (creates scadabridge-net, MS SQL, LDAP, SMTP, OPC UA, REST API)
cd infra && docker compose up -d && cd ..

# Build image + deploy all 8 nodes
bash docker/deploy.sh

# First-time only: seed test sites with Akka + gRPC addresses
bash docker/seed-sites.sh

# View logs
docker compose -f docker/docker-compose.yml logs -f
docker logs -f scadabridge-central-a

# Health
curl -s http://localhost:9001/health/ready | python3 -m json.tool
curl -s http://localhost:9002/health/ready | python3 -m json.tool

# Stop application nodes (preserves SQLite + logs)
bash docker/teardown.sh
```

## CLI Access

The CLI talks to the Traefik LB, which routes to the active central node.

```bash
dotnet run --project src/ZB.MOM.WW.ScadaBridge.CLI -- \
    --url http://localhost:9000 \
    --username multi-role --password password \
    template list
```

Direct node access: `http://localhost:9001` (central-a), `http://localhost:9002` (central-b).

## Test Users

All passwords are `password`. See `infra/glauth/config.toml` for the full list.

| Username | Roles |
|----------|-------|
| `admin` | Admin |
| `designer` | Design |
| `deployer` | Deployment |
| `multi-role` | Admin, Design, Deployment |

## Notes

- Reference deployment for the project — when a change ships, it gets validated here first.
- Concurrent with [`docker-cluster-env2`](docker-cluster-env2.md) on the same host; the two stacks share the `scadabridge-net` network and `infra/` services but use disjoint host ports (`90XX` vs `91XX`) and databases.
- Detailed setup, failover testing, and build-cache notes live in [`docker/README.md`](../docker/README.md).
- Automated failover drill: [`docker/failover-drill.sh`](../docker/failover-drill.sh) SIGKILLs the active central node and asserts Traefik recovers to the survivor (~25s + Traefik health interval); see the "Automated failover drill" subsection in `docker/README.md`.
