services: # UI dashboard dashboard: image: netbirdio/dashboard:latest restart: unless-stopped networks: - netbird - traefik_traefik env_file: - ./dashboard.env labels: - traefik.enable=true - traefik.docker.network=traefik_default - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80 - traefik.http.routers.netbird-dashboard.rule=Host(`netbird.rozic-dev.com`) - traefik.http.routers.netbird-dashboard.entrypoints=websecure - traefik.http.routers.netbird-dashboard.tls=true - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt - traefik.http.routers.netbird-dashboard.priority=50 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Signal signal: image: netbirdio/signal:latest restart: unless-stopped networks: - netbird - traefik_traefik labels: - traefik.enable=true - traefik.docker.network=traefik_default - traefik.http.services.netbird-signal.loadbalancer.server.port=10000 - traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c - traefik.http.routers.netbird-signal.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/signalexchange.SignalExchange/`) - traefik.http.routers.netbird-signal.entrypoints=websecure - traefik.http.routers.netbird-signal.service=netbird-signal - traefik.http.routers.netbird-signal.tls=true - traefik.http.routers.netbird-signal.tls.certresolver=letsencrypt - traefik.http.routers.netbird-signal.priority=200 # WebSocket route for signal - pass through without stripping prefix - traefik.http.services.netbird-signal-ws.loadbalancer.server.port=80 - traefik.http.routers.netbird-signal-ws.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/ws-proxy/signal`) - traefik.http.routers.netbird-signal-ws.entrypoints=websecure - traefik.http.routers.netbird-signal-ws.service=netbird-signal-ws - traefik.http.routers.netbird-signal-ws.tls=true - traefik.http.routers.netbird-signal-ws.tls.certresolver=letsencrypt - traefik.http.routers.netbird-signal-ws.priority=300 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Relay relay: image: netbirdio/relay:latest restart: unless-stopped networks: - netbird - traefik_traefik env_file: - ./relay.env labels: - traefik.enable=true - traefik.docker.network=traefik_default - traefik.http.services.netbird-relay.loadbalancer.server.port=33080 - traefik.http.routers.netbird-relay.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/relay`) - traefik.http.routers.netbird-relay.entrypoints=websecure - traefik.http.routers.netbird-relay.tls=true - traefik.http.routers.netbird-relay.tls.certresolver=letsencrypt - traefik.http.routers.netbird-relay.priority=200 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Management management: image: netbirdio/management:latest restart: unless-stopped networks: - netbird - traefik_traefik volumes: - netbird_management:/var/lib/netbird - ./management.json:/etc/netbird/management.json command: [ "--port", "80", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.selfhosted", "--dns-domain=netbird.selfhosted", "--idp-sign-key-refresh-enabled" ] labels: - traefik.enable=true - traefik.docker.network=traefik_default - traefik.http.services.netbird-management.loadbalancer.server.port=80 - traefik.http.services.netbird-management-grpc.loadbalancer.server.port=80 - traefik.http.services.netbird-management-grpc.loadbalancer.server.scheme=h2c # REST API - traefik.http.routers.netbird-api.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/api`) - traefik.http.routers.netbird-api.entrypoints=websecure - traefik.http.routers.netbird-api.service=netbird-management - traefik.http.routers.netbird-api.tls=true - traefik.http.routers.netbird-api.tls.certresolver=letsencrypt - traefik.http.routers.netbird-api.priority=200 # gRPC - traefik.http.routers.netbird-management-grpc.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/management.ManagementService/`) - traefik.http.routers.netbird-management-grpc.entrypoints=websecure - traefik.http.routers.netbird-management-grpc.service=netbird-management-grpc - traefik.http.routers.netbird-management-grpc.tls=true - traefik.http.routers.netbird-management-grpc.tls.certresolver=letsencrypt - traefik.http.routers.netbird-management-grpc.priority=200 # WebSocket route for management - traefik.http.routers.netbird-management-ws.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/ws-proxy/management`) - traefik.http.routers.netbird-management-ws.entrypoints=websecure - traefik.http.routers.netbird-management-ws.service=netbird-management - traefik.http.routers.netbird-management-ws.tls=true - traefik.http.routers.netbird-management-ws.tls.certresolver=letsencrypt - traefik.http.routers.netbird-management-ws.priority=300 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Coturn coturn: image: coturn/coturn restart: unless-stopped volumes: - ./turnserver.conf:/etc/turnserver.conf:ro network_mode: host command: - -c /etc/turnserver.conf logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Zitadel - identity provider zitadel: restart: 'always' image: 'ghcr.io/zitadel/zitadel:v2.59.3' command: 'start-from-init --masterkeyFromEnv --tlsMode external' ports: - "8085:8080" # <-- added env_file: - ./zitadel.env environment: ZITADEL_PROJECTIONS_MAXFAILURES: "3" ZITADEL_PROJECTIONS_RETRYDELAY: "2s" ZITADEL_PROJECTIONS_MAXPARALLELPROJECTIONS: "4" ZITADEL_PROJECTIONS_TARGETS1_PARALLEL_PREFILLS: "2" ZITADEL_PROJECTIONS_TARGETS1_BATCHSIZE: "500" depends_on: zdb: condition: 'service_healthy' volumes: - ./machinekey:/machinekey - netbird_zitadel_certs:/zdb-certs:ro networks: - netbird - traefik_traefik labels: - traefik.enable=true - traefik.docker.network=traefik_default - traefik.http.services.zitadel.loadbalancer.server.port=8080 - traefik.http.services.zitadel.loadbalancer.server.scheme=h2c # OIDC wellknown - traefik.http.routers.zitadel-wellknown.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/.well-known`) - traefik.http.routers.zitadel-wellknown.entrypoints=websecure - traefik.http.routers.zitadel-wellknown.service=zitadel - traefik.http.routers.zitadel-wellknown.priority=300 - traefik.http.routers.zitadel-wellknown.tls=true - traefik.http.routers.zitadel-wellknown.tls.certresolver=letsencrypt # OAuth - traefik.http.routers.zitadel-oauth.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/oauth`) - traefik.http.routers.zitadel-oauth.entrypoints=websecure - traefik.http.routers.zitadel-oauth.service=zitadel - traefik.http.routers.zitadel-oauth.priority=300 - traefik.http.routers.zitadel-oauth.tls=true - traefik.http.routers.zitadel-oauth.tls.certresolver=letsencrypt # OIDC - traefik.http.routers.zitadel-oidc.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/oidc`) - traefik.http.routers.zitadel-oidc.entrypoints=websecure - traefik.http.routers.zitadel-oidc.service=zitadel - traefik.http.routers.zitadel-oidc.priority=300 - traefik.http.routers.zitadel-oidc.tls=true - traefik.http.routers.zitadel-oidc.tls.certresolver=letsencrypt # UI Console - traefik.http.routers.zitadel-ui.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/ui`) - traefik.http.routers.zitadel-ui.entrypoints=websecure - traefik.http.routers.zitadel-ui.service=zitadel - traefik.http.routers.zitadel-ui.priority=300 - traefik.http.routers.zitadel-ui.tls=true - traefik.http.routers.zitadel-ui.tls.certresolver=letsencrypt # Device flow - traefik.http.routers.zitadel-device.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/device`) - traefik.http.routers.zitadel-device.entrypoints=websecure - traefik.http.routers.zitadel-device.service=zitadel - traefik.http.routers.zitadel-device.priority=300 - traefik.http.routers.zitadel-device.tls=true - traefik.http.routers.zitadel-device.tls.certresolver=letsencrypt # Management API - traefik.http.routers.zitadel-mgmt.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/management/v1`) - traefik.http.routers.zitadel-mgmt.entrypoints=websecure - traefik.http.routers.zitadel-mgmt.service=zitadel - traefik.http.routers.zitadel-mgmt.priority=300 - traefik.http.routers.zitadel-mgmt.tls=true - traefik.http.routers.zitadel-mgmt.tls.certresolver=letsencrypt # Admin API - traefik.http.routers.zitadel-admin.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/admin/v1`) - traefik.http.routers.zitadel-admin.entrypoints=websecure - traefik.http.routers.zitadel-admin.service=zitadel - traefik.http.routers.zitadel-admin.priority=300 - traefik.http.routers.zitadel-admin.tls=true - traefik.http.routers.zitadel-admin.tls.certresolver=letsencrypt # gRPC endpoints - traefik.http.routers.zitadel-grpc-auth.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/zitadel.auth.v1.AuthService/`) - traefik.http.routers.zitadel-grpc-auth.entrypoints=websecure - traefik.http.routers.zitadel-grpc-auth.service=zitadel - traefik.http.routers.zitadel-grpc-auth.priority=400 - traefik.http.routers.zitadel-grpc-auth.tls=true - traefik.http.routers.zitadel-grpc-auth.tls.certresolver=letsencrypt - traefik.http.routers.zitadel-grpc-admin.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/zitadel.admin.v1.AdminService/`) - traefik.http.routers.zitadel-grpc-admin.entrypoints=websecure - traefik.http.routers.zitadel-grpc-admin.service=zitadel - traefik.http.routers.zitadel-grpc-admin.priority=400 - traefik.http.routers.zitadel-grpc-admin.tls=true - traefik.http.routers.zitadel-grpc-admin.tls.certresolver=letsencrypt - traefik.http.routers.zitadel-grpc-mgmt.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/zitadel.management.v1.ManagementService/`) - traefik.http.routers.zitadel-grpc-mgmt.entrypoints=websecure - traefik.http.routers.zitadel-grpc-mgmt.service=zitadel - traefik.http.routers.zitadel-grpc-mgmt.priority=400 - traefik.http.routers.zitadel-grpc-mgmt.tls=true - traefik.http.routers.zitadel-grpc-mgmt.tls.certresolver=letsencrypt # === CRITICAL ROUTE: /auth/v1 for PAT + wait_api === - traefik.http.routers.zitadel-auth-v1.rule=Host(`netbird.rozic-dev.com`) && PathPrefix(`/auth/v1`) - traefik.http.routers.zitadel-auth-v1.entrypoints=websecure - traefik.http.routers.zitadel-auth-v1.service=zitadel - traefik.http.routers.zitadel-auth-v1.tls=true - traefik.http.routers.zitadel-auth-v1.tls.certresolver=letsencrypt - traefik.http.routers.zitadel-auth-v1.priority=950 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Postgres for Zitadel zdb: restart: 'always' networks: [netbird] image: 'postgres:15-alpine' env_file: - ./zdb.env volumes: - netbird_zdb_data:/var/lib/postgresql/data:rw healthcheck: test: ["CMD-SHELL", "pg_isready -U root"] interval: 5s timeout: 60s retries: 10 start_period: 5s logging: driver: "json-file" options: max-size: "500m" max-file: "2" volumes: netbird_zdb_data: netbird_management: netbird_zitadel_certs: networks: netbird: driver: bridge traefik_traefik: external: true name: traefik_default