Skip to content

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 to localhost: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]
  1. Infrastructure level (Traefik): Rate limiting (100 req/min public, 50 req/min admin) and security headers (HSTS, X-Frame-Options, CSP)
  2. Application level (Dart Frog middleware): IP blocklist, malicious path detection (.env, .php, config files), SQL injection patterns, path traversal prevention
  3. Analytics level: Regional request tracking with automatic blocking after 3 suspicious requests in 24 hours

See Backend Architecture for details on the middleware stack.