Architecture Overview

This document explains how kup6s-pages components work together to provide multi-tenant static site hosting.

Components

pages-operator

The operator watches StaticSite CRDs across all namespaces and manages:

  • Traefik IngressRoutes - Route traffic to nginx based on domain

  • Traefik Middlewares - addPrefix middleware for efficient routing

  • cert-manager Certificates - Automatic TLS for custom domains

Key behaviors:

  • Uses ClusterRole to watch StaticSites in any namespace

  • Creates resources in the same namespace as the StaticSite

  • Shares certificates between sites using the same domain

pages-syncer

The syncer clones and pulls Git repositories:

  • Periodic sync - Polls repositories at configurable intervals (default 5m)

  • Webhook sync - Instant updates on git push

  • Secret access - Reads deploy tokens from the StaticSite’s namespace

  • Status updates - Updates StaticSite status with sync progress

Storage layout:

/sites/
├── my-website/              # Direct clone (no subpath)
├── .repos/
│   └── docs/               # Full clone when using subpath
│       └── dist/           # Build output
└── docs -> .repos/docs/dist/  # Symlink for subpath serving

nginx

A single nginx pod serves all static sites:

  • Shared PVC - ReadWriteMany storage shared with syncer

  • Simple config - Static root /sites; with try_files

  • HA ready - Multiple replicas with pod anti-affinity

Request Flow

  1. Client requests https://www.example.com/about.html

  2. Traefik matches Host(\www.example.com`)` IngressRoute

  3. Middleware adds prefix: /my-website/about.html

  4. nginx serves /sites/my-website/about.html from PVC

Cross-Namespace Design

kup6s-pages namespace (central)
├── pages-operator     → ClusterRole (watches all namespaces)
├── pages-syncer       → ClusterRole (reads secrets from any namespace)
├── nginx              → Serves from shared PVC
└── PVC: sites-data    → ReadWriteMany storage

customer-app namespace
├── StaticSite CRD     → Created by user
├── Secret (optional)  → Git credentials (read by syncer)
├── IngressRoute       → Created by operator
├── Middleware         → Created by operator
└── Certificate        → Created by operator (for custom domains)

Storage Requirements

The PVC must support ReadWriteMany (RWX) access mode since both syncer and nginx pods need concurrent access. In this cluster, the storagebox StorageClass (SMB CSI) provides RWX support.

Why addPrefix?

Instead of reconfiguring nginx for each site, we use Traefik’s addPrefix middleware:

  • No nginx reloads - Adding a site doesn’t restart nginx

  • No race conditions - ConfigMap updates can cause pod restarts

  • Simple nginx config - Static configuration never changes

  • Efficient routing - Traefik handles dynamic routing

This design allows scaling to hundreds of sites with minimal resource overhead.