Where it runs and why

Deployment

Where it runs, why there, and what a missing setting costs.

Where it actually runs

Three hosts, and a fourth that only watches

This is the shape of the whole project, and it is worth knowing because it explains most of the failures this deployment has had. Every one of them was a piece of this diagram answering a different question than the one being asked — a build that succeeded while the site served an older commit, a checkout where the database is deliberately absent, a project left pointing at the source directory instead of the built one.

Render

the bot

One process runs the bot's gateway and the console's web server together, because they share the database and the live client. Its free tier sleeps when nobody knocks — which is why the pages a stranger reads first do not live here.

discord.py gateway Flask console scheduled-message loop SQLite

Cloudflare Pages

the public pages

A CDN with no bandwidth ceiling, so a link that does well costs nothing. It serves the marketing site and this hub, and the live numbers on both come from the same public endpoint on the bot's own host — a page served from a cache still has to ask for its figures.

static build CSP + HSTS no bandwidth cap this hub

GitHub

the source

Where the code lives and where each host's build is triggered from. The repository is private and this hub deliberately links to nothing inside it: a public page pointing at a private path is a 404 with extra steps.

private repository triggers both builds

UptimeRobot

the watcher

An outside process checking the host on a schedule, which is the only reading here that comes from neither of the other two. Its monitors are read by the background loop and cached, so this page can be left open all day without ever costing it a request.

monitors 30-day ratio outside both hosts

Two decisions, written down

Why the pages live where they live

These were not defaults. Both are the kind of choice that is cheap now and expensive to reverse, so they are recorded here rather than left in a commit message.

Static pages belong on a CDN with no bandwidth cap

unlimited egress

The front door was served by the same process that runs the bot, and that process is on a free tier that sleeps — so the page a stranger reads before deciding to trust this thing was shut whenever nobody had knocked recently. Free and always reachable are the whole requirement, and the field is narrower than it looks:

HostFree bandwidthVerdict
Cloudflare Pages unlimited chosen — no ceiling in either direction
Netlify ~100 GB / month free until it isn't
Vercel ~100 GB / month free until it isn't
GitHub Pages ~100 GB / month not available on this plan

Only one of those has no ceiling at all, in either direction. The other two are free until a link does well, and then they are a bill — which is the one thing "free forever" cannot survive.

The hub reads; the console writes

read-only

A public host must not hold a credential that can change a server's configuration, so this site cannot. It is built from the bot's own registry, its own changelog and its own public readings, and its one network call is a merge of two endpoints that are already public and already answer with counts instead of names.

Everything that writes — switches, the composer, scheduled messages, the per-server identity — hands off to the console on the bot's own process, which asks who you are first and scopes every answer to the servers you actually administer.

Settings

53 settings, and what a missing one costs

Two kinds, and the difference matters: runtime settings are read by the running bot, and build settings are read by whichever process renders these pages. A build setting that is absent produces a page missing something — and no error anywhere, because the page has no process behind it to raise. That is exactly how the front door shipped without its invite button.

not read from here — this page was built on a machine that is not the deployment, so it asks the bot's own host instead

settings this deployment has not been given

Of 53 the code knows about. A default is used for each one, and most defaults are perfectly fine.

of those would stop something working

Essential, security or capability settings — the absences an operator can act on. The rest are tuning, and leaving them alone is a decision.

Runtime

not read here

Read by the running bot. Absent here means a feature is limited.

present

Build

not read here

Read by whatever renders these pages. Absent here means a page quietly loses something and nothing raises.

present
What each kind of absence costs, and how many this deployment has.
Kind What it means Missing Of
Essential A feature cannot work at all without it. 1
Security Something is reachable that should not be. 4
Capability A feature works, but with less than it could do. 13
Tuning A default is in use. Often fine. 31
Host The platform provides it; absent means the host is not set up. 4
No value on this page, and no name either The hub reports how many settings are missing and never which ones. The names are part of a deployment's private surface — a missing sign-in secret is a fact about where it is weak — so they are answered, in full, to a signed-in administrator on the console.
by design

The pipeline

How a change reaches production

A change is committed On main, in the private repository.
local
The suite runs The whole suite, plus the registry and policy guards. A failure here is the only thing that stops a change from reaching either host.
tests
The pages are rendered The real app renders each page, every root-relative URL is rewritten, and the builder refuses to emit a link that would 404 on a static host.
build
Both hosts pick it up Render redeploys the bot from the same commit; Cloudflare rebuilds the pages. Neither is a claim that the other landed, which is why the deploy step asks each host which commit it is serving.
deploy
An outside check confirms it A scheduled job asks the published site for files that must never be readable there — a source file, a config file, an env file — and alerts if any of them answers at all.
after