diff --git a/README.md b/README.md
index 1f72126..06fca65 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,43 @@ Staging contains an unchanged copy of production todo data. Treat staging hosts,
## Architecture
+```mermaid
+flowchart TB
+ client([HTTP clients])
+
+ subgraph cloud[Hetzner Cloud]
+ lb[Hetzner Load Balancer]
+
+ subgraph network[Private Network 10.0.0.0/16]
+ subgraph prod[Production VM - Rocky Linux 10]
+ prod_app[Go App
Rootless Podman]
+ prod_db[(PostgreSQL
127.0.0.1:5432)]
+ prod_app -->|Local connection| prod_db
+ end
+
+ subgraph staging[Staging VM - Rocky Linux 10]
+ staging_app[Go App
Rootless Podman]
+ staging_db[(PostgreSQL
127.0.0.1:5432)]
+ staging_app -->|Local connection| staging_db
+ end
+ end
+
+ storage[(Hetzner Object Storage
S3-compatible bucket)]
+ end
+
+ client -->|Port 80| lb
+ lb -->|10.0.1.10:8080| prod_app
+ prod_db -->|WAL archive and hourly base backup| storage
+ storage -->|Restore LATEST at 06:00 and 12:00| staging_db
+
+ classDef production fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d,stroke-width:2px;
+ classDef stagingEnv fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a,stroke-width:2px;
+ class prod_app,prod_db production;
+ class staging_app,staging_db stagingEnv;
+ style prod fill:#fff1f2,stroke:#b91c1c,stroke-width:2px
+ style staging fill:#eff6ff,stroke:#1d4ed8,stroke-width:2px
+```
+
- The load balancer accepts HTTP on port 80 and reaches production on `10.0.1.10:8080` over the private network.
- The Go application runs as the rootless `todo` user through Quadlet-generated `app-todo.service` with host networking.
- PostgreSQL listens only on `127.0.0.1:5432`; the cloud firewall does not expose it.