* docs: move design and API contract into docs/ * docs: add releases and versioning page * docs: add configuration reference for server and web * docs: add reverse proxy page * docs: add upgrading page * docs: add operations page and move backup and profiling docs * docs: add getting packets in and security pages * docs: add troubleshooting page * docs(api): fold admin, activity, route and summary notes into the contract * docs(deploy): add type 2 split deployment, finish the beacon rename, ignore secrets * docs: rewrite README as the front door and share the contributor workflow * docs: remove em dashes from prose and config comments * docs: apply review fixes to type 2, upgrading, troubleshooting and first-run notes * docs: fix upgrade order, restore steps, type 2 setup and contract details from review * docs: keep the gitignore to deployment secrets and runtime state
4.9 KiB
Day-to-day operations
Everything here assumes the all-in-one Docker stack. The commands translate to the split deployment by running them on the server host.
Checking it is healthy
Beacon has no health endpoint yet. Use these instead:
docker compose psshows every containerUp.dbandredishave health checks;appwaits for both before it starts.curl -s https://<your-domain>/api/v1/brokerslists each MQTT broker with its connection status. A broker that never connects is a URL or credentials problem; see Getting packets in.- Open
wss://<your-domain>/wsin a WebSocket client. The first frame ishello. docker compose logs -f appshows aconnectedline per broker. Individual packets are not logged at the defaultinfolevel, so a quiet log after that is normal; use the observers endpoint or the web app to see traffic.
Logs
Logs go to stderr and Docker collects them. Docker's default json-file driver does not
rotate, so on a long-running host set a cap, either in /etc/docker/daemon.json for every
container or per service in docker-compose.yml:
logging:
driver: json-file
options: { max-size: "50m", max-file: "5" }
Set log.level (debug, info,
warn, error; default info) and log.format (text or json) in config.yaml, or
override them with LOG_LEVEL and LOG_FORMAT. Invalid values stop startup.
Every record has a component. Ingest records add the broker name; HTTP completion records
add the validated client address, route, status and duration. Query strings and WebSocket
hello payloads are never logged. Expected ingest skips and routine WebSocket lifecycle events
are at debug level.
docker compose logs -f app # the API and ingest
docker compose logs -f caddy # the proxy
Caddy also writes a JSON access log to data/Caddy/logs/access.json, which the fail2ban rules
in app_config/fail2ban/ read.
Where the data lives
Everything persistent is under data/ next to docker-compose.yml:
data/postgres/is the database. Deleting it deletes all history.data/redis/is the cache. It can be removed while the stack is down; Beacon rebuilds it.data/Caddy/data/holds TLS certificates. Deleting it means a new certificate request on the next start, which counts against Let's Encrypt rate limits.data/app/config.yamland.envare your configuration. Keep copies of both somewhere else; no backup below includes.env.
Retention
Everything below is a duration in config.yaml. Shorten them if disk is tight, lengthen them
if you want more history.
| Setting | Default | What it covers |
|---|---|---|
packets.retention |
168h (7 days) |
Packets, observations and channel messages |
analytics.rollup_retention |
2160h (90 days) |
Hourly stats rollups. Also the longest window the stats endpoints accept |
telemetry.retention |
744h (31 days) |
Observer telemetry snapshots |
routes.retention |
336h (14 days) |
Known routes, counted from when a route was last seen |
nodes.delete_after |
720h (30 days) |
Nodes not heard from |
observers.delete_after |
off | Observers not heard from. Opt in by setting a duration |
Historical stats read the hourly rollups, not raw packets. Each UTC hour is rolled about 95
minutes after it closes, and packet cleanup holds back up to 24 hours of raw rows until their
hours are rolled, so raw data lives a little longer than packets.retention says. An hour
whose raw rows were deleted first is reported as partial, with no values.
Backing up
Two options.
A plain pg_dump from the host, which is enough for most operators:
docker compose exec -T db pg_dump -U beacon -d beacon -Fc > beacon-$(date +%F).dump
Keep data/app/config.yaml and .env with it. The dump holds message data and anything you
have decrypted, so treat it as private.
The admin backup endpoint produces an archive of the database and the saved
config.yaml, with a manifest and checksums, and a command-line tool that verifies it. It is
off by default and needs the admin key. See Backup and export.
Restoring
Restore into an empty database, never over live data. With the stack running and the app stopped, drop and recreate the database, load the dump, and only then start the app:
docker compose stop app
docker compose exec -T db psql -U beacon -d postgres -c 'DROP DATABASE beacon;' -c 'CREATE DATABASE beacon;' \
&& docker compose exec -T db pg_restore -U beacon -d beacon < beacon-YYYY-MM-DD.dump \
&& docker compose start app
If pg_restore reports errors the app is not started; read them before trying again.
A 1.x dump cannot be restored into 2.0.0; the server refuses the old schema. See Upgrading.
CPU profiling
Bounded CPU captures on a running instance are described in Profiling.