Self-directed learning project

Self-hosted k3s cluster on AWS with Flask/PostgreSQL and Cloudflare

A single-node Kubernetes cluster on EC2 running a public Flask app over Traefik and Cloudflare — with the API server closed to the internet and reachable only over a Tailscale mesh.

  • Cloud
  • Kubernetes
  • Networking

01 Problem

I wanted production-shaped Kubernetes experience — ingress, stateful workloads, public exposure, secure admin access — on a single low-cost cloud instance rather than a managed control plane.

02 Approach

Deployed a single-node k3s cluster and ran a containerized Flask application backed by PostgreSQL as a StatefulSet.

Published it to the internet through the bundled Traefik ingress with Cloudflare in front for DNS and TLS, while keeping the Kubernetes API server private: the AWS security group closes port 6443 to the public and the API is reachable only over a Tailscale mesh, so kubectl works from my Windows machine without ever exposing the control plane.

I also built a separate multi-node variant with kubeadm running inside WSL2, joining the EC2 node over Tailscale.

03 Architecture

Two paths, deliberately separate: the public data plane, and the admin path that never touches the internet.

k3s cluster architecture: public ingress through Cloudflare, private API access over Tailscale Traffic from the public internet reaches Cloudflare, which forwards it into an AWS EC2 instance to the Traefik ingress, then to the Flask pod, then to the PostgreSQL StatefulSet. Separately, an administrator workstation reaches the k3s API server over a Tailscale mesh. Port 6443 is closed to the public internet at the AWS security group. AWS EC2 · t3.small · Ubuntu 24.04 security group port 6443 — closed to the public Internet public Cloudflare DNS · TLS · proxy Traefik ingress klipper-lb · :80 / :443 Flask app container · Deployment PostgreSQL StatefulSet · PVC k3s API server :6443 · private only Windows host kubectl Tailscale WireGuard mesh flannel CNI · in-cluster networking
Public data plane Private admin path (Tailscale) Closed at the security group

04 Stack

  • k3s v1.35.4
  • Ubuntu 24.04 (t3.small)
  • Traefik
  • klipper-lb
  • flannel CNI
  • PostgreSQL StatefulSet
  • Docker
  • Tailscale
  • Cloudflare
  • AWS EC2 / Security Groups

05 Result

A working public web app on infrastructure I stood up end to end.

Built as a test cluster and since decommissioned.

06 What broke, and what fixed it

k3s-aws-cluster/diagnostics 3 entries
  • Control-plane OOM restart loop

    Diagnosed and fixed by pinning --node-ip to a single interface.

  • EXTERNAL-IP advertising

    Corrected by setting explicit --tls-san values.

  • Exposure boundary

    Separated public data-plane reachability from private API access.