Files
nodeledge-mqtt-listener/docker-compose.yml
T
2026-09-03 19:36:02 +02:00

142 lines
5.1 KiB
YAML

# Nodeledge MQTT listener stack (Portainer / docker compose compatible).
#
# Services:
# db PostgreSQL holding the mqtt_messages table
# mqtt-listener subscribes to MQTT, injects IMEI + device into JSON payloads, writes rows
# api FastAPI read API over the database, protected by static app tokens
# broker OPTIONAL test broker (compose profile "test") for trying the stack out
#
# Portainer: Stacks -> Add stack -> paste this file, then set the environment
# variables (see .env.example) in the stack's "Environment variables" section.
# Locally: cp .env.example .env && docker compose --profile test up -d --build
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-nodeledge}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in the stack environment}
POSTGRES_DB: ${POSTGRES_DB:-nodeledge}
TZ: ${TZ:-Europe/Stockholm}
volumes:
- db_data:/var/lib/postgresql/data
# Creates the mqtt_messages table on the first database start only.
- ./init/01_schema.sql:/docker-entrypoint-initdb.d/01_schema.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 10s
mqtt-listener:
build:
context: .
dockerfile: listener/Dockerfile
init: true
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
MQTT_BROKER_HOST: ${MQTT_BROKER_HOST:-broker}
MQTT_BROKER_PORT: ${MQTT_BROKER_PORT:-1883}
MQTT_USERNAME: ${MQTT_USERNAME:-}
MQTT_PASSWORD: ${MQTT_PASSWORD:-}
MQTT_CLIENT_ID: ${MQTT_CLIENT_ID:-nodeledge-mqtt-listener}
# Matches topics on the form /<prefix>/<imei> (see README to widen it).
MQTT_TOPIC_FILTER: ${MQTT_TOPIC_FILTER:-/+/+}
MQTT_QOS: ${MQTT_QOS:-1}
DB_DSN: postgresql://${POSTGRES_USER:-nodeledge}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in the stack environment}@db:5432/${POSTGRES_DB:-nodeledge}
IMEI_MAP_FILE: /app/config/imei_map.json
INJECT_IMEI_KEY: ${INJECT_IMEI_KEY:-imei}
INJECT_DEVICE_KEY: ${INJECT_DEVICE_KEY:-device}
# Comma-separated payload keys searched for a device timestamp.
PAYLOAD_TIME_KEYS: ${PAYLOAD_TIME_KEYS:-timestamp,time,ts}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
TZ: ${TZ:-Europe/Stockholm}
volumes:
# IMEI -> device name mapping, see configs/listener/imei_map.json
- ./configs/listener:/app/config:ro
healthcheck:
test: ["CMD", "python", "/app/listener/healthcheck.py"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
api:
build:
context: .
dockerfile: api/Dockerfile
init: true
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_DSN: postgresql://${POSTGRES_USER:-nodeledge}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in the stack environment}@db:5432/${POSTGRES_DB:-nodeledge}
APP_TOKENS_FILE: /app/config/app_tokens.json
# Read/write location for the listener's IMEI map, shared via the
# ./configs/listener mount (the listener reads the same file)
IMEI_MAP_FILE: /app/config-listener/imei_map.json
API_MAX_LIMIT: ${API_MAX_LIMIT:-10000}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
TZ: ${TZ:-Europe/Stockholm}
volumes:
# app tokens for API auth + the listener's IMEI map; mounted rw so the
# /configs endpoints can update them (writes are atomic)
- ./configs/api:/app/config:rw
- ./configs/listener:/app/config-listener:rw
ports:
- "${API_HOST_PORT:-18080}:8000"
healthcheck:
test: ["CMD", "python", "/app/api/healthcheck.py"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
pgadmin:
image: dpage/pgadmin4:9.17
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-change_this_bad_password}
PGADMIN_DISABLE_POSTFIX: ${PGADMIN_DISABLE_POSTFIX}
PGADMIN_ENABLE_TLS: false
PGADMIN_LISTEN_ADDRESS: ${PGADMIN_LISTEN_ADDRESS}
PGADMIN_LISTEN_PORT: 80
TZ: ${TZ:-Europe/Stockholm}
ports:
- ${PGADMIN_HOST_PORT:-18081}:80
healthcheck:
test: ["CMD", "wget", "-O", "-", "http://localhost:80/misc/ping"]
interval: 10s
timeout: 10s
start_period: 160s
retries: 3
broker:
# Optional MQTT broker for local testing; excluded unless the "test"
# profile is active (docker compose --profile test ...). Point
# MQTT_BROKER_HOST at your real broker in production.
image: eclipse-mosquitto:2
profiles: ["test"]
restart: unless-stopped
ports:
- "${MQTT_HOST_PORT:-1883}:1883"
volumes:
- ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
healthcheck:
test: ["CMD", "mosquitto_pub", "-h", "127.0.0.1", "-t", "broker/healthcheck", "-m", "ping"]
interval: 30s
timeout: 5s
retries: 3
volumes:
db_data: