Architecture Overview¶
KanjiIQ follows a multi-container pod architecture deployed on Kubernetes. The frontend and backend run as separate containers within the same pod, communicating over localhost.
System Diagram¶
graph TB
subgraph Internet
U[User Browser]
end
subgraph Hetzner["Hetzner k3s Cluster"]
subgraph NS["Namespace: jlpt-kanji"]
T[Traefik Ingress Controller]
subgraph Pod["Application Pod (x2 replicas)"]
FE[Flutter Web<br/>Nginx :80]
BE[Dart Frog API<br/>:8080]
end
PG[(PostgreSQL 15<br/>PVC)]
subgraph Middleware["Traefik Middlewares"]
RL[Rate Limiting]
SH[Security Headers]
end
end
CM[cert-manager<br/>Let's Encrypt]
end
U -->|HTTPS| T
T --> Middleware
Middleware -->|kanjiiq.com| FE
Middleware -->|api.kanjiiq.com| BE
FE -->|/api/ proxy| BE
BE --> PG
CM -->|TLS Certificates| T
Domain Architecture¶
| Domain | Service | Purpose |
|---|---|---|
kanjiiq.com |
Frontend (Nginx) | Flutter Web application |
www.kanjiiq.com |
Frontend (Nginx) | Redirect to main domain |
api.kanjiiq.com |
Backend (Dart Frog) | REST API |
admin.kanjiiq.com |
Backend (Dart Frog) | Admin dashboard API |
docs.kanjiiq.app |
Docs (Nginx) | This documentation site |
Multi-Container Pod Design¶
The application runs as a single Kubernetes Deployment with 2 replicas, each containing two containers:
# Simplified view of k8s/05-deployment.yaml
spec:
replicas: 2
template:
spec:
containers:
- name: backend # Dart Frog API on :8080
- name: frontend # Nginx serving Flutter Web on :80
Why a multi-container pod?
- The frontend Nginx proxies
/api/requests tolocalhost:8080(the backend), avoiding cross-origin issues - Both containers scale together — each replica is a complete, self-contained unit
- Simplified networking: frontend-to-backend communication stays within the pod
Component Summary¶
Frontend¶
- Framework: Flutter Web with Material Design 3
- Serving: Nginx Alpine (non-root, UID 1000)
- State Management: Provider
- Routing: GoRouter
- Offline Support: Local SQLite cache with background sync
Backend¶
- Framework: Dart Frog 1.1.0
- Authentication: JWT (for admin endpoints)
- Security: Multi-layer middleware stack (IP blocking, path detection, rate limiting)
- Translation: OpenAI API for dynamic content translation
Database¶
- Engine: PostgreSQL 15
- Storage: Kubernetes PersistentVolumeClaim (10Gi)
- Schema: JSONB columns for multilingual content storage
- Primary Keys: UUID
Infrastructure¶
- Cluster: k3s on Hetzner dedicated server
- Ingress: Traefik with automatic HTTPS
- TLS: cert-manager with Let's Encrypt
- CI/CD: Forgejo Actions (self-hosted)
- Registry: Forgejo Container Registry
Security Layers¶
KanjiIQ implements defense in depth with multiple security layers:
graph LR
R[Request] --> L1[Traefik<br/>Rate Limiting]
L1 --> L2[Traefik<br/>Security Headers]
L2 --> L3[App: IP<br/>Blocklist Check]
L3 --> L4[App: Malicious<br/>Path Detection]
L4 --> L5[App: SQL Injection<br/>Detection]
L5 --> L6[App: Regional<br/>Analytics]
L6 --> H[Request Handler]
- Infrastructure level (Traefik): Rate limiting (100 req/min public, 50 req/min admin) and security headers (HSTS, X-Frame-Options, CSP)
- Application level (Dart Frog middleware): IP blocklist, malicious path detection (.env, .php, config files), SQL injection patterns, path traversal prevention
- Analytics level: Regional request tracking with automatic blocking after 3 suspicious requests in 24 hours
See Backend Architecture for details on the middleware stack.