Beacon
Uptime monitoring for the containers I self-host
- Watches from outside the box
- No inbound ports, no manual DNS
- One docker compose up
I run a handful of Docker containers at home that are reachable from the internet. Beacon exists because I kept finding out one of them had fallen over by trying to use it.
The one rule
The monitor must not run on the same host as the things it watches. Its entire job is to notice when that host dies, which it cannot do from inside it. Obvious once stated, easy to get wrong when you are the person who owns both boxes and has a spare slot on one of them. Beacon runs somewhere else, deliberately, and everything about its packaging assumes that.
Getting out of the house safely
The services it watches reach the internet through a Cloudflare Tunnel — no inbound ports open on the home network, no port forwarding, no DNS records maintained by hand, no certificates to renew. The tunnel dials out; nothing dials in.
What it is
A Nuxt 4 app with an auth-protected dashboard: what is up, what is down, how long it has been
that way, and history per service. Checks and results live in Postgres via Drizzle, with
migrations applied on startup so a deploy is a restart. Auth is nuxt-auth-utils. The whole
thing is a single docker compose up on a host that needs Docker and nothing else — no Node, no
package manager; dependencies are installed and the app built inside the image.
Configuration is load-bearing
A note I left in the deployment guide for my future self, after it bit me twice: every
application variable must be NUXT_-prefixed. Nitro bakes runtimeConfig at build time and
re-reads it at runtime only from NUXT_*. Set ADMIN_PASSWORD_HASH instead of
NUXT_ADMIN_PASSWORD_HASH and sign-in returns a 503 while docker compose exec shows you a
perfectly correct-looking value in the environment. The container starts, the dashboard loads,
and the one thing you need it to do fails silently.
Small operational details like that are most of what running your own infrastructure actually is.