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:
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):
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.