# RustDesk Server — VPS (ROOTLESS PODMAN) port of the home server's ~/Server/rustdesk, # migrated 2026-08-12. # # ⚠️ THIS SERVICE CANNOT GO BEHIND CLOUDFLARE'S PROXY. # RustDesk's ID/relay protocol is raw TCP/UDP on 21115-21119. Cloudflare's proxy only # carries HTTP/HTTPS; raw TCP needs Spectrum, which is not on this plan. So # rustdesk.gbrown.org MUST be a grey-cloud (DNS-only) record pointing at # 178.156.197.55, and these ports must be open in firewalld's public zone. # # Consequence, accepted deliberately: this publishes the VPS's real IP. That is # tolerable because the firewall still restricts 80/443 to Cloudflare ranges, so # knowing the IP does not grant access to any of the web services. # # The server keypair in ./volumes/server/id_ed25519 was carried over from the home # server. Its public half matches RUSTDESK_API_RUSTDESK_KEY below # (WLgvHhau6aa5nDPQutTHeQBpIrOEb8aPXByVBWQwkKc=), which is what lets existing clients # reconnect without being re-paired. NEVER regenerate it. # # Rootless adaptations: # - :Z on both bind mounts (SELinux Enforcing); each is exclusive to this container. # - All published ports are >1024 so no privileged-port handling is needed. # - The image starts as root under s6 and drops privileges internally, so no # userns_mode is required. # # PORT REFERENCE (all must be open in the firewall): # 21114/tcp web UI + API 21117/tcp hbbr relay # 21115/tcp hbbs NAT type test 21118/tcp websocket (web client) # 21116/tcp hbbs ID registration 21119/tcp websocket relay # 21116/udp hbbs heartbeat <-- UDP, easy to forget networks: nginx-bridge: external: true services: rustdesk-server: image: docker.io/lejianwen/rustdesk-server-s6:latest container_name: rustdesk-server hostname: rustdesk-server domainname: rustdesk.gbrown.org networks: ['nginx-bridge'] ports: - 21114:21114 - 21115:21115 - 21116:21116 - 21116:21116/udp - 21117:21117 - 21118:21118 - 21119:21119 environment: - MUST_LOGIN=Y - TZ=America/New_York # SPLIT HOSTNAMES — this is deliberate, do not "simplify" it back to one name. # relay.gbrown.org DNS-only (grey cloud) -> 178.156.197.55 # Carries the raw TCP/UDP protocol on 21115-21119. # Cloudflare's proxy only handles HTTP/HTTPS, so this # hostname MUST bypass it. That publishes the VPS IP, # which is acceptable: the firewall still restricts # 80/443 to Cloudflare ranges, so knowing the IP grants # no access to any web service. # rustdesk.gbrown.org Proxied -> NPM -> rustdesk-server:21114 # The web console and API, over TLS via Cloudflare. # Keeping the API on the proxied name is what avoids sending login # credentials over plain HTTP. # RELAY is read by the s6 run script as `hbbs -r $RELAY`. It is what hbbs # hands back to clients when P2P hole-punching fails, so it MUST be set here: # the RUSTDESK_API_* vars below only configure the web console/API, not hbbs. # Left unset, the image defaults to `relay.example.com`, which resolves to # nothing -- so every connection needing a relay (i.e. every off-LAN, # off-WireGuard client behind CGNAT) silently fails. - RELAY=relay.gbrown.org - RUSTDESK_API_RUSTDESK_ID_SERVER=relay.gbrown.org - RUSTDESK_API_RUSTDESK_RELAY_SERVER=relay.gbrown.org - RUSTDESK_API_RUSTDESK_API_SERVER=https://rustdesk.gbrown.org - RUSTDESK_API_RUSTDESK_KEY=WLgvHhau6aa5nDPQutTHeQBpIrOEb8aPXByVBWQwkKc= - RUSTDESK_API_RUSTDESK_JWT_KEY=${RUSTDESK_API_RUSTDESK_JWT_KEY} - RUSTDESK_API_LANG=en - RUSTDESK_API_APP_DISABLE_PWD_LOGIN=true - RUSTDESK_API_ADMIN_TITLE=Gib's Rustdesk - RUSTDESK_API_ADMIN_HELLO=