Maintainer · git push · PRs
What it does
Pushes to master and opens PRs. CI runs on every PR. Eligible patch and minor dependency updates can merge automatically after required checks pass; major updates need review.
Why it's built this way
Changes go through CI. Routine dependency updates can be automated while major updates remain reviewable.
GitHub repo · master · web_events.json
What it does
Holds the code and the data file. The scraper commits web_events.json straight to master, and any push to master can start a deploy.
Why it's built this way
One home for the code and the data. Publishing is a plain git commit, so every update is versioned and can be rolled back.
GitHub Actions · buildx · Trivy · attest
What it does
Lints, type-checks and tests every PR. On master it detects which images changed, builds and pushes them with an SBOM and provenance, scans the published images with Trivy, then calls the deploy hooks.
Why it's built this way
Only changed images get rebuilt. Each one ships with an SBOM and provenance, and a vulnerability scan has to pass before deployment hooks run.
GHCR · backend · frontend images
What it does
Where the backend and frontend images live. The VPS pulls from here, and nothing is ever built on the server.
Why it's built this way
Images are built once, in CI, and pulled to the server. Nothing is compiled on the VPS.
Venue sites · HTML · JSON · iCal
What it does
The venues' own websites, ticket APIs and calendars. Each one has a small parser of its own under sources/.
Why it's built this way
Every venue gets its own small parser, so one site changing its markup never touches the others.
Backend · Python 3.14 · httpx · uv
What it does
A Python container that sits idle. Coolify's scheduled task runs the scrape inside it: fetch, keep the next 14 days, export, back up, publish.
Why it's built this way
Stateless by design. All it produces is files. Production runs attempt a verified backup; a failed backup is reported even when fresh data is published.
Coolify · scheduler · deploy API
What it does
The deploy tool on the VPS. It owns the scrape schedule, takes the webhook from CI, pulls the new image and swaps the container once its health check passes.
Why it's built this way
A deploy is a webhook plus a health-checked container swap, so a new version only takes over once it's healthy.
Object storage · S3 API · write-once
What it does
S3-compatible storage. Each production run's output goes in as a write-once snapshot with a sha256 manifest.
Why it's built this way
The backup writer does not overwrite existing snapshots, and sha256 checks detect changes to their contents.
Umami · analytics · Postgres
What it does
Self-hosted analytics with its own Postgres. The browser never talks to it directly; nginx forwards /s/ for it.
Why it's built this way
Analytics stay first-party. nginx proxies the tracker, so visitors never make a request to a third party.
Frontend · nginx :8080 · Astro static
What it does
nginx serving the built Astro site on :8080. Static files, a /health endpoint, and the /s/ forwarding for analytics.
Why it's built this way
Plain static files behind nginx, with a /health endpoint. Quick to serve and cheap to run.
Caddy · reverse proxy · TLS
What it does
The reverse proxy Coolify sets up. It handles TLS at the origin and routes each hostname to its container.
Why it's built this way
TLS and routing come from the platform's labels, so there's no hand-written proxy config to maintain.
Cloudflare · DNS · TLS · cache
What it does
DNS, TLS and caching in front of the origin. You don't need it: a fork can point DNS straight at the VPS.
Why it's built this way
An optional layer for DNS, TLS and caching. A fork can skip it and point DNS straight at the VPS.
Visitors · browsers
What it does
Browsers. The pages are plain static HTML, with no account and no tracking cookies.
Why it's built this way
Just HTML. No account to create and no tracking cookies.
Data update
- Coolify's scheduled task runs python -m boringhannover.main inside the backend container.
- Sources run in sequence with a pause between them. The aggregator keeps the next 14 days; the exporter writes the JSON web snapshot, occasion programmes and weekly archive.
- On production runs the output is copied to storage outside the server, write-once and checked against a sha256. Local runs skip it.
- Publishes changed occasion programmes first and the web_events.json manifest last through the GitHub Contents API. Unchanged files are skipped.
Deploy
- Any push to master starts a deploy, including the scraper's own data commit (run step 4).
- The Deploy workflow checks whether backend or frontend files changed and builds only what did.
- buildx builds and pushes images with an SBOM and provenance. Trivy scans the published images before deployment hooks can run. The image digests go into the run summary.
- One authenticated POST per changed image.
- The VPS pulls the image. Nothing gets built on it.
- The new container has to pass /health before the old one is removed.
Visitor request
- Cloudflare terminates TLS and serves cached files where it can.
- Proxied to the VPS on 443.
- Caddy matches the hostname to the frontend container by its labels. nginx serves files that were built ahead of time.
- The analytics beacon goes to the frontend's own /s/ path and nginx forwards it to Umami, so the browser never makes a third-party request.