One container against Postgres, with the polished desktop and mobile clients that make it the least demanding of the four chat options. Runs as the bind-mount owner via USER_TAG: the image bakes in USER mattermost (uid 2000) so it never runs as root and cannot chown its own data directory, which under rootless Docker means it dies on first write. CFG_MATTERMOST_AUTHELIA stays false — OIDC/SAML is a paid tier here, so forward-auth would block the native clients from the API without buying single sign-on in exchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
5.3 KiB
YAML
93 lines
5.3 KiB
YAML
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
external: true
|
|
|
|
services:
|
|
# Mattermost Team Edition — one app container against Postgres. The server
|
|
# and the web client are the same binary, so unlike the Matrix stack there
|
|
# is no separate frontend service to route.
|
|
mattermost-service: #LIBREPORTAL|SERVICE_TAG_1|mattermost-service
|
|
container_name: mattermost-service
|
|
image: mattermost/mattermost-team-edition:11.9 #LIBREPORTAL|MATTERMOST_VERSION_TAG|11.9
|
|
# The image bakes in `USER mattermost` (uid 2000), so it never runs as
|
|
# root and cannot chown its own bind mounts on first boot. Same problem
|
|
# vikunja has: under rootless Docker uid 2000 maps to a host sub-UID
|
|
# that owns nothing, and the container dies on its first write to
|
|
# /mattermost/data. USER_TAG resolves to the identity that actually owns
|
|
# the mounts — 0:0 under rootless, the real uid:gid under rooted.
|
|
user: "USER_DATA" #LIBREPORTAL|USER_TAG|USER_DATA
|
|
restart: unless-stopped
|
|
# GLUETUN_OFF_BEGIN
|
|
ports:
|
|
- "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1
|
|
# GLUETUN_OFF_END
|
|
volumes:
|
|
- ./config:/mattermost/config
|
|
- ./data:/mattermost/data
|
|
- ./logs:/mattermost/logs
|
|
- ./plugins:/mattermost/plugins
|
|
- ./client-plugins:/mattermost/client/plugins
|
|
- ./bleve-indexes:/mattermost/bleve-indexes
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
- MM_SQLSETTINGS_DRIVERNAME=postgres
|
|
# Fixed role and database name, random password. The database is
|
|
# only reachable on the internal docker network, and a generated
|
|
# username buys nothing while making manual psql recovery painful.
|
|
- MM_SQLSETTINGS_DATASOURCE=postgres://mattermost:PASSWORD_DATA_1@mattermost-postgres:5432/mattermost?sslmode=disable&connect_timeout=10 #LIBREPORTAL|PASSWORD_TAG_1|PASSWORD_DATA_1
|
|
# Mattermost builds every absolute link (invites, password resets,
|
|
# CORS and websocket origin checks) from this. Wrong value and the
|
|
# web client connects but the websocket is rejected, which shows up
|
|
# as a chat that loads and then never receives a message.
|
|
- MM_SERVICESETTINGS_SITEURL=APP_URL_DATA #LIBREPORTAL|APP_URL_TAG|APP_URL_DATA
|
|
- MM_BLEVESETTINGS_INDEXDIR=/mattermost/bleve-indexes
|
|
- MM_FILESETTINGS_DIRECTORY=/mattermost/data/
|
|
- MM_LOGSETTINGS_ENABLEFILE=true
|
|
- MM_LOGSETTINGS_FILELOCATION=/mattermost/logs
|
|
depends_on:
|
|
- mattermost-postgres
|
|
labels:
|
|
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA
|
|
libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA
|
|
libreportal.backup.db: "postgres:mattermost-postgres:postgres:"
|
|
libreportal.backup.files: "mattermost-service:/mattermost/data:data"
|
|
traefik.enable: TRAEFIK_ENABLE_DATA #LIBREPORTAL|TRAEFIK_ENABLE_TAG|TRAEFIK_ENABLE_DATA
|
|
# TRAEFIK_PORT_1_BEGIN
|
|
traefik.http.routers.mattermost-service.entrypoints: web,websecure
|
|
traefik.http.routers.mattermost-service.rule: Host(`DOMAINSUBNAME_DATA_1`) #LIBREPORTAL|DOMAINSUBNAME_TAG_1|DOMAINSUBNAME_DATA_1
|
|
traefik.http.routers.mattermost-service.tls: true
|
|
traefik.http.routers.mattermost-service.tls.certresolver: production
|
|
traefik.http.services.mattermost-service.loadbalancer.server.port: PORT_INTERNAL_DATA_1 #LIBREPORTAL|PORT_INTERNAL_TAG_1|PORT_INTERNAL_DATA_1
|
|
traefik.http.routers.mattermost-service.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_1 #LIBREPORTAL|IP_TAG_1|IP_DATA_1
|
|
# GLUETUN_OFF_END
|
|
# GLUETUN_ON_BEGIN
|
|
# network_mode: "container:gluetun-service"
|
|
# GLUETUN_ON_END
|
|
|
|
# No `user:` override here on purpose: the postgres entrypoint starts as
|
|
# root, chowns PGDATA to the postgres user and then drops privileges. Under
|
|
# rootless Docker container-root *is* the install user on the host, so it
|
|
# owns ./postgres and the chown succeeds. Pinning a uid would break that.
|
|
mattermost-postgres:
|
|
image: postgres:15-alpine
|
|
container_name: mattermost-postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
|
- POSTGRES_USER=mattermost
|
|
- POSTGRES_PASSWORD=PASSWORD_DATA_1 #LIBREPORTAL|PASSWORD_TAG_1|PASSWORD_DATA_1
|
|
- POSTGRES_DB=mattermost
|
|
volumes:
|
|
- ./postgres:/var/lib/postgresql/data
|
|
networks:
|
|
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
|
ipv4_address: IP_DATA_2 #LIBREPORTAL|IP_TAG_2|IP_DATA_2
|