Skip to content

Kubernetes Manifests

All Kubernetes resources are defined in the k8s/ directory and applied in numbered order.

Manifest Overview

File Resource Purpose
00-namespace.yaml Namespace Creates jlpt-kanji namespace
01-secrets.yaml Secret Database URL, JWT secret, OpenAI key
02-postgres-pvc.yaml PersistentVolumeClaim 10Gi storage for PostgreSQL
03-postgres-deployment.yaml Deployment + Service PostgreSQL 15 database
04-libretranslate-deployment.yaml Deployment + Service Self-hosted translation (optional)
05-deployment.yaml Deployment Main app (frontend + backend containers)
06-service.yaml Service (x2) Frontend (:80) and Backend (:8080) services
07-ingress.yaml Ingress Traefik routing for jlpt.iqquest.app
08-security-middlewares.yaml Middleware (CRD) Rate limiting and security headers
ingress-kanjiiq.yaml Ingress Routing for kanjiiq.com domains
ingress-docs.yaml Ingress Routing for docs.kanjiiq.app
docs-deployment.yaml Deployment Documentation site
docs-service.yaml Service Documentation service

Initial Deployment

Apply manifests in order:

# Create namespace and secrets
kubectl apply -f k8s/00-namespace.yaml
kubectl apply -f k8s/01-secrets.yaml

# Database
kubectl apply -f k8s/02-postgres-pvc.yaml
kubectl apply -f k8s/03-postgres-deployment.yaml

# Application
kubectl apply -f k8s/05-deployment.yaml
kubectl apply -f k8s/06-service.yaml

# Networking
kubectl apply -f k8s/07-ingress.yaml
kubectl apply -f k8s/08-security-middlewares.yaml
kubectl apply -f k8s/ingress-kanjiiq.yaml

Namespace: jlpt-kanji

All resources live in a single namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: jlpt-kanji

This provides:

  • Resource isolation from other workloads
  • Scoped RBAC (if needed)
  • Easy cleanup (kubectl delete namespace jlpt-kanji)
  • Resource quota enforcement (if needed)

Secrets Management

Sensitive values are stored in Kubernetes Secrets (not committed to Git):

apiVersion: v1
kind: Secret
metadata:
  name: jlpt-kanji-secrets
  namespace: jlpt-kanji
type: Opaque
stringData:
  database-url: "postgresql://user:pass@jlpt-postgres:5432/jlpt_flashcard"
  jwt-secret: "random-secret-string"
  openai-api-key: "sk-..."

The 01-secrets.yaml in the repository is a template — actual values are applied manually on the server.

Pod Design

The main application pod runs two containers side-by-side:

spec:
  replicas: 2
  template:
    spec:
      imagePullSecrets:
        - name: forgejo-registry
      containers:
        - name: backend
          image: <registry>/jlpt-kanji-backend:latest
          ports:
            - containerPort: 8080
          resources:
            requests: { memory: "256Mi", cpu: "250m" }
            limits: { memory: "512Mi", cpu: "500m" }

        - name: frontend
          image: <registry>/jlpt-kanji-frontend:latest
          ports:
            - containerPort: 80
          resources:
            requests: { memory: "128Mi", cpu: "100m" }
            limits: { memory: "256Mi", cpu: "200m" }

Health Checks

Both containers have liveness and readiness probes:

  • Liveness: Restarts the container if it becomes unresponsive (every 10s, 3 failures)
  • Readiness: Removes the pod from Service endpoints until ready (every 5s, 2 failures)

Security Context

All containers run as non-root (UID 1000):

securityContext:
  runAsUser: 1000
  runAsGroup: 1000
  runAsNonRoot: true

Services

Two ClusterIP services expose the pod's containers to the cluster:

# Frontend service (port 80 → 80)
name: jlpt-kanji-frontend
ports:
  - port: 80
    targetPort: 80

# Backend service (port 80 → 8080)
name: jlpt-kanji-backend
ports:
  - port: 80
    targetPort: 8080

The backend service maps external port 80 to internal port 8080, so Traefik routes to all services on the same port.

Ingress

Host-based routing via Traefik:

spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - kanjiiq.com
        - api.kanjiiq.com
        - admin.kanjiiq.com
      secretName: kanjiiq-tls
  rules:
    - host: kanjiiq.com       → jlpt-kanji-frontend:80
    - host: api.kanjiiq.com   → jlpt-kanji-backend:80
    - host: admin.kanjiiq.com → jlpt-kanji-backend:80

TLS certificates are automatically provisioned by cert-manager when the Ingress is created.