Skip to content

Infrastructure Architecture

Everything below is defined in this repository's terraform/ and helm/ trees - nothing here is aspirational. See Terraform for the full module reference.

Single-VM topology (staging + production)

Staging and production share one K3s VM (terraform/environments/platform), namespace-separated at the Kubernetes layer (akshayabazaar-staging / akshayabazaar-production), not two physically separate nodes. Everything else stays fully separate per environment:

Layer Shared? How
K3s VM / Traefik Yes - one node, one public IP Hostname-based Traefik routing (staging.<domain> vs <domain>)
Private network Yes - one Hetzner Network Static IPs: .10 platform node, .20 staging data VM, .30 production data VM, .40 dev data VM
Kubernetes namespace No One Helm release per environment, --namespace at deploy time
Data VM (MySQL + Redis) No Separate Hetzner server, credentials, and firewall per environment
Kubernetes Secret No akshayabazaar-<env>-secrets - distinct names even though namespacing alone already isolates them
R2 bucket (media/backups) No akshayabazaar-media-<env> / akshayabazaar-backups-<env>
NetworkPolicy No Kubernetes' own semantics already scope every rule to its own namespace

Full rationale for where this line was drawn: terraform/README.md "Single-VM topology".

Terraform module map

terraform/
├── bootstrap/        # one-time: creates the R2 bucket used as the Terraform remote-state backend
├── cloud-init/        # shared cloud-init templates (k3s-node-shared.yaml.tpl, data-vm.yaml.tpl)
├── modules/
│   ├── hetzner-network/    # private network + subnet
│   ├── hetzner-server/     # one Hetzner VM primitive - used for BOTH the K3s node and every data VM
│   ├── hetzner-firewall/   # Hetzner Cloud firewall rules
│   ├── cloudflare-dns/     # DNS A records
│   ├── cloudflare-security/ # zone-wide security settings (owned by production only)
│   └── cloudflare-r2/      # a single R2 bucket
└── environments/
    ├── platform/    # the shared K3s node + network - applied first, standalone
    ├── staging/     # staging's own data VM, DNS record, R2 buckets
    ├── production/  # production's own data VM, DNS record, R2 buckets, owns zone-wide Cloudflare settings
    ├── dev/         # structural scaffold, modeled on staging - not yet applied
    └── shared/      # cross-environment concerns

There is no dedicated kubernetes or database Terraform module - K3s is installed via cloud-init/k3s-node-shared.yaml.tpl at VM boot time on a hetzner-server instance, and MySQL/Redis run as software installed via cloud-init/data-vm.yaml.tpl on another hetzner-server instance. Hetzner Cloud has no native managed Kubernetes or managed MySQL/Redis product; provision_data_vm=false plus external_mysql_host/external_redis_host is the escape hatch to a real managed database provider if one is adopted later.

What runs where

Hetzner Cloud
├── K3s node (environments/platform)
│   ├── Traefik (bundled with K3s)
│   ├── namespace: staging      → akshaya-local/akshayabazaar Helm release → frontend + backend pods
│   └── namespace: production   → akshayabazaar Helm release → frontend + backend pods
├── staging data VM (environments/staging)
│   └── MySQL 8.2 + Redis 7.2 (cloud-init installed)
└── production data VM (environments/production)
    └── MySQL 8.2 + Redis 7.2 (cloud-init installed)

Cloudflare
├── DNS (cloudflare-dns module) - A records per environment, pointing at the K3s node's public IP
├── Security (cloudflare-security module) - zone-wide WAF/SSL settings, owned by production
└── R2 (cloudflare-r2 module) - one media bucket + one backup bucket per environment

Local development infrastructure (not Terraform-managed)

The local environment (a developer's own machine) uses Docker Desktop's built-in k3d/K3s - entirely separate from the Terraform-provisioned infrastructure above, with its own namespace-local MySQL/Redis pods (mysql-local/redis-local, hand-written manifests, not part of the Helm chart) standing in for the real data VM. See Kubernetes → k3d and Local Development.