refactor(prometheus,stoat): prefix service names so instances are possible

Both apps were barred from multiple instances by the same rule: a
service or container name with no <app> prefix cannot be made unique per
instance, so a second copy would fight the first for it.

Prometheus is now fully unblocked. node-exporter and cadvisor became
prometheus-node-exporter and prometheus-cadvisor — service key,
container_name and SERVICE_TAG default together — and prometheus.yml's
scrape targets follow them. The image references (prom/node-exporter,
gcr.io/cadvisor/cadvisor) and the job_name labels are deliberately
untouched: the first are upstream coordinates, the second are labels
dashboards and alert rules key off, and renaming either would break
something to fix nothing.

Stoat's sixteen services are now all stoat-prefixed. Its container_names
already were, so only the service keys and their depends_on references
moved. The subtlety is DNS: compose registers the service name as a
network alias, so renaming a key silently retires the name anything
else resolves it by — Revolt.toml reaches the broker as "rabbit", the
Caddyfile proxies to api, events, autumn, january and gifbox, and the
bucket job talks to http://minio:9000. Ten services already carried an
explicit alias that survives the rename; the six that did not
(database, redis, rabbit, createbuckets, crond, pushd) have been given
one holding their old short name. Every name that resolved before still
resolves. Verified by parsing the result: 16 services, every one keeping
its short alias, and no dangling depends_on in either app.

Stoat stays non-instanceable, but for one narrower reason now, recorded
in its config: LiveKit advertises its own tcp_port 7881 and UDP range
50000-50100 to clients from livekit.yml, so those cannot be randomised
per instance without generating that file per instance and allocating a
range for each.

Stoat's port descriptors name their parent service, so they follow the
rename too: caddy -> stoat-caddy, livekit -> stoat-livekit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-19 23:57:05 +01:00
parent 3ebd4ee5bb
commit acc91777c4
5 changed files with 59 additions and 43 deletions

View File

@ -44,8 +44,8 @@ services:
# network_mode: "container:gluetun-service"
# GLUETUN_ON_END
node-exporter: #LIBREPORTAL|SERVICE_TAG_2|node-exporter
container_name: node-exporter
prometheus-node-exporter: #LIBREPORTAL|SERVICE_TAG_2|prometheus-node-exporter
container_name: prometheus-node-exporter
image: prom/node-exporter
restart: unless-stopped
command:
@ -57,8 +57,8 @@ services:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_2 #LIBREPORTAL|IP_TAG_2|IP_DATA_2
cadvisor: #LIBREPORTAL|SERVICE_TAG_3|cadvisor
container_name: cadvisor
prometheus-cadvisor: #LIBREPORTAL|SERVICE_TAG_3|prometheus-cadvisor
container_name: prometheus-cadvisor
image: gcr.io/cadvisor/cadvisor
restart: unless-stopped
privileged: true

View File

@ -15,8 +15,7 @@ CFG_PROMETHEUS_APP_NAME=prometheus
# (own data/DB/subdomain/backups) via `libreportal instance create`. Only set on
# apps whose compose identity (container_name, Traefik routers, backup labels)
# is instance-safe — see scripts/instance/instance_create.sh.
# Not instance-safe. Declares node-exporter and cadvisor, names with no "prometheus" prefix, which cannot be made unique per instance.
CFG_PROMETHEUS_MULTI_INSTANCE=false
CFG_PROMETHEUS_MULTI_INSTANCE=true
CFG_PROMETHEUS_BACKUP=true
CFG_PROMETHEUS_BACKUP_STRATEGY=auto
CFG_PROMETHEUS_UPDATE_TYPE=auto

View File

@ -16,8 +16,8 @@ scrape_configs:
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- targets: ['prometheus-node-exporter:9100']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- targets: ['prometheus-cadvisor:8080']

View File

@ -35,7 +35,7 @@ services:
# Upstream's healthcheck is kept rather than the LibrePortal HEALTHCHECK_TAG:
# half the stack uses `depends_on: condition: service_healthy` against it, so
# disabling the healthcheck would deadlock the boot order. Same for rabbit.
database: #LIBREPORTAL|SERVICE_TAG_1|database
stoat-database: #LIBREPORTAL|SERVICE_TAG_1|stoat-database
container_name: stoat-database
image: mongo:8.0 #LIBREPORTAL|STOAT_MONGO_VERSION_TAG|8.0
restart: unless-stopped
@ -58,9 +58,11 @@ services:
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_1 #LIBREPORTAL|IP_TAG_1|IP_DATA_1
aliases:
- database
# Valkey — event message broker and KV store.
redis: #LIBREPORTAL|SERVICE_TAG_2|redis
stoat-redis: #LIBREPORTAL|SERVICE_TAG_2|stoat-redis
container_name: stoat-redis
image: valkey/valkey:9-alpine #LIBREPORTAL|STOAT_VALKEY_VERSION_TAG|9-alpine
restart: unless-stopped
@ -69,9 +71,11 @@ services:
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_2 #LIBREPORTAL|IP_TAG_2|IP_DATA_2
aliases:
- redis
# RabbitMQ — internal message broker (push notifications, voice events).
rabbit: #LIBREPORTAL|SERVICE_TAG_3|rabbit
stoat-rabbit: #LIBREPORTAL|SERVICE_TAG_3|stoat-rabbit
container_name: stoat-rabbit
image: rabbitmq:4-alpine #LIBREPORTAL|STOAT_RABBITMQ_VERSION_TAG|4-alpine
restart: unless-stopped
@ -90,13 +94,15 @@ services:
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_3 #LIBREPORTAL|IP_TAG_3|IP_DATA_3
aliases:
- rabbit
# MinIO — S3-compatible object storage for uploads and avatars.
#
# The bucket-name aliases are load-bearing: the file server addresses
# buckets virtual-host style (<bucket>.minio), so without these the DNS
# lookup fails and every upload errors.
minio: #LIBREPORTAL|SERVICE_TAG_4|minio
stoat-minio: #LIBREPORTAL|SERVICE_TAG_4|stoat-minio
container_name: stoat-minio
image: minio/minio:latest #LIBREPORTAL|STOAT_MINIO_VERSION_TAG|latest
restart: unless-stopped
@ -125,11 +131,11 @@ services:
# One-shot: creates the uploads bucket, then exits. Not a failure when you
# see it stopped.
createbuckets: #LIBREPORTAL|SERVICE_TAG_5|createbuckets
stoat-createbuckets: #LIBREPORTAL|SERVICE_TAG_5|stoat-createbuckets
container_name: stoat-createbuckets
image: minio/mc:latest #LIBREPORTAL|STOAT_MINIO_MC_VERSION_TAG|latest
depends_on:
- minio
- stoat-minio
# Credentials come in through the environment rather than being written
# into the entrypoint: a #LIBREPORTAL annotation only substitutes on the
# line it sits on, and inside a folded block scalar it would end up as
@ -150,10 +156,12 @@ services:
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_5 #LIBREPORTAL|IP_TAG_5|IP_DATA_5
aliases:
- createbuckets
# Caddy — internal path router for the whole stack. This is the only service
# Traefik talks to, and the only one carrying a Traefik router.
caddy: #LIBREPORTAL|SERVICE_TAG_6|caddy
stoat-caddy: #LIBREPORTAL|SERVICE_TAG_6|stoat-caddy
container_name: stoat-caddy
image: caddy:2-alpine #LIBREPORTAL|STOAT_CADDY_VERSION_TAG|2-alpine
restart: unless-stopped
@ -198,17 +206,17 @@ services:
# GLUETUN_ON_END
# API server.
api: #LIBREPORTAL|SERVICE_TAG_7|api
stoat-api: #LIBREPORTAL|SERVICE_TAG_7|stoat-api
container_name: stoat-api
image: ghcr.io/stoatchat/api:v0.15.1 #LIBREPORTAL|STOAT_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
redis:
stoat-redis:
condition: service_started
rabbit:
stoat-rabbit:
condition: service_healthy
volumes:
- ./Revolt.toml:/Revolt.toml:ro
@ -219,15 +227,15 @@ services:
- api
# Websocket / events service.
events: #LIBREPORTAL|SERVICE_TAG_8|events
stoat-events: #LIBREPORTAL|SERVICE_TAG_8|stoat-events
container_name: stoat-events
image: ghcr.io/stoatchat/events:v0.15.1 #LIBREPORTAL|STOAT_EVENTS_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
redis:
stoat-redis:
condition: service_started
volumes:
- ./Revolt.toml:/Revolt.toml:ro
@ -238,15 +246,15 @@ services:
- events
# Autumn — file server.
autumn: #LIBREPORTAL|SERVICE_TAG_9|autumn
stoat-autumn: #LIBREPORTAL|SERVICE_TAG_9|stoat-autumn
container_name: stoat-autumn
image: ghcr.io/stoatchat/file-server:v0.15.1 #LIBREPORTAL|STOAT_AUTUMN_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
createbuckets:
stoat-createbuckets:
condition: service_started
volumes:
- ./Revolt.toml:/Revolt.toml:ro
@ -257,7 +265,7 @@ services:
- autumn
# January — link metadata and image proxy.
january: #LIBREPORTAL|SERVICE_TAG_10|january
stoat-january: #LIBREPORTAL|SERVICE_TAG_10|stoat-january
container_name: stoat-january
image: ghcr.io/stoatchat/proxy:v0.15.1 #LIBREPORTAL|STOAT_JANUARY_VERSION_TAG|v0.15.1
restart: unless-stopped
@ -272,7 +280,7 @@ services:
# Gifbox — Tenor proxy for the GIF picker. Inert until a Tenor API key is
# added to secrets.env; see the upstream Guides.md.
gifbox: #LIBREPORTAL|SERVICE_TAG_11|gifbox
stoat-gifbox: #LIBREPORTAL|SERVICE_TAG_11|stoat-gifbox
container_name: stoat-gifbox
image: ghcr.io/stoatchat/gifbox:v0.15.1 #LIBREPORTAL|STOAT_GIFBOX_VERSION_TAG|v0.15.1
restart: unless-stopped
@ -286,51 +294,55 @@ services:
- gifbox
# Scheduled task daemon.
crond: #LIBREPORTAL|SERVICE_TAG_12|crond
stoat-crond: #LIBREPORTAL|SERVICE_TAG_12|stoat-crond
container_name: stoat-crond
image: ghcr.io/stoatchat/crond:v0.15.1 #LIBREPORTAL|STOAT_CROND_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
minio:
stoat-minio:
condition: service_started
volumes:
- ./Revolt.toml:/Revolt.toml:ro
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_12 #LIBREPORTAL|IP_TAG_12|IP_DATA_12
aliases:
- crond
# Push notification daemon.
pushd: #LIBREPORTAL|SERVICE_TAG_13|pushd
stoat-pushd: #LIBREPORTAL|SERVICE_TAG_13|stoat-pushd
container_name: stoat-pushd
image: ghcr.io/stoatchat/pushd:v0.15.1 #LIBREPORTAL|STOAT_PUSHD_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
redis:
stoat-redis:
condition: service_started
rabbit:
stoat-rabbit:
condition: service_healthy
volumes:
- ./Revolt.toml:/Revolt.toml:ro
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_13 #LIBREPORTAL|IP_TAG_13|IP_DATA_13
aliases:
- pushd
# Voice ingress daemon — receives LiveKit's webhooks.
voice-ingress: #LIBREPORTAL|SERVICE_TAG_14|voice-ingress
stoat-voice-ingress: #LIBREPORTAL|SERVICE_TAG_14|stoat-voice-ingress
container_name: stoat-voice-ingress
image: ghcr.io/stoatchat/voice-ingress:v0.15.1 #LIBREPORTAL|STOAT_VOICE_INGRESS_VERSION_TAG|v0.15.1
restart: unless-stopped
env_file: secrets.env
depends_on:
database:
stoat-database:
condition: service_healthy
rabbit:
stoat-rabbit:
condition: service_healthy
volumes:
- ./Revolt.toml:/Revolt.toml:ro
@ -351,13 +363,13 @@ services:
# to work from outside the LAN:
# sudo ufw allow 50000:50100/udp
# Voice still falls back to TCP 7881 without it, at the cost of latency.
livekit: #LIBREPORTAL|SERVICE_TAG_15|livekit
stoat-livekit: #LIBREPORTAL|SERVICE_TAG_15|stoat-livekit
container_name: stoat-livekit
image: ghcr.io/stoatchat/livekit-server:v1.9.13 #LIBREPORTAL|STOAT_LIVEKIT_VERSION_TAG|v1.9.13
restart: unless-stopped
command: --config /etc/livekit.yml
depends_on:
redis:
stoat-redis:
condition: service_started
ports:
- "PORTS_DATA_2" #LIBREPORTAL|PORTS_TAG_2|PORTS_DATA_2
@ -371,7 +383,7 @@ services:
- livekit
# The web client itself. Served by Caddy at /.
web: #LIBREPORTAL|SERVICE_TAG_16|web
stoat-web: #LIBREPORTAL|SERVICE_TAG_16|stoat-web
container_name: stoat-web
image: ghcr.io/stoatchat/for-web:0c31cf0 #LIBREPORTAL|STOAT_WEB_VERSION_TAG|0c31cf0
restart: unless-stopped

View File

@ -18,7 +18,12 @@ CFG_STOAT_APP_NAME=stoat
# (own data/DB/subdomain/backups) via `libreportal instance create`. Only set on
# apps whose compose identity (container_name, Traefik routers, backup labels)
# is instance-safe — see scripts/instance/instance_create.sh.
# Not instance-safe. Pins LiveKit to host port 7881, and declares database/redis/rabbit/minio and friends — names with no "stoat" prefix that cannot be made unique per instance.
# Not instance-safe, for ONE remaining reason. The service names are now all
# stoat-prefixed, so compose identity is no longer the blocker. What is: LiveKit
# advertises its own tcp_port (7881) and UDP range (50000-50100) to clients from
# livekit.yml, so those cannot be randomised per instance without generating that
# file per instance and allocating a UDP range for each. Until then a second copy
# could not bind 7881.
CFG_STOAT_MULTI_INSTANCE=false
# No prerequisites. Stoat bakes its public URL into the client bundle, but that
# URL can just as well be http://<lan-ip>:<port> — text chat, channels, roles
@ -97,12 +102,12 @@ CFG_STOAT_NETWORK=default
#
# Only one HTTP port: Caddy fronts the entire stack internally, so /api, /ws,
# /autumn and the rest all arrive on this single host.
CFG_STOAT_PORT_1="caddy|webui|random:80|public|tcp|false|true|true|Web Interface||stoat"
CFG_STOAT_PORT_1="stoat-caddy|webui|random:80|public|tcp|false|true|true|Web Interface||stoat"
# LiveKit's TCP fallback. Pinned rather than random on purpose: LiveKit
# advertises this exact port number to clients from livekit.yml, so a randomised
# external port would be advertised wrongly and voice would fail to connect.
# Not Traefik-managed — WebRTC is not HTTP.
CFG_STOAT_PORT_2="livekit|voice-tcp|7881:7881|public|tcp|false|false|false|LiveKit voice/video (TCP fallback)|"
CFG_STOAT_PORT_2="stoat-livekit|voice-tcp|7881:7881|public|tcp|false|false|false|LiveKit voice/video (TCP fallback)|"
# Stoat exposes no safe way to set a password or grant a role from outside the
# app, so these tools list and enable/disable only — see scripts/stoat_auth.sh.