This Website


Cover

Audio



This Website

*AI-generated content, based on my own information and instructions.

Django Kubernetes PostgreSQL Cloudflare Since 2020

This website was born in 2020, when all I wanted was to learn how to publish something — anything — online. It looks quite different today: it went from a ready-made site builder to a custom Django application, and from a single AWS VM to a Kubernetes cluster running locally, with automated deployment and resilience layers that didn't exist in the first version.

What you'll find here

  • Technical Architecture — how the site works today: network infrastructure, Kubernetes, CI/CD, and the stack choices behind it.
  • Repository Structure — the source code's directory tree, fetched live.
  • Original version (2021) — the motivation-and-learning account I wrote when I first built the site, preserved exactly as it was published back then — images and beginner's voice included.

As a data analyst, most of my work could always be done locally and offline — scraping data, running regressions, training models. But the world kept moving toward the cloud, and I was missing a piece: knowing how to publish and automate all of that without leaving my personal computer running. This site was how I chose to fill that gap — and it's still where I experiment with new technologies and share projects and studies.

Below is the current architecture; at the end of the post, the original 2021 text tells the story of how it all started.


Technical Architecture

This document describes, at a high level, how caiodonalisio.com works today — from network infrastructure to the CI/CD pipeline.

Overview

Internet
Cloudflare Tunnel
NGINX (static files)
Django / Gunicorn
PostgreSQL ⇄ Redis

1. Network Infrastructure

The site runs on a single-node Kubernetes cluster, hosted on a local mini-PC:

Internet
Mikrotik Router (RouterOS)
inter-VLAN routing and firewall
↓ trunk (tagged)
Managed switch (VLAN-aware)
VLAN 10 · Home VLAN 20 · Servers VLAN 30 · IoT
↓ access (VLAN 20)
Mini-PC · Ubuntu Server · K3s single-node

VLAN isolation — the server sits on a dedicated VLAN, isolated from the rest of the home network. The router ensures the server VLAN has no access to the home network, that only specific devices can reach management SSH, and that outbound internet access is limited to what the Cloudflare Tunnel needs. This segmentation means that even if the server were compromised, there would be no lateral access to home devices.

Internet exposure — there is no port forwarding on the router. All inbound traffic goes through the Cloudflare Tunnel, which keeps a persistent outbound connection to Cloudflare's edge. That removes the need for a static public IP, open firewall ports, or manual TLS certificate management.

2. Kubernetes (K3s)

Single-node K3s with the following resources in the caiodonalisio namespace:

ResourceReplicasRole
Django / Gunicorn2Web application
NGINX1Reverse proxy + static files
PostgreSQL1Database
Redis1Cache + session store
Cloudflared1Tunnel daemon

Storage — provisioned via K3s's local-path: staticfiles-pvc (2Gi) for collected static files, media-pvc (2Gi) for uploads and media, postgres-pvc (10Gi) for PostgreSQL data.

Health checks and resilience — the Django deployment exposes a /health/ endpoint, checked by readiness and liveness probes:

readinessProbe → /health/ (60s delay, every 15s, 3 failures)
livenessProbe  → /health/ (90s delay, every 30s, 5 failures)

Combined with a PodDisruptionBudget (minimum 1 replica available) and zero-downtime rolling updates (maxUnavailable: 0, maxSurge: 1), this guarantees no downtime during deploys. A pre-stop hook waits 15 seconds before SIGTERM, letting in-flight connections drain.

3. Cloudflare Tunnel

The cloudflared daemon runs as a deployment in the cluster, routing public hostnames to the internal NGINX service. Three configuration details matter in practice:

  • hostNetwork: true — avoids intermittent 502s on single-node clusters
  • dnsPolicy: ClusterFirstWithHostNet — keeps internal service resolution working
  • protocol: http2 — multiplexed connection to Cloudflare's edge

4. Application Stack

ComponentVersionNotes
Python3.12
Django5.1Modular settings (dev / prod / test)
Gunicorn4 workers, port 3334
Celery5.4Configured, workers disabled
PostgreSQL16.4Primary persistence
Redis7.4.1Sessions, cache, Celery broker

Simplified Django project structure:

website/           # Project settings
├── settings/      # base / production / development
├── 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 (CustomUser)
pages/             # Static pages

Serving — NGINX acts as reverse proxy and serves /static/ and /media/ directly; WhiteNoise is the fallback for static files; Gunicorn handles dynamic requests over WSGI.

5. CI/CD

Tests (pytest)
Build (Docker)
Semantic Release
Trivy scan Push to Docker Hub Update K8s manifests
Argo CD · Auto-Sync

The GitHub Actions workflow triggers on every push to main. Versions are generated automatically by python-semantic-release from the commit message: fix: bumps PATCH, feat: bumps MINOR, BREAKING CHANGE: bumps MAJOR.

Argo CD watches the repository's k8s/ directory and applies changes automatically (prune and self-heal enabled, with progressive retries). The pipeline itself updates the image tag in the manifest, which triggers the sync.

6. Containerization

Multi-stage build, using uv (a Rust-based package manager) to speed up dependency resolution:

# Stage 1: builder — resolves and installs dependencies with uv
FROM python:3.12-slim AS builder
...

# Stage 2: runtime — copies only what's needed
FROM python:3.12-slim
COPY --from=builder /.venv /.venv
COPY . /usr/src/
EXPOSE 3334
CMD ["./entrypoint.sh"]

entrypoint.sh runs on container startup: waits for PostgreSQL to be reachable, runs collectstatic and migrate, syncs media files, and finally starts Gunicorn.

7. Full Request Flow

Internet
Cloudflare Edge (DDoS, TLS)
↓ tunnel
Mikrotik → VLAN 20 → Mini-PC
cloudflared (hostNetwork)
nginx-svc
/static/ → staticfiles-pvc /media/ → media-pvc /* → website-svc
website-svc (2 replicas) · Django + Gunicorn
postgres-svc ⇄ redis-svc

8. Security Considerations

LayerMeasure
NetworkVLAN isolation, no port forwarding
EdgeCloudflare DDoS protection and WAF
TransportTLS managed by Cloudflare
ClusterNetwork policies, secrets for credentials
ApplicationCSRF, secure cookies, ALLOWED_HOSTS
CI/CDImage vulnerability scanning (Trivy)

Last updated: August 2026.


Repository Structure

Below is the directory tree of this site's repository, fetched and rendered live.