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>
396 lines
18 KiB
YAML
396 lines
18 KiB
YAML
# Stoat (formerly Revolt) — the closest open-source equivalent to Discord's
|
|
# model of servers, channels, roles and voice.
|
|
#
|
|
# Layout note, because it looks inconsistent at first glance: the compose
|
|
# *service keys* below (database, redis, api, autumn, ...) are deliberately kept
|
|
# identical to upstream's compose.yml, while container_name is prefixed with
|
|
# stoat- so nothing collides with other LibrePortal apps. Compose registers both
|
|
# the service key and the container name on the network, so upstream's internal
|
|
# defaults — the S3 endpoint baked into the file server, MINIO_DOMAIN, the
|
|
# service names in Revolt.toml — keep resolving, and LibrePortal still gets the
|
|
# prefixed container names its port, firewall and backup layers key on.
|
|
#
|
|
# SERVICE_TAG_n carries the compose *key*, not the container name, for two
|
|
# reasons: ipUpdateComposeTags allocates exactly one IP per SERVICE_TAG and fills
|
|
# IP_TAG_i only where SERVICE_TAG_i exists (so every service with an
|
|
# ipv4_address needs one, index-aligned, or it deploys with a literal
|
|
# IP_DATA_n and compose refuses to start), and `libreportal app restart <app>
|
|
# <service>` passes the value straight to `docker compose restart`, which only
|
|
# understands keys.
|
|
#
|
|
# Upstream fronts the whole stack with Caddy doing path routing (/api, /ws,
|
|
# /autumn, ...). That is kept as-is and Traefik simply proxies to it, which is
|
|
# upstream's own supported "behind another reverse proxy" mode — reimplementing
|
|
# eight path routes as Traefik labels would be a second copy to keep in sync for
|
|
# no benefit. Caddy is given HOSTNAME=:80 by the install hook so it serves plain
|
|
# HTTP and never tries to obtain its own certificate.
|
|
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
external: true
|
|
|
|
services:
|
|
# MongoDB — primary datastore.
|
|
#
|
|
# 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.
|
|
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
|
|
volumes:
|
|
- ./data/db:/data/db
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
# Required on Linux 6.19 and newer — see SERVER-121912. MongoDB 8.x
|
|
# bundles a TCMalloc whose restartable-sequence registration the
|
|
# newer kernel rejects, and mongod aborts on startup rather than
|
|
# degrading. Letting glibc own the rseq registration sidesteps it.
|
|
# Upstream Stoat hit the same wall (stoatchat/self-hosted#268).
|
|
- GLIBC_TUNABLES=glibc.pthread.rseq=1
|
|
healthcheck:
|
|
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
|
|
interval: 10s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 10s
|
|
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.
|
|
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
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
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).
|
|
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
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
- RABBITMQ_DEFAULT_USER=stoat
|
|
- RABBITMQ_DEFAULT_PASS=STOAT_RABBITMQ_PASSWORD_1_DATA #LIBREPORTAL|STOAT_RABBITMQ_PASSWORD_1_TAG|STOAT_RABBITMQ_PASSWORD_1_DATA
|
|
volumes:
|
|
- ./data/rabbit:/var/lib/rabbitmq
|
|
healthcheck:
|
|
test: rabbitmq-diagnostics -q ping
|
|
interval: 10s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 20s
|
|
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.
|
|
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
|
|
command: server /data
|
|
volumes:
|
|
- ./data/minio:/data
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
- MINIO_ROOT_USER=stoatminio
|
|
- MINIO_ROOT_PASSWORD=STOAT_MINIO_PASSWORD_1_DATA #LIBREPORTAL|STOAT_MINIO_PASSWORD_1_TAG|STOAT_MINIO_PASSWORD_1_DATA
|
|
- MINIO_DOMAIN=minio
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_4 #LIBREPORTAL|IP_TAG_4|IP_DATA_4
|
|
aliases:
|
|
- minio
|
|
- revolt-uploads.minio
|
|
# Legacy bucket names, kept because instances created before
|
|
# the consolidation still address them.
|
|
- attachments.minio
|
|
- avatars.minio
|
|
- backgrounds.minio
|
|
- icons.minio
|
|
- banners.minio
|
|
- emojis.minio
|
|
|
|
# One-shot: creates the uploads bucket, then exits. Not a failure when you
|
|
# see it stopped.
|
|
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:
|
|
- 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
|
|
# literal text in the command anyway. $$ escapes the dollar so compose
|
|
# leaves it for the container's shell instead of interpolating it here.
|
|
environment:
|
|
- MC_USER=stoatminio
|
|
- MC_PASS=STOAT_MINIO_PASSWORD_1_DATA #LIBREPORTAL|STOAT_MINIO_PASSWORD_1_TAG|STOAT_MINIO_PASSWORD_1_DATA
|
|
entrypoint: >
|
|
/bin/sh -c "
|
|
while ! /usr/bin/mc ready minio; do
|
|
/usr/bin/mc alias set minio http://minio:9000 $$MC_USER $$MC_PASS;
|
|
echo 'Waiting minio...' && sleep 1;
|
|
done;
|
|
/usr/bin/mc mb --ignore-existing minio/revolt-uploads;
|
|
exit 0;
|
|
"
|
|
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.
|
|
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
|
|
env_file: .env.web
|
|
# GLUETUN_OFF_BEGIN
|
|
ports:
|
|
- "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1
|
|
# GLUETUN_OFF_END
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- ./stoat.json:/stoat.json:ro
|
|
- ./data/caddy-data:/data
|
|
- ./data/caddy-config:/config
|
|
labels:
|
|
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA
|
|
libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA
|
|
libreportal.backup.db: "mongo:stoat-database:data/db:"
|
|
# Uploads live in MinIO, not on a filesystem the file server owns,
|
|
# so the object store's own data dir is what has to be captured.
|
|
libreportal.backup.files: "stoat-minio:/data:data/minio"
|
|
traefik.enable: TRAEFIK_ENABLE_DATA #LIBREPORTAL|TRAEFIK_ENABLE_TAG|TRAEFIK_ENABLE_DATA
|
|
# TRAEFIK_PORT_1_BEGIN
|
|
traefik.http.routers.stoat-caddy.entrypoints: web,websecure
|
|
traefik.http.routers.stoat-caddy.rule: Host(`DOMAINSUBNAME_DATA_1`) #LIBREPORTAL|DOMAINSUBNAME_TAG_1|DOMAINSUBNAME_DATA_1
|
|
traefik.http.routers.stoat-caddy.tls: true
|
|
traefik.http.routers.stoat-caddy.tls.certresolver: production
|
|
traefik.http.services.stoat-caddy.loadbalancer.server.port: PORT_INTERNAL_DATA_1 #LIBREPORTAL|PORT_INTERNAL_TAG_1|PORT_INTERNAL_DATA_1
|
|
traefik.http.routers.stoat-caddy.middlewares: MIDDLEWARE_DATA_1 #LIBREPORTAL|MIDDLEWARE_TAG_1|MIDDLEWARE_DATA_1
|
|
# TRAEFIK_PORT_1_END
|
|
traefik.docker.network: DOCKER_NETWORK_DATA #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
healthcheck:
|
|
disable: HEALTHCHECK_DATA #LIBREPORTAL|HEALTHCHECK_TAG|HEALTHCHECK_DATA
|
|
# GLUETUN_OFF_BEGIN
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_6 #LIBREPORTAL|IP_TAG_6|IP_DATA_6
|
|
aliases:
|
|
- caddy
|
|
# GLUETUN_OFF_END
|
|
# GLUETUN_ON_BEGIN
|
|
# network_mode: "container:gluetun-service"
|
|
# GLUETUN_ON_END
|
|
|
|
# API server.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
stoat-redis:
|
|
condition: service_started
|
|
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_7 #LIBREPORTAL|IP_TAG_7|IP_DATA_7
|
|
aliases:
|
|
- api
|
|
|
|
# Websocket / events service.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
stoat-redis:
|
|
condition: service_started
|
|
volumes:
|
|
- ./Revolt.toml:/Revolt.toml:ro
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_8 #LIBREPORTAL|IP_TAG_8|IP_DATA_8
|
|
aliases:
|
|
- events
|
|
|
|
# Autumn — file server.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
stoat-createbuckets:
|
|
condition: service_started
|
|
volumes:
|
|
- ./Revolt.toml:/Revolt.toml:ro
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_9 #LIBREPORTAL|IP_TAG_9|IP_DATA_9
|
|
aliases:
|
|
- autumn
|
|
|
|
# January — link metadata and image proxy.
|
|
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
|
|
env_file: secrets.env
|
|
volumes:
|
|
- ./Revolt.toml:/Revolt.toml:ro
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_10 #LIBREPORTAL|IP_TAG_10|IP_DATA_10
|
|
aliases:
|
|
- january
|
|
|
|
# Gifbox — Tenor proxy for the GIF picker. Inert until a Tenor API key is
|
|
# added to secrets.env; see the upstream Guides.md.
|
|
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
|
|
env_file: secrets.env
|
|
volumes:
|
|
- ./Revolt.toml:/Revolt.toml:ro
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_11 #LIBREPORTAL|IP_TAG_11|IP_DATA_11
|
|
aliases:
|
|
- gifbox
|
|
|
|
# Scheduled task daemon.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
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.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
stoat-redis:
|
|
condition: service_started
|
|
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.
|
|
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:
|
|
stoat-database:
|
|
condition: service_healthy
|
|
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_14 #LIBREPORTAL|IP_TAG_14|IP_DATA_14
|
|
aliases:
|
|
- voice-ingress
|
|
|
|
# LiveKit — the WebRTC SFU behind voice and video.
|
|
#
|
|
# Its media ports cannot go through Traefik: WebRTC is UDP, and Traefik is
|
|
# an HTTP proxy. The TCP fallback port is declared in stoat.config so the
|
|
# port and firewall layers manage it. The UDP range is published literally
|
|
# below because LibrePortal's port table stores one port per row and its
|
|
# firewall rebuild emits /tcp rules only — a range declared there would
|
|
# produce a wrong rule rather than no rule. Open it yourself if voice needs
|
|
# 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.
|
|
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:
|
|
stoat-redis:
|
|
condition: service_started
|
|
ports:
|
|
- "PORTS_DATA_2" #LIBREPORTAL|PORTS_TAG_2|PORTS_DATA_2
|
|
- "50000-50100:50000-50100/udp"
|
|
volumes:
|
|
- ./livekit.yml:/etc/livekit.yml:ro
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_15 #LIBREPORTAL|IP_TAG_15|IP_DATA_15
|
|
aliases:
|
|
- livekit
|
|
|
|
# The web client itself. Served by Caddy at /.
|
|
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
|
|
env_file: .env.web
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_16 #LIBREPORTAL|IP_TAG_16|IP_DATA_16
|
|
aliases:
|
|
- web
|