142 lines
5.1 KiB
YAML
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: |