Architecture

How the single-host platform is organized.

The platform stays intentionally small enough to review without hiding behind tooling the host size does not need. Containers provide isolation and deployability, while routing and service boundaries stay easy to inspect.

Last reviewed: April 2026 Public surfaces: 5 Primary ingress: Nginx + TLS
Why this choice The platform is intentionally small enough to stay reviewable. Container boundaries, hostnames, and reverse-proxy rules are easier to reason about here than a larger orchestration layer would be on one VM.
flowchart TD User["Client"] --> DNS["Public DNS"] DNS --> Nginx["Nginx reverse proxy"] Nginx --> Shellr["shellr.net"] Nginx --> DMA["dma.shellr.net"] Nginx --> Grafana["grafana.shellr.net"] Nginx --> Status["status.shellr.net"] Shellr --> App["Portfolio app container"] DMA --> DMAApp["DMA app container"] DMAApp --> DB["MariaDB"] Grafana --> Metrics["Prometheus metrics"] Grafana --> Logs["Loki logs"] Status --> Kuma["Uptime Kuma"] Docs["docs.shellr.net"] --> Pages["GitHub Pages"]

Operational Boundaries

Public, private, and failure-sensitive paths.

  • Public surfaces: shellr.net, dma.shellr.net, status.shellr.net, and protected grafana.shellr.net terminate at Nginx.
  • Private paths: MariaDB, Prometheus, and internal service traffic stay on Docker networks and are not exposed on the host.
  • Failure model: a broken app container should not collapse the rest of the platform, and docs remain outside the production VM on GitHub Pages.
  • Review model: each hostname maps to a clear runtime responsibility, which keeps routing and incident analysis simpler.

Trust and Failure Boundaries

What is public, what is internal, and what fails together.

  • Public ingress: only Nginx binds to host ports 80 and 443.
  • Loopback-only services: Grafana, Prometheus, Alertmanager, and Uptime Kuma stay on loopback or internal Docker networks.
  • Cross-host separation: docs.shellr.net is intentionally outside the VM so documentation does not disappear with an app incident.
  • Shared-risk boundary: app, DMA, monitoring, and logging still share one VM, so retention and recovery paths are designed with that limit in mind.

Deployment Flow

Build, stage, switch, verify.

sequenceDiagram participant Dev as Developer participant GH as GitHub participant Actions as Actions participant VM as VM Dev->>GH: push GH->>Actions: workflow Actions->>VM: transfer release VM->>VM: validate config VM->>VM: update containers VM->>VM: run healthchecks alt ok VM->>Actions: success else failed VM->>VM: rollback end
  • Failure mode: deployments are not considered complete until health checks pass.
  • Rollback path: staged releases and backup copies exist so a bad change can be reverted without improvisation.
  • Operational limit: this stays intentionally SSH- and Compose-based instead of adding a local runner or control plane.