# BetterDesk Console - Docker Compose Setup # Architecture: Go server (single binary) + Node.js web console # # ============================================================================ # QUICK START (no build required): # curl -fsSL https://raw.githubusercontent.com/UNITRONIX/Rustdesk-FreeConsole/main/docker-compose.quick.yml -o docker-compose.yml # docker compose up -d # # BUILD FROM SOURCE (this file): # docker compose build && docker compose up -d # ============================================================================ # # The Go server replaces separate hbbs/hbbr — it handles signal, relay, # and HTTP API in a single process. services: # BetterDesk Server (Go) — replaces hbbs + hbbr # Single binary: signal (21116), relay (21117), API (21114), NAT test (21115) server: build: context: . dockerfile: Dockerfile.server image: betterdesk-server:local pull_policy: never container_name: betterdesk-server hostname: betterdesk-server command: ["/usr/local/bin/betterdesk-server", "-mode", "all", "-api-port", "21114", "-key-file", "/opt/rustdesk/id_ed25519"] ports: - "21114:21114" # Go HTTP API (default — direct client + REST) - "21115:21115" # NAT type test - "21116:21116/tcp" # Signal TCP - "21116:21116/udp" # Signal UDP - "21117:21117" # Relay TCP - "21118:21118" # WebSocket Signal - "21119:21119" # WebSocket Relay volumes: - rustdesk-data:/opt/rustdesk - console-data:/app/data:ro environment: - ENCRYPTED_ONLY=1 - DB_URL=${DB_URL:-/opt/rustdesk/db_v2.sqlite3} - AUTH_DB_PATH=/app/data/auth.db - SIGNAL_RATE_LIMIT_PER_IP=${SIGNAL_RATE_LIMIT_PER_IP:-20} - P2P_FIRST=${P2P_FIRST:-Y} - ALWAYS_USE_RELAY=${ALWAYS_USE_RELAY:-N} - P2P_FALLBACK_MS=${P2P_FALLBACK_MS:-2000} - SAME_NAT_RELAY=${SAME_NAT_RELAY:-Y} # Enrollment policy. Fresh volumes default to "managed" (operator approves # new devices); pre-existing volumes keep their current mode. Set to # "open", "managed", or "locked" to override. - ENROLLMENT_MODE=${ENROLLMENT_MODE:-} - NTP_SERVERS=${NTP_SERVERS:-pool.ntp.org,time.google.com,time.cloudflare.com} - BILLING_MAX_CLOCK_SKEW_MS=${BILLING_MAX_CLOCK_SKEW_MS:-2000} - BILLING_REQUIRE_SYNCED_CLOCK=${BILLING_REQUIRE_SYNCED_CLOCK:-1} - BILLING_TRUST_OS_NTP=${BILLING_TRUST_OS_NTP:-Y} networks: - betterdesk-net restart: unless-stopped # SECURITY (audit fix L-02, 2026-04-10): drop all capabilities except # those needed by the entrypoint to chown volumes + su-exec to non-root. security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - SETUID - SETGID - CHOWN - FOWNER healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:21114/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # BetterDesk Console — Node.js Web Interface console: build: context: . dockerfile: Dockerfile.console image: betterdesk-console:local pull_policy: never container_name: betterdesk-console hostname: betterdesk-console ports: - "5000:5000" # Web console (admin panel) - "21121:21121" # Backward compat proxy → Go :21114 volumes: - rustdesk-data:/opt/rustdesk # Shared server data (keys, db) — needs write for WAL mode - console-data:/app/data # Console-specific data (auth.db, sessions) environment: - NODE_ENV=production - PORT=5000 - HOST=0.0.0.0 - API_HOST=0.0.0.0 - API_ENABLED=true - API_PORT=21121 - RUSTDESK_API_PROXY=true - GO_API_PORT=21114 - SERVER_BACKEND=betterdesk - HBBS_API_URL=http://betterdesk-server:21114/api - BETTERDESK_API_URL=http://betterdesk-server:21114/api - RUSTDESK_PATH=/opt/rustdesk - DATA_DIR=/app/data - DB_PATH=/opt/rustdesk/db_v2.sqlite3 - PUB_KEY_PATH=/opt/rustdesk/id_ed25519.pub - API_KEY_PATH=/opt/rustdesk/.api_key - SESSION_SECRET=${SESSION_SECRET:-} - WS_HBBS_HOST=betterdesk-server - WS_HBBS_PORT=21116 - WS_HBBR_HOST=betterdesk-server - WS_HBBR_PORT=21117 - DOCKER=true # Optional: Set custom admin credentials for first run # - ADMIN_USERNAME=admin # - ADMIN_PASSWORD=YourSecurePassword123 networks: - betterdesk-net restart: unless-stopped # SECURITY (audit fix L-02, 2026-04-10): drop all caps except those # needed by su-exec (SETUID/SETGID) and volume permission fixes (CHOWN/FOWNER). security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - SETUID - SETGID - CHOWN - FOWNER depends_on: server: condition: service_started healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:5000/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s # Nginx Reverse Proxy (Optional — only with --profile nginx) nginx: image: nginx:alpine container_name: betterdesk-nginx ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro networks: - betterdesk-net restart: unless-stopped depends_on: - console profiles: - nginx volumes: rustdesk-data: driver: local driver_opts: type: none o: bind device: ${RUSTDESK_DATA_PATH:-./data} console-data: driver: local networks: betterdesk-net: driver: bridge ipam: config: - subnet: 172.28.0.0/16