Chapter 3 — Network Architecture
3.1 VPC Design
TwoTarget: two dedicated VPC networks, one per environment, both in NYC2:NYC2, no peering:
| VPC | Environment | Members |
|---|
prod-vpc-nyc2
Prod
sbsdash-server-prod, monitor-servers, sbs-wiki (post NYC2 rebuild)
dev-vpc-nyc2
Dev
Dev-SBS-server
As built: neither dedicated VPC exists. Current membership:
default-nyc2
10.100.0.0/20
Dev-SBS-server (.2), sbsdash-server-prod (.3), monitor-servers (.4) — Dev and Prod share one flat private network ⚠
default-nyc1
10.116.0.0/20
sbs-wiki (.2) — unreachable from NYC2 private networking
10.120.x
—
Dev-Gitlab-server (§2.2.2)
DesignConstraint constraints,driving statedthe fordecision: accuracya againstDigitalOcean DigitalOcean'droplet's VPC model:assignment is fixed at creation — moving a droplet to a new VPC requires snapshot-and-rebuild. Two paths (Build Register item 18):
A(a) Rebuild into dedicated VPCs — recommended. At the current fleet size (4–5 droplets, pre-data-services, pre-tenant-data) rebuilds are cheap: snapshot, re-create in target VPC, re-run configuration. Cost grows sharply once databases and Compartments are live. This path restores the "no shared network" rule of §2.2.3 as a structural guarantee. The wiki NYC2 rebuild is already mandatory, so path (a) folds it into one rebuild wave.
This specification proceeds on path (a); §3.2 onward describes the target VPCs. If path (b) is elected, §3.1–3.2 are amended by Change Order.
DigitalOcean VPC model notes (unchanged): a VPC is a single flat private network per region — there is no user-defined sub-subnetting within a VPC, nosubnets, route tables, and noor security groupsgroups; in the AWS sense. Segmentation inside the intra-VPC segmentation is therefore enforced by Cloud Firewalls (tag-scoped) plus host firewalls (§3.5, §3.6),. not by subnet boundaries.
sbs-wikiprod-vpc-nyc23.2 Address Plan
| Network | CIDR | |
|---|---|---|
prod-vpc-nyc2 |
10.10.0.0/20 |
|
dev-vpc-nyc2 |
10.20.0.0/20 |
default-nyc2 (10.100.0.0/20), default-nyc1 (10.116.0.0/20), 10.120.x
—
Legacy — vacated at end of rebuild wave
Reserved documentation convention: Prod privatePrivate IPs are recorded in the Build Register per droplet at VPC enrollment; the authoritative address inventory lives in the IaC state (Chapter 10), not in this document, to avoid drift.
3.3 Internal Routing and Service Communication
All service-to-service traffic between Prod droplets transits the VPC private interface (eth1), never public IPs. Enforced expectations:
- Application → data services (target) — the application stack on
sbsdash-server-prodreaches PostgreSQL, MongoDB, and Redis exclusively on VPC-privateaddresses.addresses;Datadata services bind to the private interfaceonly;only,theywithhavenolistener on thepublicinterfacelistener (verified atgate V-seriesLH-SBS-INST-001 networkchecks, LH-SBS-INST-001)gates). - Prometheus scrape paths (deployed, verified 2026-07-17) —
monitor-serversscrapesexportersnode_exporter on(10.100.0.3:9100) andsbsdash-server-proddev (10.100.0.2:9100) oversbs-wikithe VPC. Exporter ports (9100 node_exporter, application exporters) are admittedonlyfrom themonitor-serversprivateIPnetworking;viaexporterstag-scopedbindfirewallprivate-onlyrule — theyand are unreachablefrompublicly. On thepublicmonitorinterfaceitself, Prometheus (9090), Grafana (3000), andfromlocalothernode_exporterVPC(9100) bind 127.0.0.1 only, fronted by nginx TLS — confirmed byss -tlnpverification. This matches the target design and is marked verified for the NYC2 members. - Wiki
isolationscrape (interim nonconformance) —BookStack onsbs-wikioriginates(NYC1)noisconnectionsscraped on its public IP (167.172.135.88:9100), unencrypted, admitted only from the monitor's public IP by ufw rule. Accepted temporarily (metrics only, low sensitivity); eliminated by the NYC2 rebuild, after which the wiki exporter rebinds to its private address like theapplicationrestorofdatathetier.fleet.
sbsdash-server-prod accepts SSH only monitor-servers (ssh -J). This /etc/hosts entries (small fixed fleet)Inter-Compartment note:note (unchanged): Compartment isolation at the network layer (one of the eight isolation layers) is realized within sbsdash-server-prod at the Docker Compose network level — each Compartment's containers join aone dedicated compose network;network per Compartment, no twoshared Compartmentbridges, networks share a bridge. Cross-cross-Compartment traffic is denied by non-membership. Docker network29.1.3 non-membership,/ consistentCompose with2.40.3 §2.2.1are ofnow thepresent platformon architecture.prod, dev, and monitor (2026-07-18), so this layer is buildable. Container-level detail is specified in Chapter 6.
3.4 Egress Control
DigitalOcean provides no managed NAT/egress gateway; egress control is enforced at the droplethost firewall layer:
Default-deny outbound is not practical at the cloud firewall(Cloud Firewalls support outbound rules but a strict allowlist breaks OS packaging and ACME). Policy instead:restricted-Restricted-egress allowlist at the host firewall(nftables)on each Prod droplet, permitting:- TCP 443 to Anthropic API
endpointsendpoints;(platform AI traffic)
As built: no egress control exists on any droplet (all outbound open). Egress enforcement lands with the host-firewall baseline (§3.6, Build Register item 20).
3.5 Cloud Firewall Rules (DigitalOcean Cloud Firewalls, tag-scoped)
ThreeAs built: no Cloud Firewalls exist. Host-level ufw is active on monitor and wiki only; prod and dev have no active firewall at any layer — their public interfaces are protected solely by service bind addresses. This is the most exposed element of the current state and is remediated first in the firewall workstream (Build Register item 19).
Target — four firewalls, attached by droplet tag:
FW prod-app → tag prod (sbsdash-server-prod)
| Dir | Proto/Port | Source / Destination | Purpose |
|---|---|---|---|
| In | TCP 443 | 0.0.0.0/0, ::/0 | Tenant surfaces |
| In | TCP 80 | 0.0.0.0/0, ::/0 | ACME HTTP-01 + |
| In | TCP 22 | monitor-servers |
|
| In | TCP |
monitor-servers private IP |
Prometheus scrape |
| Out | Per §3.4 | — | Restricted egress |
FW prod-monitor → tag grafana (monitor-servers)
| Dir | Proto/Port | Source | Purpose |
|---|---|---|---|
| In | TCP 443 | 0.0.0.0/0 | monitor.sbsdash.com (Grafana behind |
| In | TCP 80 | 0.0.0.0/0 | ACME + redirect |
| In | TCP 22 | Admin allowlist IPs (Build Register) | Bastion SSH — sole public SSH entry to Prod |
| — | — | — |
FW prod-wiki → tag wiki (sbs-wiki), post-rebuild)
| Dir | Proto/Port | Source | Purpose |
|---|---|---|---|
| In | TCP 443 | 0.0.0.0/0 | wiki.sbsdash.com (BookStack behind SSO) |
| In | TCP 80 | 0.0.0.0/0 | ACME + redirect |
| In | TCP 22 | monitor-servers |
SSH via bastion |
| In | TCP 9100 | monitor-servers private IP |
node_exporter scrape (rebinds private post-rebuild) |
FW dev-app → tag develop mirrors prod-app with— SSH open to the samefrom admin allowlist and no public 443 requirement (Dev has no production DNS); Devallowlist; web surfaces admit only admin allowlist IPs.only (no production DNS points at Dev — enforcement of this depends on resolving §2.2.2).
Deviation from prior draft, deliberate: direct public SSH to prod-app is removed in favor of bastion-only SSH, codifying the as-built jump-host pattern — it is the stronger posture. Rule hygiene:hygiene unchanged: firewalls are IaC-defined (Chapter 10);defined; console edits to firewall rules are treated as drift and reverted.reverted; Thethe admin SSH allowlist is a named IaC variable in IaC, reviewed at each IAM-06 access review.
3.6 Host Firewall (nftables)
Cloud Firewalls filter at the platform edge; nftablesa host firewall on each droplet provides defense-in-depth and the per-container egress allowlist (§3.4), which Cloud Firewalls alone cannot expressexpress.
As built: ufw on monitor (22/80/443 in) and wiki (22/80/443 + 9100-from-monitor in); ufw inactive on prod and dev; no egress rules anywhere; no nftables rulesets deployed.
Target: nftables on all droplets — required because per-container:
3.7 Verification
Network controls in this chapter map to gates in (LH-SBS-INST-001:001): public-listener scan (— no data-service or Prometheus ports on public interfaces),interfaces (passes today on monitor per ss -tlnp evidence; wiki exporter public listener is a tracked exception until rebuild); Dev↔Prod isolation probe (— no private path),path between environments (fails today: shared default-nyc2; passes after rebuild wave); egress-allowlist test (denied— denied-domain attempt logged),logged and(not testable today: no egress control); per-Compartment cross-network denial test.test (pending Chapter 6 build); bastion path test — prod SSH unreachable except via monitor. INF-01 is the controlling SEC-001 requirement throughout.