services: # Traefik reverse proxy (automatic TLS via Let's Encrypt) traefik: image: traefik:v3.6 container_name: netbird-traefik restart: unless-stopped networks: netbird: ipv4_address: 172.30.0.10 command: # Logging - "--log.level=INFO" - "--accesslog=true" # Docker provider - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=netbird" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" # Disable timeouts for long-lived gRPC streams - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0" # HTTP to HTTPS redirect - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" # Let's Encrypt ACME - "--certificatesresolvers.letsencrypt.acme.email=f.cangi@it-solutions.it" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" # gRPC transport settings - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s" - "--serverstransport.forwardingtimeouts.idleconntimeout=0s" - "--providers.file.filename=/etc/traefik/dynamic.yaml" ports: - '443:443' - '80:80' volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - netbird_traefik_letsencrypt:/letsencrypt - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro logging: driver: "json-file" options: max-size: "500m" max-file: "2" # UI dashboard dashboard: image: netbirdio/dashboard:latest container_name: netbird-dashboard restart: unless-stopped networks: [netbird] env_file: - ./dashboard.env labels: - traefik.enable=true - traefik.http.routers.netbird-dashboard.rule=Host(`netbird.smavs.duckdns.org`) - 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.service=dashboard - traefik.http.routers.netbird-dashboard.priority=1 - traefik.http.services.dashboard.loadbalancer.server.port=80 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Combined server (Management + Signal + Relay + STUN) netbird-server: image: netbirdio/netbird-server:latest container_name: netbird-server restart: unless-stopped networks: [netbird] ports: - '3478:3478/udp' - '172.20.10.19:9090:9090' volumes: - netbird_data:/var/lib/netbird - ./config.yaml:/etc/netbird/config.yaml command: ["--config", "/etc/netbird/config.yaml"] labels: - traefik.enable=true # gRPC router (needs h2c backend for HTTP/2 cleartext) - traefik.http.routers.netbird-grpc.rule=Host(`netbird.smavs.duckdns.org`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`)) - traefik.http.routers.netbird-grpc.entrypoints=websecure - traefik.http.routers.netbird-grpc.tls=true - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt - traefik.http.routers.netbird-grpc.service=netbird-server-h2c - traefik.http.routers.netbird-grpc.priority=100 # Backend router (relay, WebSocket, API, OAuth2) - traefik.http.routers.netbird-backend.rule=Host(`netbird.smavs.duckdns.org`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`)) - traefik.http.routers.netbird-backend.entrypoints=websecure - traefik.http.routers.netbird-backend.tls=true - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt - traefik.http.routers.netbird-backend.service=netbird-server - traefik.http.routers.netbird-backend.priority=100 # Services - traefik.http.services.netbird-server.loadbalancer.server.port=80 - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80 - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c logging: driver: "json-file" options: max-size: "500m" max-file: "2" # NetBird Proxy - exposes internal resources to the internet proxy: image: netbirdio/reverse-proxy:latest container_name: netbird-proxy ports: - 51820:51820/udp restart: unless-stopped networks: [netbird] depends_on: netbird-server: condition: service_started crowdsec: condition: service_healthy env_file: - ./proxy.env volumes: - netbird_proxy_certs:/certs - netbird-proxy-data:/var/lib/netbird labels: # TCP passthrough for any unmatched domain (proxy handles its own TLS) - traefik.enable=true - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`) - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true - traefik.tcp.routers.proxy-passthrough.service=proxy-tls - traefik.tcp.routers.proxy-passthrough.priority=1 - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443 - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file logging: driver: "json-file" options: max-size: "500m" max-file: "2" crowdsec: image: crowdsecurity/crowdsec:v1.7.7 container_name: netbird-crowdsec restart: unless-stopped networks: [netbird] environment: COLLECTIONS: crowdsecurity/linux volumes: - ./crowdsec:/etc/crowdsec - crowdsec_db:/var/lib/crowdsec/data healthcheck: test: [CMD, cscli, lapi, status] interval: 10s timeout: 5s retries: 15 labels: - traefik.enable=false logging: driver: "json-file" options: max-size: "500m" max-file: "2" volumes: netbird_data: netbird_traefik_letsencrypt: netbird_proxy_certs: crowdsec_db: netbird-proxy-data: networks: netbird: driver: bridge ipam: config: - subnet: 172.30.0.0/24 gateway: 172.30.0.1