beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

beacon-docs

Documentation, architecture, and ready-to-run Docker deployments for MeshCore Beacon.

This repo is the single place to:

  1. Grab a deployment — copy the Docker Compose folder for the topology you want, fill in your variables, and docker compose up -d.
  2. Read the docs — project-wide design and API documentation that describe how the whole system works.

Deploy Beacon

Two deployment topologies are provided. Pick one, copy its folder to your server, set your variables, and bring it up.

TypeFolderWhat it isStatus
Type 1 — All-in-Onedocker-deployment-type1/Full stack on a single server: API (app), Postgres (db), Redis (redis), web frontend (web), and Caddy reverse proxy with automatic TLS.✅ Ready
Type 2 — Split Server + Webdocker-deployment-type2/API backend on one server, web frontend on a dedicated server.🚧 Not done yet (WIP)

Step-by-step (Type 1)

1. Get the files onto your server

Clone the repo (you only need the deployment folder, but cloning is the simplest way to grab it):

git clone https://github.com/MeshCore-Beacon/beacon-docs.git
cd beacon-docs/docker-deployment-type1

The folder is self-contained:

docker-deployment-type1/
├── docker-compose.yml # the stack
├── .env # your secrets (you create this — see step 2)
└── data/ # persistent state, bind-mounted into the containers
├── app/config.yaml # Beacon app config (regions, channels, retention)
├── Caddy/CaddyFile/Caddyfile.proxy
├── postgres/ # ← Postgres database files live here (created on first run)
└── redis/ # ← Redis data lives here (created on first run)

data/postgres/ and data/redis/ are empty in git on purpose. They're bind-mount targets: the db and redis containers write their data into them, so your database and cache survive docker compose down and restarts. Don't delete them unless you intend to wipe all data — rm -rf data/postgres resets the database. They populate automatically the first time you run docker compose up -d.

2. Create and fill in your .env

Copy the template from app_config/.env.example and edit it:

cp ../app_config/.env.example .env
nano .env

Set every CHANGE_* value. The variables you must fill in:

VariableServiceWhat to set
POSTGRES_DSNappDatabase connection string. Change the password (CHANGE_DB_PASS) to a strong one.
REDIS_ADDRappredis:6379 — points the API at the compose Redis service. Leave it out and the server runs uncached, so every read hits Postgres.
MQTT_BROKER_1_* / MQTT_BROKER_2_*appURL, username, and password for your live MeshCore MQTT packet sources.
DOMAINcaddyYour public domain (e.g. beacon.example.com). Caddy auto-provisions a Let's Encrypt cert for it.
VITE_API_BASEwebhttps://<your-domain>/api/v1 — must be the public domain, never localhost.
VITE_WS_URLwebwss://<your-domain>/ws
VITE_MAP_CENTER / VITE_MAP_ZOOMweb(Optional) Fallback "All" map view. The app auto-fits the map to all IATA locations from config.yaml; these values are only used as a fallback when those IATAs have no location set. Omit for a world view.

⚠️Password must match in two places. The password inside POSTGRES_DSN (in .env) must equal POSTGRES_PASSWORD in docker-compose.yml. Update both before bringing the stack up.

3. Fill in the app config

Edit data/app/config.yaml to define your network:

nano data/app/config.yaml
  • iatas — the airport-code anchor points for your coverage area (name + lat/lng).
  • regions — map regions that group IATAs together.
  • channel_keys — hashtag channels and/or explicit channel keys to decrypt.
  • scopes, telemetry, packets, ingest — transport scopes, retention windows, and an optional geographic ingest filter.

4. Point DNS at the server

Create an A/AAAA record for your DOMAIN pointing at the server's public IP. Caddy needs ports 80 and 443 reachable to issue the TLS certificate.

5. Bring it up

docker compose up -d

Check it's healthy:

docker compose ps
docker compose logs -f

Visit https://<your-domain> and you're off to the races. 🚀

After changing any VITE_* value later, recreate the web container so the new values get baked into the JS bundle:

docker compose up -d --force-recreate web

(then hard-refresh / use incognito, since /assets/* is cached immutable.)

Container images

The app and web services pull public images from GitHub Container Registry (ghcr.io/meshcore-beacon/beacon-server and …/beacon-web) — no docker login is required.

Troubleshooting — 403 Forbidden on pull. If docker compose up fails with a ... manifests/<tag>: 403 Forbidden error, the package has been set (or defaulted) to Private on GHCR. A maintainer must set it back to Public — see Maintainers: publishing images.

Type 2 — Split Server + Web

🚧 Not done yet.docker-deployment-type2/ is a placeholder; instructions and compose files will land here.


Project documentation

Project-wide docs that describe the entire system live in app_documentation/:

  • High Level Design — single source of truth. System overview, database schema, ingestion pipeline, and future features.
  • API Contract — REST endpoints, WebSocket protocol, search, backpressure/reconnection, and mobile-specific concerns.

Source repositories

Maintainers: publishing images

Container images are published automatically by each repo's docker-publish.yml workflow on pushes to main/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 (one-time, per package):

  1. GitHub → the MeshCore-Beacon org → Packages → select beacon-server.
  2. Package settingsDanger ZoneChange visibilityPublic.
  3. Repeat for beacon-web.

Once a package is Public, every future CI push to it stays Public. GITHUB_TOKEN cannot change package visibility, so this step can't be automated in the workflow.

Repo structure

  • /docker-deployment-type1 — Single-server (all-in-one) Docker Compose deployment ✅
  • /docker-deployment-type2 — Split server/web Docker Compose deployment 🚧 WIP
  • /app_config — Example .env, config.yaml, and Caddyfile templates
  • /app_documentation — Project-wide design & API docs
  • /logos — Brand assets

Contributing

Contributions are welcome — see CONTRIBUTING.md for the workflow and the Code of Conduct. 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.

License

Beacon is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as beacon-server.

About

Documentation, architecture decisions, and guides

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages