Meine Speaker-Seite lebt jetzt komplett in der Oracle Cloud. Kein Webserver, kein VM, keine Nginx-Config, keine systemd-Units. Nur Jekyll-Output in einem Object-Storage-Bucket und ein API Gateway davor. Dieser Post beschreibt die Architektur, die Fallstricke und warum es nicht der Weg ist, den ich zuerst eingeschlagen habe.
Browser (robbie.databee.org)
│ CNAME auf OCI-Gateway-Hostname
▼
OCI API Gateway (public, eu-frankfurt-1)
TLS-Terminierung, Path-Rewrites
│
├─ / → bucket/o/index.html
├─ /{path*} → bucket/o/${path}
└─ /files/* → assets-bucket/o/${path}
│
▼
OCI Object Storage
robbie-databee-org (Website-HTML, 2500+ Objekte)
robbie-talks-assets (Slides, Demos, Video-Clips)
Zwei Buckets, ein Gateway, kein Compute. Betriebskosten für das Traffic-Niveau einer Speaker-Seite: im Bereich von Cent pro Monat.
Das war mein erster Reflex und der erste Sackgassen-Moment. OCI
Load Balancer kann Redirects und Path-Based-Routing auf verschiedene
Backend-Sets — aber kein transparentes URL-Rewriting. Für
Object-Storage-Hosting brauchst du genau das, weil die Web-URL
/about/ auf den Bucket-Key about/index.html abgebildet werden
muss. Nicht als 3xx-Redirect an den Browser, sondern intern.
Oracles eigener Blog empfiehlt für Object-Storage-fronted Static Sites API Gateway oder einen eigenen Webserver, nicht LB. Ich habe den Reflex mit zwei Stunden VCN-Konfiguration bestraft, bevor ich die Doku genauer gelesen habe. Lektion: auf das Werkzeug hören, das die Plattform selbst für das Problem vorsieht.
Damit /foo, /foo/ und /foo/index.html alle funktionieren, ohne
dass API Gateway String-Manipulation im Request-Path macht (was es
nicht kann), lade ich jede Seite beim Deploy dreifach hoch: als
foo/index.html (kanonisch), als foo/ (trailing-slash-Alias) und
als foo (bare-Alias). Alle drei Objekte zeigen auf dieselbe HTML-
Datei.
Storage ist billig genug, dass die Triple-Aufbewahrung nicht schmerzt. Für 120 Talks + diverse Pages sind das ~2500 Objekte. Der Deploy-Helper in Python macht das parallel über das OCI-SDK:
def iter_uploads(site_dir):
for root, _, files in os.walk(site_dir):
for name in files:
local = Path(root) / name
rel = Path(root).relative_to(site_dir) / name
yield (str(local), rel.as_posix())
if name == "index.html":
rel_dir = Path(root).relative_to(site_dir).as_posix()
if rel_dir and rel_dir != ".":
yield (str(local), f"{rel_dir}/")
yield (str(local), rel_dir)
oci os object bulk-upload ist schnell und bequem, aber es setzt
Content-Type nicht aus der Dateiendung. Alle Objekte bekommen
application/octet-stream. Browser interpretieren das als
“unbekannter Binärblob” und bieten Download statt Render. CSS lädt
nicht, JavaScript wird nicht ausgeführt, und Chrome zeigt dir einen
freundlichen Download-Dialog für eine Datei namens “Download”.
Lösung: im Deploy-Helper explizit Content-Type aus der Dateiendung
setzen. Python macht das mit einem kleinen Mapping .css →
text/css, .js → text/javascript, .html → text/html; charset=utf-8
— und Object Storage speichert das als Object-Metadata, das der
Gateway an den Client durchreicht.
Self-signed Cert zum Starten, Let’s Encrypt sobald DNS zeigt. Weil
die DNS bei Strato liegt (kein OCI-DNS, also kein automatisches
ACME via OCI Certificate Service), läuft die Erst-Ausstellung mit
certbot --manual --preferred-challenges dns und einem einmaligen
TXT-Record beim Registrar.
Spannender Teil: wie das Cert in API Gateway reinkommt. Erste Runde
habe ich oci api-gateway certificate create benutzt — funktioniert,
aber das Service-Limit für API-Gateway-Certificates ist 1 pro
Region, und nach einem Delete ist der Zähler eventually-consistent.
Zehn Minuten Warten, dann ging Create erst wieder. Das ist kein
Fehler, sondern Design — und ein Problem, wenn du mal rotieren
willst ohne Downtime.
Der Ausweg: OCI Certificate Service (certs-mgmt) hat ein
anderes Quota, und der Gateway akzeptiert dessen OCIDs als
certificate-id. Zwei Resource-Typen, beide verwendbar, nur das
Certs-Mgmt-Quota ist weniger eng.
Dritte Lektion gratis: OCI API Gateway schickt das Intermediate-
Cert nicht automatisch mit, auch wenn du es beim
create-by-importing-config als cert-chain-pem übergibst. Der
Gateway braucht zusätzlich ein CA-Bundle, das du separat als
OCI-Certs-Mgmt-Resource anlegst und per --ca-bundles an den
Gateway hängst. Ohne das sagt Chrome zwar “Zertifikat gültig”, aber
trotzdem “Verbindung nicht sicher” — die Kette ist nicht komplett.
Die alte Seite hatte 62 Talks in einem verschachtelten Ordnerbaum
mit Leerzeichen und Umlauten in den Verzeichnis-Namen. Die neue
Struktur ist flach: _talks/YYYY-MM-DD-slug.md. Ein Python-Skript
hat die Migration gemacht — inklusive redirect_from: in jeder
Datei, damit die alten Post-Style-URLs (/talks/2021/01/27/slug/)
per jekyll-redirect-from 301 auf das neue Schema zeigen. SEO-
Juice bleibt erhalten.
Lücken-Schließung für 2021–2026: Oracle ACE bietet einen HTML-Export seiner Contributions-Liste an. Ein weiteres Python-Script parst den Export, dedupliziert nach Datum + normalisiertem Titel gegen die bestehenden Markdown-Dateien, und schreibt neue Entries für alles, was fehlt. 58 Vorträge auf einen Schlag nachgeholt, 25 falsch-erkannte Sprachen via Heuristik-Korrektur gefixt.
Jeder Vortrag hat ein status-Feld:
proposed → Abstract eingereicht
accepted → Zusage da, Upcoming-Widget zeigt ihn
scheduled → Termin fix, Slides-Arbeit läuft
delivered → gehalten, Recording verlinkt
archived → komplett dokumentiert
Die Landing-Page hat zwei Tiles: “Triff mich” zeigt die nächsten
accepted/scheduled-Talks, “Vergangene Vorträge” zeigt Zähler +
letzten Eintrag. Auf /talks/ kommen die Upcoming oben, darunter
Jahres-Archiv. Ein future: true im _config.yml — sonst baut
Jekyll die Zukunfts-Talks gar nicht erst.
GA war der einzige Grund, einen Consent-Banner zu brauchen. OCI API Gateway schreibt Access-Logs, und über Service Connector Hub landen die in einem Object-Storage-Bucket. Mit GoAccess darüber fällt ein HTML-Dashboard raus: Seitenaufrufe, Referrer, User-Agents, Top-Pfade, 404s. Keine Cookies, kein JavaScript, kein Consent. GA raus, Seite wieder DSGVO-sauber.
Summa summarum: unter 50 Cent pro Monat. Und der Stack ist vollständig in der OCI — für einen Oracle-Speaker ist das ein hübscher “eat your own dogfood”-Punkt auf der Speaker-Page selbst.
Den Deploy-Workflow dokumentiert ein dediziertes README im Repo. Wer das Muster selber fahren will: das Template ist auf GitLab und funktioniert mit wenig Anpassung für jede Jekyll-basierte Speaker-Seite.