This Website

Cover

Cover

Audio



*AI-generated content

Technical Architecture: caiodonalisio.com

This document describes the technical architecture of caiodonalisio.com, covering everything from network infrastructure to the CI/CD pipeline.

Overview

Internet ↓ Cloudflare Tunnel ↓ NGINX → static files ↓ Django/Gunicorn ↓ PostgreSQL ←→ Redis


1. Network Infrastructure

Hardware

The site runs on a single-node Kubernetes cluster hosted on a local mini-PC. The network topology is as follows:

Internet ↓ Mikrotik Router (RouterOS) • Inter-VLAN routing • Firewall per VLAN ↓ trunk port (tagged) Managed Switch (VLAN-aware) • VLAN 10: Home • VLAN 20: Servers • VLAN 30: IoT ↓ access port (VLAN 20) Mini-PC • Ubuntu Server • K3s single-node

VLAN Isolation

The server resides in a dedicated VLAN (VLAN 20), isolated from the rest of the home network. The Mikrotik manages:

  • Inter-VLAN firewall: the server VLAN has no access to the home network
  • Management access: only specific devices from the main VLAN can access SSH
  • Internet egress: permitted for the Cloudflare Tunnel to establish an outbound connection

This segmentation ensures that even in the event of server compromise, the attacker would have no lateral access to home devices.

Internet Exposure

There is no port forwarding on the router. All incoming traffic passes through the Cloudflare Tunnel, which maintains a persistent outbound connection to Cloudflare's edge. This eliminates the need for:

  • A static public IP
  • Open firewall ports
  • Manual TLS certificate management

2. Kubernetes (K3s)

Cluster

K3s single-node with the following resources deployed in the caiodonalisio namespace:

| Resource | Replicas | Role | |----------|----------|------| | Django/Gunicorn | 2 | Web application | | NGINX | 1 | Reverse proxy + static files | | PostgreSQL | 1 | Database | | Redis | 1 | Cache + session store | | Cloudflared | 1 | Tunnel daemon |

Storage

Uses K3s's local-path provisioner:

  • staticfiles-pvc (2Gi): collected static files
  • media-pvc (2Gi): uploads and media files
  • postgres-pvc (10Gi): PostgreSQL data

Health Checks and Resilience

The Django deployment configures:

```yaml readinessProbe: httpGet: path: /health/ port: 3334 initialDelaySeconds: 60 periodSeconds: 15 failureThreshold: 3

livenessProbe: httpGet: path: /health/ port: 3334 initialDelaySeconds: 90 periodSeconds: 30 failureThreshold: 5 ```

Combined with a PodDisruptionBudget (minAvailable: 1) and rolling updates (maxUnavailable: 0, maxSurge: 1), this ensures zero-downtime during deploys.

Graceful Shutdown

A pre-stop hook with a 15-second sleep allows in-flight connections to drain before SIGTERM:

yaml lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 15"]


3. Cloudflare Tunnel

The cloudflared daemon runs as a deployment in the cluster, configured via ConfigMap:

yaml tunnel: <tunnel-id> credentials-file: /etc/cloudflared/creds/credentials.json ingress: - hostname: caiodonalisio.com service: http://nginx-svc:80 - hostname: www.caiodonalisio.com service: http://nginx-svc:80 - service: http_status:404

Relevant settings:

  • hostNetwork: true: avoids intermittent 502 issues on single-node clusters
  • dnsPolicy: ClusterFirstWithHostNet: preserves internal service resolution
  • protocol: http2: multiplexed connection to the edge

4. Application Stack

Backend

| Component | Version | Configuration | |-----------|---------|---------------| | Python | 3.12 | - | | Django | 5.1 | Modular settings (dev/prod/test) | | Gunicorn | - | 4 workers, port 3334 | | Celery | 5.4 | Configured, workers disabled |

Database and Cache

| Component | Version | Usage | |-----------|---------|-------| | PostgreSQL | 16.4 | Primary persistence | | Redis | 7.4.1 | Sessions, cache, Celery broker |

Django Structure

``` website/ # Project settings ├── settings/ │ ├── base.py │ ├── production.py │ └── development.py ├── urls.py # Routes + /health/ endpoint └── celery.py

blog/ # Main app ├── models.py # Post, PostFile, PostBook ├── views.py # Markdown rendering └── storage.py # Custom OverwriteStorage

accounts/ # Authentication └── models.py # CustomUser

pages/ # Static pages ```

Serving

  • NGINX: reverse proxy, serves /static/ and /media/ directly
  • WhiteNoise: fallback for static files with CompressedManifestStaticFilesStorage
  • Gunicorn: WSGI server for dynamic requests

5. CI/CD Pipeline

GitHub Actions

The build_release.yml workflow runs on push to main:

Test (pytest) ↓ Build (docker) ↓ Semantic Release ↓ ├→ Trivy Scan ├→ Push to Docker Hub └→ Update K8s Manifests ↓ Argo CD Auto-Sync

Semantic Versioning

Versions are generated automatically via python-semantic-release:

  • fix: → PATCH
  • feat: → MINOR
  • BREAKING CHANGE: → MAJOR

GitOps with Argo CD

Argo CD monitors the k8s/ directory and applies changes automatically:

yaml syncPolicy: automated: prune: true selfHeal: true retry: limit: 5 backoff: duration: 5s maxDuration: 3m factor: 2

The update-k8s-manifest GitHub Actions job updates the image tag in k8s/website.yml, triggering an Argo CD sync.


6. Containerization

Multi-stage Build

```dockerfile

Stage 1: Builder

FROM python:3.12.4-slim AS builder COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ RUN uv pip compile requirements.txt -o requirements.lock RUN uv pip sync requirements.lock

Stage 2: Runtime

FROM python:3.12.4-slim COPY --from=builder /.venv /.venv COPY . /usr/src/ EXPOSE 3334 CMD ["./entrypoint.sh"] ```

The use of uv (a Rust-based package manager) significantly speeds up dependency builds.

Entrypoint

entrypoint.sh runs on container startup:

  1. Waits for PostgreSQL (healthcheck via netcat)
  2. collectstatic
  3. migrate
  4. Media file sync
  5. Starts Gunicorn

7. Full Request Flow

INTERNET ↓ CLOUDFLARE EDGE (DDoS, SSL) ↓ tunnel MIKROTIK → VLAN 20 → Mini-PC ↓ K3s CLUSTER ↓ cloudflared (hostNetwork) ↓ nginx-svc • /static/ → staticfiles-pvc • /media/ → media-pvc • /* → website-svc ↓ website-svc (2 replicas) Django + Gunicorn :3334 ↓ postgres-svc ←→ redis-svc


8. Security Considerations

| Layer | Measure | |-------|---------| | Network | VLAN isolation, no port forwarding | | Edge | Cloudflare DDoS protection, WAF | | Transport | TLS managed by Cloudflare | | Cluster | Network policies, secrets for credentials | | Application | CSRF, secure cookies, ALLOWED_HOSTS | | CI/CD | Trivy scan for image vulnerabilities |


Last updated: January 2026