Skip to content

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:

annotations:
  cert-manager.io/cluster-issuer: letsencrypt-prod

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_SHA for 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