Beacon watches a MeshCore mesh from above. It listens to the MQTT brokers that observers publish into, decodes every LoRa packet it hears, stores it, and shows the network live: packets, channels, nodes, observers, routes, traces and stats, on a map and in lists.
This repo is where you deploy and operate it. The code lives in beacon-server (the API and ingest) and beacon-web (the frontend).
Coming from 1.x? 2.0.0 needs a fresh database and a few config changes. Read Upgrading before you pull new images.
One server runs everything: the API, Postgres, Redis, the web frontend and Caddy with automatic HTTPS. You need a domain pointed at the server, ports 80 and 443 open, Docker with the compose plugin, and a subscriber account on a MeshCore MQTT broker (where packets come from).
If you would rather run the API and the web frontend on different hosts, see Split deployment.
Clone the repo; you only need the deployment folder, but cloning is the simplest way to get it.
git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1The folder is self-contained:
docker-deployment-type1/
├── docker-compose.yml # the stack (pins the network to 172.30.0.0/24)
├── .env # your secrets (you create this in step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon config: regions, channels, retention, proxy trust
├── Caddy/CaddyFile/Caddyfile.proxy
├── Caddy/conf.d/blocklist.caddy # manual IP and User-Agent block list
├── Caddy/logs/ # JSON access log (read by fail2ban, created on first run)
├── postgres/ # Postgres database files (created on first run)
└── redis/ # Redis data (created on first run)
data/postgres/ and data/redis/ are bind mounts, so your database and cache survive
docker compose down and restarts. Do not delete data/postgres/ unless you mean to wipe all
data. A data/postgres/ left over from Beacon 1.x must be moved away first; 2.0.0 will not
start on a 1.x database (see Upgrading).
cp ../app_config/.env.example .env
nano .envSet every CHANGE_* value. The ones you cannot skip:
| Variable | What to set |
|---|---|
POSTGRES_DSN |
The database connection string. Change CHANGE_DB_PASS to a strong password. The same password must go in POSTGRES_PASSWORD in docker-compose.yml. |
MQTT_BROKER_1_URL, _USERNAME, _PASSWORD |
Your MeshCore MQTT broker and the subscriber account on it. The template also lists a second broker; if you have only one, clear MQTT_BROKER_2_URL (an empty URL disables that worker). |
DOMAIN |
Your public hostname, for example beacon.example.com. Caddy requests a Let's Encrypt certificate for it. |
VITE_API_BASE |
https://<your-domain>/api/v1. The browser calls this, so it must be the public domain, never localhost. |
VITE_WS_URL |
wss://<your-domain>/ws |
Everything else in the file is optional and explained in Configuration: the admin API key, log settings, Redis, and the web app's map view, tabs, themes, name and banner.
nano data/app/config.yamliatas: the airport codes your observers report under, with a name and coordinates.regions: groups of IATAs that become the region picker in the web app.channel_keys: hashtag channels and explicit keys to decrypt. Without them, channel messages are stored as hashes only.scopes: transport scopes, if your mesh uses them. Each one needs aregion.packets,telemetry,analytics,routes: how long things are kept. Packets default to 7 days.ingest.allow_countriesis set to[CA], so packets from observers outside Canada are dropped. Change it to your country, or delete theingestblock to accept everything.server.trusted_proxiesis already set to the compose subnet so Beacon sees real visitor addresses through Caddy. Change it only if you change the subnet indocker-compose.yml.
Every other key, with its default, is documented in
app_config/config.yaml.example; the
Configuration page is the map of what each block does.
Create an A or AAAA record for DOMAIN pointing at the server's public IP. Caddy needs
ports 80 and 443 reachable from the internet to get its certificate.
docker compose up -d
docker compose ps
docker compose logs -f appWithin a minute app should log a connected line for each broker. Packets themselves are
not logged at the default level; open https://<your-domain> and watch the Packets tab, or
check https://<your-domain>/api/v1/observers fills in. If something is off, Troubleshooting
lists the usual suspects.
To change a VITE_* value later, edit .env and run docker compose up -d web. The web
container writes those values to /config.js on every start, so visitors get them on their
next page load.
API, database and ingest on one host, the web frontend on another: docker-deployment-type2.
The app and web services pull public images from GitHub Container Registry
(ghcr.io/meshcore-beacon/beacon-server and ghcr.io/meshcore-beacon/beacon-web). No
docker login is needed.
The compose files use latest, which follows stable releases. To decide for yourself when to
upgrade, pin both images to the same release line (:2.0) or an exact release (:2.0.0); server
and web share major.minor. Tags, versioning and the release process are in
Releases and versioning. A 403 Forbidden on pull means the package has
gone private; see Troubleshooting.
Run it
- Getting packets in: brokers, the account Beacon needs, topics.
- Configuration: every environment variable and a map of
config.yaml. - Reverse proxy: Caddy, nginx, Apache, rate limits, split origins.
- Securing a deployment: the admin key, the listener, origins, bans.
- Day-to-day operations: health, logs, retention, backup and restore.
- Upgrading: 2.0.0 from 1.x, and routine upgrades.
- Troubleshooting: symptom, cause, fix.
- Releases and versioning: image tags and how releases are cut.
Understand it
- High level design: how it works, the schema, the ingest pipeline.
- API contract: REST, WebSocket, admin endpoints, errors.
- Backup and export and CPU profiling.
Change it
- Contributing: the workflow for all three repos and running the full stack locally.
- beacon-server: historical stats, how the hourly rollups work.
- beacon-web: translations, adding a language.
Deployment folders are docker-deployment-type1/ and docker-deployment-type2/; templates
and proxy configs (.env, config.yaml, Caddy, nginx, Apache, fail2ban) are in app_config/;
brand assets are in logos/.
- beacon-server (API and ingest): github.com/MeshCore-Beacon/beacon-server, image
ghcr.io/meshcore-beacon/beacon-server - beacon-web (frontend): github.com/MeshCore-Beacon/beacon-web, image
ghcr.io/meshcore-beacon/beacon-web
Container images are published automatically by each repo's docker-publish.yml workflow on
pushes to main and dev and on v* tags. GHCR packages are private by default, which makes
anonymous docker pull fail with 403 Forbidden. To make a package publicly pullable, once per
package:
- GitHub, the MeshCore-Beacon org, Packages, select
beacon-server. - Package settings, Danger Zone, Change visibility, Public.
- Repeat for
beacon-web.
Once a package is public, every future push keeps it public. GITHUB_TOKEN cannot change
package visibility, so this step cannot be automated in the workflow.
Contributions are welcome. CONTRIBUTING.md has the workflow and the Code of Conduct applies. Issues are disabled on this repo; to discuss a change first, reach out on the MeshCore Canada Discord. Security reports go through SECURITY.md, not public channels.
Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server and beacon-web.