Infrastructure¶
KanjiIQ runs on a self-hosted Kubernetes cluster on Hetzner, using Traefik for ingress and cert-manager for automatic TLS.
Hosting: Hetzner Dedicated Server¶
The entire production environment runs on a single Hetzner dedicated server:
- Kubernetes distribution: k3s (lightweight, single-binary Kubernetes)
- OS: Linux
- Networking: Public IPv4 with Cloudflare DNS
Why Hetzner?¶
- Predictable monthly pricing (no per-request or per-hour charges)
- European data center locations (GDPR-friendly)
- Excellent price-to-performance ratio
- Full root access for k3s installation
Kubernetes: k3s¶
k3s is a lightweight, CNCF-certified Kubernetes distribution that bundles:
- Container runtime: containerd
- Ingress controller: Traefik (pre-installed)
- Service load balancer: Klipper
- Storage: Local path provisioner
- DNS: CoreDNS
k3s runs Kubernetes with a single binary (~100MB) instead of the multi-component kubeadm setup. It is fully compatible with standard Kubernetes APIs — all kubectl commands and manifests work identically.
Ingress: Traefik¶
Traefik serves as the cluster's ingress controller, handling:
- TLS termination: Automatic HTTPS for all domains
- Routing: Host-based routing to services (kanjiiq.com → frontend, api.kanjiiq.com → backend)
- Rate limiting: Per-IP request throttling via CRD middlewares
- Security headers: HSTS, X-Frame-Options, CSP via middleware chain
Middleware Configuration¶
# Public traffic: rate limit + security headers
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: public-security-chain
spec:
chain:
middlewares:
- name: rate-limit # 100 req/min, burst 50
- name: security-headers # HSTS, X-Frame-Options, etc.
# Admin traffic: stricter rate limit + auth
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: admin-security-chain
spec:
chain:
middlewares:
- name: api-rate-limit # 50 req/min, burst 25
- name: security-headers
- name: admin-auth
TLS: cert-manager + Let's Encrypt¶
Automatic TLS certificate management:
graph LR
I[Ingress with<br/>cert-manager annotation] --> CM[cert-manager]
CM --> LE[Let's Encrypt<br/>ACME challenge]
LE --> C[TLS Certificate]
C --> S[Kubernetes Secret]
S --> T[Traefik uses cert<br/>for HTTPS]
- ClusterIssuer:
letsencrypt-prod(cluster-wide certificate authority) - Challenge type: HTTP-01 via Traefik
- Renewal: Automatic before expiry
- Storage: Certificates stored as Kubernetes Secrets
Ingress resources request certificates with a single annotation:
DNS: Cloudflare¶
Domain DNS is managed through Cloudflare:
- A records: Point to Hetzner server IP
- Proxied: Optional Cloudflare CDN/WAF layer
- CF-IPCountry header: Used by the backend for geolocation without IP databases
Container Registry: Forgejo¶
Docker images are stored in the Forgejo Container Registry at <registry>:
- Co-located with source code (single platform)
- Image pull secrets configured in Kubernetes (
forgejo-registry) - Tags:
:latest+:COMMIT_SHAfor each build - No external registry dependencies (Docker Hub, GHCR, etc.)
Resource Allocation¶
| Component | CPU Request | CPU Limit | Memory Request | Memory Limit |
|---|---|---|---|---|
| Backend | 250m | 500m | 256Mi | 512Mi |
| Frontend | 100m | 200m | 128Mi | 256Mi |
| PostgreSQL | 250m | 500m | 256Mi | 512Mi |
| Docs | 10m | 100m | 32Mi | 64Mi |