Files
meshcore-hub/docs/deployment.md
Louis King 3593d14878 feat(api): make Redis a required cache dependency
Redis becomes mandatory infrastructure (PostgreSQL-parity): the
REDIS_ENABLED flag, NullCache backend, and every conditional caching
code path are removed. /health/ready reports not_ready (503) when
Redis is unreachable and cached endpoints return 503 instead of
silently degrading; invalidation helpers never raise after a
committed write.

The bundled redis container joins the core profile only via
docker-compose.dev.yml (production points REDIS_* at an external
instance, same no-shadowing pattern as postgres), the backend suite
runs against real Redis (test-redis on 127.0.0.1:55433, CI redis:8
service container, per-xdist-worker key prefixes), and operator docs
describe the required-dependency setup (upgrading.md gains the
v0.20.0 section).
2026-08-30 21:51:51 +01:00

115 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment
This document covers production deployments of MeshCore Hub: reverse-proxy setup, multi-instance routing, API scaling, and the optional Redis response cache. For the full environment-variable reference, see [configuration.md](configuration.md).
## Production Setup
For production deployments, use `docker-compose.prod.yml` which connects services to an external proxy network. No ports are exposed directly — all traffic goes through your reverse proxy.
**Prerequisites:**
1. A reverse proxy (Nginx Proxy Manager, Caddy, Traefik, etc.)
2. Docker network for proxy communication
**Steps:**
```bash
# Create proxy network (once)
docker network create proxy-net
# Download compose files and config
mkdir meshcore-hub && cd meshcore-hub
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/docker-compose.yml
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/docker-compose.prod.yml
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/.env.example
cp .env.example .env
# Edit .env: set COMPOSE_PROJECT_NAME, MQTT credentials, API keys, etc.
# Start core services
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile core up -d
# Or include local MQTT broker
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile mqtt --profile core up -d
# Or include packet capture on the same host
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile mqtt --profile core --profile observer up -d
```
> **Database in production:** the bundled `postgres` container is **not** started by the `core` profile with `docker-compose.prod.yml` — its `postgres` network alias would shadow a shared instance of the same name on the same network. Point the `DATABASE_*` variables at your shared/managed Postgres (see [database.md](database.md)). If this host has no external database, opt in to the bundled container explicitly with `--profile postgres` and set a strong `DATABASE_PASSWORD`.
Configure your reverse proxy to forward to the containers:
| Service | Container | Port | Path |
| -------------- | ----------------------------- | ---- | -------------------------------- |
| Web Dashboard | `{COMPOSE_PROJECT_NAME}-web` | 8080 | `/` |
| API | `{COMPOSE_PROJECT_NAME}-api` | 8000 | `/api`, `/metrics`, `/health` |
| MQTT WebSocket | `{COMPOSE_PROJECT_NAME}-mqtt` | 1883 | `/` (only if using local broker) |
> **Important:** Do not host under a subpath (e.g., `/meshcore`). Proxy at `/`.
### Reverse Proxy
MeshCore Hub is designed to run behind a reverse proxy in production. A Traefik override file is provided with pre-configured labels:
```bash
# Download the Traefik override
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/docker-compose.traefik.yml
# Set your domain in .env
echo "TRAEFIK_DOMAIN=meshcore.example.com" >> .env
# Start with Traefik labels
docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.traefik.yml --profile core up -d
```
This routes the web dashboard and API to `TRAEFIK_DOMAIN` with automatic TLS.
### Multi-Instance Deployments
To run multiple Hub instances (e.g., production + staging) on the same Docker host, set `TRAEFIK_PRIORITY` to control which router wins when domains overlap. Higher values are matched first:
```bash
# Production (.env)
COMPOSE_PROJECT_NAME=hub
TRAEFIK_DOMAIN=example.com
TRAEFIK_PRIORITY=10
# Staging (.env) — separate directory with its own config
COMPOSE_PROJECT_NAME=hub-beta
TRAEFIK_DOMAIN=beta.example.com
TRAEFIK_PRIORITY=20
```
This ensures `beta.example.com` (priority 20) is matched before the production wildcard `*.example.com` (priority 10). For other services on the same network (e.g., an MQTT broker at `mqtt.example.com`), use an even higher priority (e.g., 30).
> **Shared Postgres cluster:** the setup above runs each instance in its own directory with its own volumes — add `--profile postgres` to each instance's `up` command for a bundled Postgres container per instance. To instead run several instances (e.g. `prod` + `stg`) against **one** PostgreSQL cluster — isolated via a per-instance schema (`search_path`) — see [database.md](database.md#schema-per-instance-search_path).
## Scaling the API
The API is read-mostly and holds no per-process state — the response cache lives in Redis and authentication is stateless — so it scales across multiple worker processes. Set `API_WORKERS` to run more than one worker in a single container:
```bash
# .env
API_WORKERS=4
```
Each worker is an independent process sharing one listening socket, so the kernel balances connections across them and CPU-bound work (JSON serialisation, validation) spreads over multiple cores. Workers read their configuration from **environment variables** (CLI flags are not propagated to forked workers), which is how Docker Compose already supplies config. Redis (required infrastructure) gives all workers one shared response cache.
Pick a worker count around the number of CPU cores available to the container; start with `2`–`4` and measure under realistic load. Workers all talk to the same PostgreSQL database (the bundled container or an external instance — see [database.md](database.md)), so scaling extends across hosts unchanged.
> Prefer `API_WORKERS` over running multiple `api` containers (`--scale api=N`): the `api` service uses a fixed `container_name`, and one process-managed container per stack keeps logs, health checks, and monitoring simple.
## Redis Caching
Redis-backed caching for API responses — **required infrastructure** (since v0.20). An unreachable Redis fails readiness (`/health/ready` → 503) and cached endpoints return `503 cache backend unavailable`; it is not an optional accelerator.
**Docker (dev):** `docker-compose.dev.yml` unions the bundled `redis` service into the `core` profile, so the standard dev workflow starts it automatically. Start it standalone with the explicit `cache` profile (or `all`).
**Docker (production):** `docker-compose.prod.yml` deliberately does **not** start the bundled Redis with `core` — point `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` at your shared/external instance (starting the bundled container alongside it would shadow a `redis` host on the same network), or opt in explicitly with `--profile cache` / `--profile all`.
**Bare-metal:** Install and run a Redis server (the `localhost` defaults point at a local `redis-server`).
**Multi-instance:** Use different `REDIS_KEY_PREFIX` values per instance to share one Redis without key collisions.
For the full list of `REDIS_*` environment variables, see [configuration.md → Caching](configuration.md#caching).