# ============================================================================= # 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" ports: - "80:80" - "443:443" 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 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