Kamailio als TCP-only SIP Proxy für Voice-Interface mit Openclaw. TLS-Terminierung via Traefik auf Port 5061 (Let's Encrypt). Zwei SIP-Accounts: tobias (Linphone iOS) und dotty (Mac Mini Asterisk). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
916 lines
30 KiB
YAML
916 lines
30 KiB
YAML
# =============================================================================
|
|
# Collaboration Stack - PRODUCTION
|
|
# =============================================================================
|
|
# RAM-Limits nach Empfehlung gesetzt (23.03.2026)
|
|
# Gesamt-RAM-Bedarf: ~13-14 GB (nach Entwickler-Empfehlungen)
|
|
# =============================================================================
|
|
services:
|
|
# =============================================================================
|
|
# TRAEFIK - Reverse Proxy mit Let's Encrypt
|
|
# =============================================================================
|
|
traefik:
|
|
image: traefik:v2.11
|
|
container_name: traefik
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
command:
|
|
- "--api.dashboard=true"
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.websecure.address=:443"
|
|
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
|
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
|
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
|
|
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
|
|
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
|
- "--log.level=INFO"
|
|
- "--entrypoints.siptls.address=:5061"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "5061:5061/tcp"
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- traefik_letsencrypt:/letsencrypt
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
|
|
- "traefik.http.routers.traefik.entrypoints=websecure"
|
|
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.traefik.service=api@internal"
|
|
- "traefik.http.routers.traefik.middlewares=traefik-auth"
|
|
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_AUTH}"
|
|
# =============================================================================
|
|
# KEYCLOAK - Identity Provider (SSO)
|
|
# =============================================================================
|
|
keycloak-db:
|
|
image: postgres:16-alpine
|
|
container_name: keycloak-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
environment:
|
|
POSTGRES_DB: keycloak
|
|
POSTGRES_USER: keycloak
|
|
POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
|
|
volumes:
|
|
- keycloak_db_data:/var/lib/postgresql/data
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U keycloak"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:24.0
|
|
container_name: keycloak
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 2g
|
|
command: start
|
|
environment:
|
|
KC_DB: postgres
|
|
KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
|
|
KC_DB_USERNAME: keycloak
|
|
KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
|
|
KEYCLOAK_ADMIN: admin
|
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
|
KC_HOSTNAME: sso.${DOMAIN}
|
|
KC_PROXY: edge
|
|
KC_HTTP_ENABLED: "true"
|
|
depends_on:
|
|
keycloak-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.keycloak.rule=Host(`sso.${DOMAIN}`)"
|
|
- "traefik.http.routers.keycloak.entrypoints=websecure"
|
|
- "traefik.http.routers.keycloak.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.keycloak.loadbalancer.server.port=8080"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# OPENPROJECT - Projektmanagement
|
|
# =============================================================================
|
|
openproject:
|
|
image: openproject/openproject:14
|
|
container_name: openproject
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 4g
|
|
environment:
|
|
OPENPROJECT_SECRET_KEY_BASE: ${OPENPROJECT_SECRET}
|
|
OPENPROJECT_HOST__NAME: project.${DOMAIN}
|
|
OPENPROJECT_HTTPS: "true"
|
|
OPENPROJECT_DEFAULT__LANGUAGE: de
|
|
OPENPROJECT_SEED_ADMIN_USER_PASSWORD: ${OPENPROJECT_ADMIN_PASSWORD}
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME: "SSO Login"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST: "sso.${DOMAIN}"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER: "openproject"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET: ${OIDC_CLIENT_SECRET}
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER: "https://sso.${DOMAIN}/realms/collaboration"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT: "https://sso.${DOMAIN}/realms/collaboration/protocol/openid-connect/auth"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT: "https://sso.${DOMAIN}/realms/collaboration/protocol/openid-connect/token"
|
|
OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT: "https://sso.${DOMAIN}/realms/collaboration/protocol/openid-connect/userinfo"
|
|
volumes:
|
|
- openproject_data:/var/openproject/assets
|
|
- openproject_pgdata:/var/openproject/pgdata
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.openproject.rule=Host(`project.${DOMAIN}`)"
|
|
- "traefik.http.routers.openproject.entrypoints=websecure"
|
|
- "traefik.http.routers.openproject.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.openproject.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# XWIKI - Wiki
|
|
# =============================================================================
|
|
xwiki-db:
|
|
image: mysql:8.0
|
|
container_name: xwiki-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1g
|
|
environment:
|
|
MYSQL_ROOT_PASSWORD: ${XWIKI_DB_ROOT_PASSWORD}
|
|
MYSQL_DATABASE: xwiki
|
|
MYSQL_USER: xwiki
|
|
MYSQL_PASSWORD: ${XWIKI_DB_PASSWORD}
|
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
|
volumes:
|
|
- xwiki_db_data:/var/lib/mysql
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
xwiki:
|
|
image: xwiki:16-mysql-tomcat
|
|
container_name: xwiki
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 4g
|
|
environment:
|
|
DB_HOST: xwiki-db
|
|
DB_USER: xwiki
|
|
DB_PASSWORD: ${XWIKI_DB_PASSWORD}
|
|
DB_DATABASE: xwiki
|
|
JAVA_OPTS: "-Xmx2g -Xms1g"
|
|
volumes:
|
|
- xwiki_data:/usr/local/xwiki
|
|
depends_on:
|
|
xwiki-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.xwiki.rule=Host(`wiki.${DOMAIN}`)"
|
|
- "traefik.http.routers.xwiki.entrypoints=websecure"
|
|
- "traefik.http.routers.xwiki.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.xwiki.loadbalancer.server.port=8080"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# NEXTCLOUD - Dateiverwaltung
|
|
# =============================================================================
|
|
nextcloud-db:
|
|
image: postgres:16-alpine
|
|
container_name: nextcloud-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
environment:
|
|
POSTGRES_DB: nextcloud
|
|
POSTGRES_USER: nextcloud
|
|
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
|
|
volumes:
|
|
- nextcloud_db_data:/var/lib/postgresql/data
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U nextcloud"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
nextcloud-redis:
|
|
image: redis:7-alpine
|
|
container_name: nextcloud-redis
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
nextcloud:
|
|
image: nextcloud:29-apache
|
|
container_name: nextcloud
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
POSTGRES_HOST: nextcloud-db
|
|
POSTGRES_DB: nextcloud
|
|
POSTGRES_USER: nextcloud
|
|
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
|
|
REDIS_HOST: nextcloud-redis
|
|
NEXTCLOUD_ADMIN_USER: admin
|
|
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
|
|
NEXTCLOUD_TRUSTED_DOMAINS: "cloud.${DOMAIN}"
|
|
OVERWRITEPROTOCOL: https
|
|
OVERWRITEHOST: cloud.${DOMAIN}
|
|
volumes:
|
|
- nextcloud_data:/var/www/html
|
|
depends_on:
|
|
nextcloud-db:
|
|
condition: service_healthy
|
|
nextcloud-redis:
|
|
condition: service_started
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.nextcloud.rule=Host(`cloud.${DOMAIN}`)"
|
|
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
|
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
|
|
- "traefik.http.routers.nextcloud.middlewares=nextcloud-caldav"
|
|
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.permanent=true"
|
|
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav"
|
|
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.replacement=https://$${1}/remote.php/dav/"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# LLDAP - Lightweight LDAP Server
|
|
# =============================================================================
|
|
lldap:
|
|
image: lldap/lldap:stable
|
|
container_name: lldap
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 128m
|
|
environment:
|
|
- UID=1000
|
|
- GID=1000
|
|
- TZ=Europe/Berlin
|
|
- LLDAP_JWT_SECRET=ZldD2dpHj3RssRqBwdgAVmjeSu7UfRamg7PCsrl0iw8=
|
|
- LLDAP_LDAP_USER_PASS=coHfqoruajDrXzeze6qA
|
|
- LLDAP_LDAP_BASE_DN=dc=toppyr,dc=de
|
|
volumes:
|
|
- lldap_data:/data
|
|
networks:
|
|
- collaboration
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.lldap.rule=Host(`ldap.toppyr.de`)"
|
|
- "traefik.http.routers.lldap.entrypoints=websecure"
|
|
- "traefik.http.routers.lldap.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.lldap.loadbalancer.server.port=17170"
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# POSTIZ - Social Media Scheduler
|
|
# =============================================================================
|
|
postiz:
|
|
image: ghcr.io/gitroomhq/postiz-app:latest
|
|
container_name: postiz
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 4g
|
|
restart: unless-stopped
|
|
environment:
|
|
MAIN_URL: "https://postiz.toppyr.de"
|
|
FRONTEND_URL: "https://postiz.toppyr.de"
|
|
NEXT_PUBLIC_BACKEND_URL: "https://postiz.toppyr.de/api"
|
|
JWT_SECRET: "Xk9mL2pQnRs5tUvWxYz1A3bC4dEf6gHi"
|
|
DATABASE_URL: "postgresql://postiz:postiz-secret@postiz-db:5432/postiz"
|
|
REDIS_URL: "redis://postiz-redis:6379"
|
|
BACKEND_INTERNAL_URL: "http://127.0.0.1:3000"
|
|
IS_GENERAL: "true"
|
|
LINKEDIN_CLIENT_ID: "78tv4ijhgx8c1e"
|
|
LINKEDIN_CLIENT_SECRET: "WPL_AP1.C0UAsznfDj7xWWfs.SDHO8g=="
|
|
THREADS_APP_ID: "1641922170163889"
|
|
THREADS_APP_SECRET: "dea32c2064e54bf3d58b70e51a330ef9"
|
|
MASTODON_CLIENT_ID: "2XmVi5O3Bmv8NRwKiJ6Yi4osjSezLUoRce0zBr2xlAQ"
|
|
MASTODON_CLIENT_SECRET: "foi-oiFSzVXvPsMHzsj0LUsR8qZe7VxN5uI-VKzR6IQ"
|
|
MASTODON_URL: "https://gruene.social"
|
|
DISABLE_REGISTRATION: "true"
|
|
STORAGE_PROVIDER: "local"
|
|
UPLOAD_DIRECTORY: "/uploads"
|
|
POSTIZ_GENERIC_OAUTH: "true"
|
|
NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: "Toppyr SSO"
|
|
POSTIZ_OAUTH_URL: "https://sso.toppyr.de"
|
|
POSTIZ_OAUTH_AUTH_URL: "https://sso.toppyr.de/realms/collaboration/protocol/openid-connect/auth"
|
|
POSTIZ_OAUTH_TOKEN_URL: "https://sso.toppyr.de/realms/collaboration/protocol/openid-connect/token"
|
|
POSTIZ_OAUTH_USERINFO_URL: "https://sso.toppyr.de/realms/collaboration/protocol/openid-connect/userinfo"
|
|
POSTIZ_OAUTH_CLIENT_ID: "postiz"
|
|
POSTIZ_OAUTH_CLIENT_SECRET: "xU7jKdZVrHHfp4VTligJKURgm7OXzkud"
|
|
TEMPORAL_ADDRESS: "temporal:7233"
|
|
NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads"
|
|
volumes:
|
|
- postiz_config:/config
|
|
- postiz_uploads:/uploads
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.postiz.rule=Host(`postiz.toppyr.de`)"
|
|
- "traefik.http.routers.postiz.entrypoints=websecure"
|
|
- "traefik.http.routers.postiz.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.postiz.loadbalancer.server.port=5000"
|
|
networks:
|
|
- collaboration
|
|
depends_on:
|
|
postiz-db:
|
|
condition: service_healthy
|
|
postiz-redis:
|
|
condition: service_healthy
|
|
postiz-db:
|
|
image: postgres:17-alpine
|
|
container_name: postiz-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: postiz
|
|
POSTGRES_USER: postiz
|
|
POSTGRES_PASSWORD: postiz-secret
|
|
volumes:
|
|
- postiz_db:/var/lib/postgresql/data
|
|
networks:
|
|
- collaboration
|
|
healthcheck:
|
|
test: ["CMD", "pg_isready", "-U", "postiz"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
postiz-redis:
|
|
image: redis:7.2-alpine
|
|
container_name: postiz-redis
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
restart: unless-stopped
|
|
volumes:
|
|
- postiz_redis:/data
|
|
networks:
|
|
- collaboration
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
# =============================================================================
|
|
# LANDING PAGE
|
|
# =============================================================================
|
|
landing:
|
|
image: nginx:alpine
|
|
container_name: landing
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 32m
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /opt/collaboration/landing:/usr/share/nginx/html:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.landing.rule=Host(`toppyr.de`) || Host(`www.toppyr.de`)"
|
|
- "traefik.http.routers.landing.entrypoints=websecure"
|
|
- "traefik.http.routers.landing.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.landing.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
# =============================================================================
|
|
# GRÜNE ANTRAEGE - Antragstools
|
|
# =============================================================================
|
|
gruene-antraege:
|
|
image: nginx:alpine
|
|
container_name: gruene-antraege
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 32m
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /opt/collaboration/gruene-antraege:/usr/share/nginx/html:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.gruene.middlewares=sso-auth@docker"
|
|
- "traefik.http.routers.gruene.rule=Host(`gruen.toppyr.de`)"
|
|
- "traefik.http.routers.gruene.entrypoints=websecure"
|
|
- "traefik.http.routers.gruene.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.gruene.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
# =============================================================================
|
|
# TEMPORAL STACK - Workflow Engine for Postiz
|
|
# =============================================================================
|
|
temporal-elasticsearch:
|
|
container_name: temporal-elasticsearch
|
|
image: elasticsearch:7.17.27
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1g
|
|
restart: unless-stopped
|
|
environment:
|
|
- cluster.routing.allocation.disk.threshold_enabled=true
|
|
- cluster.routing.allocation.disk.watermark.low=512mb
|
|
- cluster.routing.allocation.disk.watermark.high=256mb
|
|
- cluster.routing.allocation.disk.watermark.flood_stage=128mb
|
|
- discovery.type=single-node
|
|
- ES_JAVA_OPTS=-Xms512m -Xmx512m
|
|
- xpack.security.enabled=false
|
|
networks:
|
|
- collaboration
|
|
expose:
|
|
- 9200
|
|
volumes:
|
|
- temporal_es_data:/usr/share/elasticsearch/data
|
|
temporal-postgresql:
|
|
container_name: temporal-postgresql
|
|
image: postgres:16
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_PASSWORD: temporal
|
|
POSTGRES_USER: temporal
|
|
networks:
|
|
- collaboration
|
|
expose:
|
|
- 5432
|
|
volumes:
|
|
- temporal_pg_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD", "pg_isready", "-U", "temporal"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
temporal:
|
|
container_name: temporal
|
|
image: temporalio/auto-setup:1.28.1
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
restart: unless-stopped
|
|
depends_on:
|
|
temporal-postgresql:
|
|
condition: service_healthy
|
|
temporal-elasticsearch:
|
|
condition: service_started
|
|
environment:
|
|
- DB=postgres12
|
|
- DB_PORT=5432
|
|
- POSTGRES_USER=temporal
|
|
- POSTGRES_PWD=temporal
|
|
- POSTGRES_SEEDS=temporal-postgresql
|
|
- ENABLE_ES=true
|
|
- ES_SEEDS=temporal-elasticsearch
|
|
- ES_VERSION=v7
|
|
- TEMPORAL_NAMESPACE=default
|
|
networks:
|
|
- collaboration
|
|
expose:
|
|
- 7233
|
|
# =============================================================================
|
|
# TRAEFIK FORWARD AUTH (Keycloak SSO)
|
|
# =============================================================================
|
|
forward-auth:
|
|
image: thomseddon/traefik-forward-auth:2
|
|
container_name: forward-auth
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
restart: unless-stopped
|
|
environment:
|
|
PROVIDERS_OIDC_ISSUER_URL: "https://sso.toppyr.de/realms/collaboration"
|
|
PROVIDERS_OIDC_CLIENT_ID: "gruene-antraege"
|
|
PROVIDERS_OIDC_CLIENT_SECRET: "gruene-antraege-secret-2026"
|
|
SECRET: "forward-auth-secret-random-string-2026"
|
|
DEFAULT_PROVIDER: "oidc"
|
|
LOG_LEVEL: "debug"
|
|
COOKIE_DOMAIN: "toppyr.de"
|
|
URL_PATH: "/_oauth"
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.middlewares.sso-auth.forwardauth.address=http://forward-auth:4181"
|
|
- "traefik.http.middlewares.sso-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
|
|
- "traefik.http.middlewares.sso-auth.forwardauth.trustForwardHeader=true"
|
|
- "traefik.http.services.forward-auth.loadbalancer.server.port=4181"
|
|
# =============================================================================
|
|
|
|
# =============================================================================
|
|
# ESPOCRM - CRM System
|
|
# =============================================================================
|
|
espocrm-db:
|
|
image: mariadb:10.11
|
|
container_name: espocrm-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
MARIADB_ROOT_PASSWORD: ${ESPOCRM_DB_PASSWORD}
|
|
MARIADB_DATABASE: espocrm
|
|
MARIADB_USER: espocrm
|
|
MARIADB_PASSWORD: ${ESPOCRM_DB_PASSWORD}
|
|
volumes:
|
|
- espocrm_db_data:/var/lib/mysql
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
espocrm:
|
|
image: espocrm/espocrm:latest
|
|
container_name: espocrm
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
ESPOCRM_DATABASE_HOST: espocrm-db
|
|
ESPOCRM_DATABASE_NAME: espocrm
|
|
ESPOCRM_DATABASE_USER: espocrm
|
|
ESPOCRM_DATABASE_PASSWORD: ${ESPOCRM_DB_PASSWORD}
|
|
ESPOCRM_ADMIN_USERNAME: admin
|
|
ESPOCRM_ADMIN_PASSWORD: ${ESPOCRM_ADMIN_PASSWORD}
|
|
ESPOCRM_SITE_URL: https://crm.toppyr.de
|
|
volumes:
|
|
- espocrm_data:/var/www/html
|
|
depends_on:
|
|
espocrm-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.espocrm.rule=Host(`crm.toppyr.de`)"
|
|
- "traefik.http.routers.espocrm.entrypoints=websecure"
|
|
- "traefik.http.routers.espocrm.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.espocrm.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
|
|
# MAUTIC - Marketing Automation
|
|
# =============================================================================
|
|
mautic-db:
|
|
image: mariadb:10.11
|
|
container_name: mautic-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
MARIADB_ROOT_PASSWORD: ${MAUTIC_DB_PASSWORD}
|
|
MARIADB_DATABASE: mautic
|
|
MARIADB_USER: mautic
|
|
MARIADB_PASSWORD: ${MAUTIC_DB_PASSWORD}
|
|
volumes:
|
|
- mautic_db_data:/var/lib/mysql
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
mautic:
|
|
image: mautic/mautic:5-apache
|
|
container_name: mautic
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1g
|
|
environment:
|
|
MAUTIC_DB_HOST: mautic-db
|
|
MAUTIC_DB_PORT: 3306
|
|
MAUTIC_DB_NAME: mautic
|
|
MAUTIC_DB_USER: mautic
|
|
MAUTIC_DB_PASSWORD: ${MAUTIC_DB_PASSWORD}
|
|
MAUTIC_RUN_CRON_JOBS: "true"
|
|
MAUTIC_TRUSTED_PROXIES: "[\"0.0.0.0/0\"]"
|
|
volumes:
|
|
- mautic_data:/var/www/html
|
|
depends_on:
|
|
mautic-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
#- "traefik.http.routers.mautic.middlewares=sso-auth@docker" # Disabled for native SAML
|
|
- "traefik.http.routers.mautic.rule=Host(`marketing.toppyr.de`)"
|
|
- "traefik.http.routers.mautic.entrypoints=websecure"
|
|
- "traefik.http.routers.mautic.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.mautic.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# FREESCOUT - Helpdesk / Ticketsystem
|
|
# =============================================================================
|
|
freescout-db:
|
|
image: mariadb:10.11
|
|
container_name: freescout-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
environment:
|
|
MARIADB_ROOT_PASSWORD: ${FREESCOUT_DB_PASSWORD}
|
|
MARIADB_DATABASE: freescout
|
|
MARIADB_USER: freescout
|
|
MARIADB_PASSWORD: ${FREESCOUT_DB_PASSWORD}
|
|
volumes:
|
|
- freescout_db_data:/var/lib/mysql
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
freescout:
|
|
image: tiredofit/freescout:latest
|
|
container_name: freescout
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
CONTAINER_NAME: freescout
|
|
DB_HOST: freescout-db
|
|
DB_NAME: freescout
|
|
DB_USER: freescout
|
|
DB_PASS: ${FREESCOUT_DB_PASSWORD}
|
|
SITE_URL: https://support.toppyr.de
|
|
ADMIN_EMAIL: mail@tobiasroedel.de
|
|
ADMIN_PASS: ${FREESCOUT_ADMIN_PASSWORD}
|
|
TIMEZONE: Europe/Berlin
|
|
ENABLE_SSL_PROXY: "TRUE"
|
|
volumes:
|
|
- freescout_data:/data
|
|
depends_on:
|
|
freescout-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.freescout.rule=Host(`support.toppyr.de`)"
|
|
- "traefik.http.routers.freescout.entrypoints=websecure"
|
|
- "traefik.http.routers.freescout.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.freescout.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
# =============================================================================
|
|
# UPTIME KUMA - Monitoring
|
|
# =============================================================================
|
|
|
|
# =============================================================================
|
|
# NETDATA PROXY (External service on host)
|
|
# =============================================================================
|
|
netdata-proxy:
|
|
image: alpine/socat
|
|
container_name: netdata-proxy
|
|
restart: unless-stopped
|
|
command: TCP-LISTEN:19999,fork,reuseaddr TCP:host.docker.internal:19999
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.netdata.rule=Host(`netdata.toppyr.de`)"
|
|
- "traefik.http.routers.netdata.entrypoints=websecure"
|
|
- "traefik.http.routers.netdata.tls.certresolver=letsencrypt"
|
|
|
|
|
|
- "traefik.http.services.netdata.loadbalancer.server.port=19999"
|
|
- "traefik.http.routers.netdata.middlewares=sso-auth@docker"
|
|
networks:
|
|
- collaboration
|
|
|
|
# =============================================================================
|
|
# MONITORING DASHBOARD (Static HTML)
|
|
# =============================================================================
|
|
monitoring-dashboard:
|
|
image: nginx:alpine
|
|
container_name: monitoring-dashboard
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /var/www/monitoring:/usr/share/nginx/html:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.monitoring.priority=100"
|
|
- "traefik.http.routers.monitoring.rule=Host(`toppyr.de`) || Host(`www.toppyr.de`)"
|
|
- "traefik.http.routers.monitoring.entrypoints=websecure"
|
|
- "traefik.http.routers.monitoring.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.monitoring.middlewares="
|
|
- "traefik.http.services.monitoring.loadbalancer.server.port=80"
|
|
networks:
|
|
- collaboration
|
|
uptime-kuma:
|
|
image: louislam/uptime-kuma:1
|
|
container_name: uptime-kuma
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
restart: unless-stopped
|
|
volumes:
|
|
- uptime_kuma_data:/app/data
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.uptime.rule=Host(`status.toppyr.de`)"
|
|
- "traefik.http.routers.uptime.entrypoints=websecure"
|
|
- "traefik.http.routers.uptime.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.uptime.loadbalancer.server.port=3001"
|
|
networks:
|
|
- collaboration
|
|
|
|
|
|
|
|
# =============================================================================
|
|
# GITEA - Git Repository Server
|
|
# =============================================================================
|
|
gitea-db:
|
|
image: postgres:16-alpine
|
|
container_name: gitea-db
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256m
|
|
environment:
|
|
POSTGRES_DB: gitea
|
|
POSTGRES_USER: gitea
|
|
POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD}
|
|
volumes:
|
|
- gitea_db_data:/var/lib/postgresql/data
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
gitea:
|
|
image: gitea/gitea:1.22
|
|
container_name: gitea
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512m
|
|
environment:
|
|
- USER_UID=1000
|
|
- USER_GID=1000
|
|
- GITEA__database__DB_TYPE=postgres
|
|
- GITEA__database__HOST=gitea-db:5432
|
|
- GITEA__database__NAME=gitea
|
|
- GITEA__database__USER=gitea
|
|
- GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
|
|
- GITEA__server__DOMAIN=repo.${DOMAIN}
|
|
- GITEA__server__SSH_DOMAIN=repo.${DOMAIN}
|
|
- GITEA__server__ROOT_URL=https://repo.${DOMAIN}/
|
|
- GITEA__server__SSH_PORT=2222
|
|
- GITEA__server__SSH_LISTEN_PORT=22
|
|
- GITEA__service__DISABLE_REGISTRATION=true
|
|
- GITEA__openid__ENABLE_OPENID_SIGNIN=false
|
|
- GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION=true
|
|
- GITEA__oauth2_client__USERNAME=nickname
|
|
volumes:
|
|
- gitea_data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
ports:
|
|
- "2222:22"
|
|
depends_on:
|
|
gitea-db:
|
|
condition: service_healthy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.gitea.rule=Host(`repo.${DOMAIN}`)"
|
|
- "traefik.http.routers.gitea.entrypoints=websecure"
|
|
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# WEBHOOK HANDLER - Gitea → OpenProject Integration
|
|
# =============================================================================
|
|
webhook-handler:
|
|
image: python:3.12-alpine
|
|
container_name: webhook-handler
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
environment:
|
|
- OPENPROJECT_URL=https://project.${DOMAIN}
|
|
- OPENPROJECT_API_KEY=${OPENPROJECT_API_KEY:-}
|
|
- GITEA_URL=https://repo.${DOMAIN}
|
|
- PORT=9000
|
|
volumes:
|
|
- /opt/collaboration/webhooks:/app:ro
|
|
working_dir: /app
|
|
command: python gitea-openproject-webhook.py
|
|
networks:
|
|
- collaboration
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# KAMAILIO - SIP Proxy (Dotty Phone)
|
|
# =============================================================================
|
|
# TCP-only Proxy, TLS-Terminierung via Traefik auf Port 5061.
|
|
# Zwei SIP-Accounts: tobias (Linphone iOS) und dotty (Mac Mini Asterisk).
|
|
# Siehe: dotty-phone/README.md
|
|
# =============================================================================
|
|
kamailio:
|
|
image: kamailio/kamailio-ci:5.5.2-alpine
|
|
container_name: kamailio
|
|
restart: unless-stopped
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64m
|
|
volumes:
|
|
- ./kamailio/kamailio.cfg:/etc/kamailio/kamailio.cfg:ro
|
|
- ./kamailio/kamailio-local.cfg:/etc/kamailio/kamailio-local.cfg:ro
|
|
networks:
|
|
- collaboration
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.tcp.routers.sip.rule=HostSNI(`sip.toppyr.de`)"
|
|
- "traefik.tcp.routers.sip.entrypoints=siptls"
|
|
- "traefik.tcp.routers.sip.tls=true"
|
|
- "traefik.tcp.routers.sip.tls.certresolver=letsencrypt"
|
|
- "traefik.tcp.services.sip.loadbalancer.server.port=5060"
|
|
|
|
volumes:
|
|
gitea_db_data:
|
|
gitea_data:
|
|
traefik_letsencrypt:
|
|
traefik_certs:
|
|
keycloak_db_data:
|
|
openproject_data:
|
|
openproject_pgdata:
|
|
xwiki_db_data:
|
|
xwiki_data:
|
|
nextcloud_db_data:
|
|
nextcloud_data:
|
|
lldap_data:
|
|
postiz_config:
|
|
postiz_uploads:
|
|
postiz_db:
|
|
postiz_redis:
|
|
temporal_es_data:
|
|
temporal_pg_data:
|
|
espocrm_db_data:
|
|
espocrm_data:
|
|
mautic_db_data:
|
|
mautic_data:
|
|
freescout_db_data:
|
|
freescout_data:
|
|
uptime_kuma_data:
|
|
|
|
|
|
networks:
|
|
collaboration:
|
|
driver: bridge
|
|
|