user@homelab:~/docs$ cat docs-site.md

Tento web

Hugo, dva jazyky, image s nginx bez root práv a zabezpečení podu

# obsah

Architektura

text
Markdown (docs-site/site/content/{en,cs}/*.md)
   │  hugo --minify                     ← build stage: alpine + Hugo ověřený přes SHA256
   ▼
statické HTML/CSS/JS
   │  COPY do nginx-unprivileged        ← runtime stage: uid 101, port 8080
   ▼
ghcr.io/<owner>/homelab-docs:<tag>      ← GitHub Actions
   │  Flux → Deployment → Service → Ingress (Traefik)
   ▼
https://svejklab.dev                    ← Cloudflare Tunnel

Sestavení a nasazení popisuje kapitola CI/CD a GitOps.

Struktura

text
docs-site/
├── site/
│   ├── hugo.toml             # konfigurace, jazyky en (výchozí) + cs
│   ├── content/en/           # anglické stránky
│   ├── content/cs/           # české stránky (stejné názvy souborů)
│   ├── i18n/                 # texty šablony (en.toml, cs.toml)
│   ├── data/status_*.yaml    # tabulky na úvodní stránce
│   ├── static/images/        # diagramy (SVG)
│   ├── layouts/              # vlastní "terminálová" šablona
│   └── assets/               # CSS a JS
├── nginx/default.conf        # bezpečnostní hlavičky, /healthz
└── Dockerfile                # multi-stage build

Šablona je vlastní, bez externích témat a CDN. Web nic nenačítá z cizích domén.

Dva jazyky

Hugo má vestavěnou podporu více jazyků. Stránky se spárují podle stejného jména souboru (content/en/docs/07-ssh.md ↔ content/cs/docs/07-ssh.md) a přepínač v záhlaví vede vždy na tutéž stránku v druhém jazyce.

Angličtina (výchozí)Čeština
adresahttps://svejklab.dev/https://svejklab.dev/cs/
obsahcontent/en/content/cs/
texty šablonyi18n/en.tomli18n/cs.toml
úvodní tabulkydata/status_en.yamldata/status_cs.yaml
toml @ git
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false   # angličtina na /, čeština na /cs/

Hlavička stránek obsahuje odkazy hreflang, takže vyhledávače vědí, že jde o jazykové verze téže stránky.

Image

Dockerfile má dvě fáze:

  1. build: Alpine stáhne Hugo z GitHubu, ověří jeho SHA256 a vyrobí z Markdownu statický web (hugo --minify --gc),
  2. runtime: hotové HTML se zkopíruje do nginx-unprivileged, který běží bez root práv na portu 8080.

Výsledný image obsahuje jen nginx a statické soubory, žádný Hugo ani zdrojové texty.

nginx (nginx/default.conf):

  • /healthz pro readiness a liveness probe,
  • bezpečnostní hlavičky: Content-Security-Policy (jen vlastní skripty a styly), X-Content-Type-Options, Referrer-Policy, Permissions-Policy,
  • CSS a JS mají v názvu otisk obsahu (fingerprint), takže se cachují natrvalo. HTML se vždy ověřuje (no-cache).

Bezpečnost podu

NastaveníProč
namespace pod-security…/enforce: restrictedcluster odmítne pod, který nesplňuje přísný profil
runAsNonRoot, uid 101nginx neběží jako root
readOnlyRootFilesystem + emptyDir /tmpkontejner nemůže měnit svůj systém souborů
capabilities: drop ALL, allowPrivilegeEscalation: falsežádná zvláštní práva jádra
seccompProfile: RuntimeDefaultomezená systémová volání
automountServiceAccountToken: falsepod nemá token ke Kubernetes API
imagePullSecrets: ghcr-pullimage se stahuje ze soukromého registru
requests/limitsbez swapu je limit paměti nutnost
readiness/liveness na /healthzKubernetes pozná, že web žije
rolling update s maxUnavailable: 0nová verze bez výpadku

Úprava obsahu

Obsah se upravuje v docs-site/site/content/{en,cs}/ a nasazuje přes git push, viz CI/CD a GitOps. Lokální náhled:

shell @ mac
cd ~/Projects/homelab/docs-site/site && hugo server

Přístup