Browser-based remote desktop gateway for RDP, VNC & SSH, with a WebUI, Multi-GPU encoding, File Sharing & Authentik SSO.
A high-performance, low-latency WebRTC remote desktop container that connects to Windows machines via FreeRDP 3 and streams the display directly to your browser using Selkies. Equipped with a modern WebUI configuration dashboard, persistent profile storage, two-way file sharing, and Authentik SSO / Basic Auth.
- ⚡ Ultra-Low Latency Streaming: Powered by Selkies-GStreamer WebRTC, delivering responsive 60fps streaming with minimal lag.
- 🎯 Multi-Encoder Acceleration:
- CPU Software Encoding: Default fallback (
x264/openh264) that runs on any host. - AMD & Intel GPU Acceleration: Hardware VA-API encoding via
/dev/dri. - NVIDIA GPU Acceleration: Hardware NVENC encoding via
nvidia-container-toolkit.
- CPU Software Encoding: Default fallback (
- 🔐 Comprehensive Authentication:
- Single Sign-On (SSO): Native OIDC with Authentik, Keycloak, Google Workspace, Microsoft Entra ID, or Okta.
- Forward Auth / Reverse Proxy: Out-of-the-box header trust for Authentik Proxy Outpost, Traefik, Authelia, and Cloudflare Access.
- Basic Auth: Standard username and password protection with secure signed session cookies.
- 📁 Bidirectional File Sharing:
- FreeRDP drive redirection mounts the container's
/shareddirectory as\\tsclient\SharedFolderin Windows. - Integrated WebUI File Manager drawer for drag-and-drop file uploads and instant downloads from the remote Windows machine.
- FreeRDP drive redirection mounts the container's
- 🔊 Audio Playback: FreeRDP sound redirection (
/sound:sys:pulse) routed through PulseAudio and WebRTC directly to your browser. - 📋 Clipboard Synchronization: Bidirectional text and clipboard sync between local browser and Windows, including pasting images into RDP sessions, with an on-screen clipboard helper.
- 🎮 In-Stream Floating Toolbar:
- One-click Disconnect (terminates RDP session cleanly and returns to the dashboard).
- Special key injection: Ctrl + Alt + Del, Windows Key (⊞), and Alt + Tab.
- Slide-out drawers for Files and Clipboard.
- True fullscreen toggle.
flowchart TD
subgraph Client["Client Browser"]
WebUI["WebUI Dashboard & File Manager"]
Stream["Embedded Selkies WebRTC Stream"]
end
subgraph Container["ConnectHub Docker Container (:8080)"]
subgraph WebProxy["Nginx Reverse Proxy & Auth Guard"]
API["FastAPI Backend (Session & File Controller)"]
AuthEngine["Auth Module (Basic / OIDC / Forward Auth)"]
Files["/shared (Shared Files) & /config (Profiles)"]
end
subgraph StreamingEngine["Selkies Streaming Core"]
X11["Virtual X11 Display (:1)"]
Pulse["PulseAudio Server"]
GStreamer["GStreamer Pipeline"]
Encoders{"Encoder Selection"}
end
subgraph FreeRDPClient["FreeRDP 3 (xfreerdp)"]
RDPProcess["xfreerdp /v:host /sound /clipboard:files-to:off /drive:SharedFolder,/shared"]
end
end
subgraph Hardware["Host Hardware"]
CPU["CPU Software (x264)"]
NV["NVIDIA (NVENC)"]
AMD["AMD / Intel (VA-API)"]
end
subgraph Windows["Remote Target"]
Win["Windows PC (Port 3389 / RDP)"]
end
Client -->|HTTP / WebRTC| WebProxy
WebProxy --> API
API --> AuthEngine
API --> Files
API -->|Spawns & Monitors| RDPProcess
GStreamer --> Encoders
Encoders --> CPU
Encoders --> NV
Encoders --> AMD
RDPProcess -->|Renders Display| X11
RDPProcess -->|Redirects Audio| Pulse
RDPProcess -->|Mounts Shared Folder| Files
RDPProcess -->|RDP Protocol (TLS/NLA)| Win
X11 --> GStreamer
Pulse --> GStreamer
git clone https://github.com/rangoDJ/ConnectHub.git
cd ConnectHub
cp .env.example .envThe image ghcr.io/rangodj/connecthub:latest (linux/amd64 + linux/arm64) is built by GitHub Actions on every push to main. Every merged pull request is also released as a new version, published as X.Y.Z and X.Y tags with a matching GitHub Release. Pin a version tag (e.g. :1.1) instead of latest if you want to choose when to upgrade. sha-<commit> tags are published too.
docker compose pull
docker compose up -dAccess the WebUI at:
http://<your-server-ip>:8080
https://<your-server-ip>:8443 (self-signed; required for browser clipboard access)
The container uses SELKIES_ENCODER=h264enc, which encodes on the GPU (NVENC or VA-API) when one is passed to the container and falls back to CPU software encoding (x264) when not. AUTO_GPU picks the GPU (true for automatic, or nvidia, amdgpu, intel).
No extra configuration required! The default docker-compose.yml runs anywhere without GPU requirements. To force CPU encoding on a host that does have a GPU, set SELKIES_USE_CPU=true.
- Ensure the user running Docker is in the
videoandrendergroups. - In
docker-compose.yml, uncomment thedevices:block and theDRINODE/DRI_NODElines inside the existingenvironment:list (don't add a secondenvironment:key — YAML would discard the first one):
devices:
- /dev/dri:/dev/dri- Install NVIDIA Container Toolkit on the host.
- In
docker-compose.yml, uncomment thedeploy:block and theNVIDIA_*andAUTO_GPU=nvidialines inside the existingenvironment:list:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]Edit .env to select your preferred authentication model:
Direct open access to the dashboard. Ideal for isolated local subnets or systems behind Tailscale/WireGuard.
AUTH_MODE=noneUsername and password accounts, managed in the WebUI:
AUTH_MODE=basic
ALLOW_SIGNUPS=false- First run: there is no default account and no password in the environment. The first visitor to the login page is asked to create an account. Do this right after starting the container, before anyone else can reach it.
- More accounts: sign-up is off by default. Set
ALLOW_SIGNUPS=trueto show a Create one link on the login page, and set it back tofalseonce everyone has an account. Creating the first account is always allowed. - Changing a password: click Password in the top bar. This signs out that user's other sessions.
- Storage: accounts live in
/config/users.json, with passwords as salted scrypt hashes and the file readable only by the container user. - Forgotten password: remove that user's entry from
/config/users.json(or delete the file to start over) and restart the container.
After 10 failed attempts an IP is locked out for 15 minutes.
Upgrading from
BASIC_AUTH_USER/BASIC_AUTH_PASSWORD: those variables are no longer used. After upgrading, the login page shows the first-account form, so create your account straight away, then remove the old variables from your compose file.
Direct integration with Authentik via OpenID Connect:
- In Authentik Admin Interface:
- Go to Applications -> Providers -> Create Provider.
- Type: OAuth2/OpenID Provider.
- Name:
ConnectHub. - Client type: Confidential.
- Redirect URIs:
http://<your-server-ip-or-domain>:8080/auth/callback. - Scopes: ensure
openid,email,profileare selected. - Note the Client ID and Client Secret.
- Go to Applications -> Create Application.
- Attach the application to your new
ConnectHubprovider.
- In
.env:
AUTH_MODE=oidc
OIDC_ISSUER_URL=https://authentik.yourdomain.com/application/o/connecthub/
OIDC_CLIENT_ID=your_authentik_client_id
OIDC_CLIENT_SECRET=your_authentik_client_secret
OIDC_REDIRECT_URI=http://<your-server-ip-or-domain>:8080/auth/callbackIf you place this container behind an Authentik Embedded/Proxy Outpost, Traefik ForwardAuth, Authelia, or Cloudflare Access:
AUTH_MODE=forward_auth
FORWARD_AUTH_HEADER=X-authentik-username
FORWARD_AUTH_TRUSTED_PROXIES=172.18.0.0/16The application will automatically recognize the authenticated user and grant access without a secondary login prompt.
FORWARD_AUTH_TRUSTED_PROXIES is required: the header is only honoured when the request comes from one of these IPs/CIDRs (your proxy's address or Docker network). Otherwise anyone reaching the container port directly could forge the header. Also avoid publishing the container port publicly in this mode.
Pick the protocol at the top of the connection form. Each session runs fullscreen inside the container's display and is streamed to your browser the same way.
| Protocol | Client | Default port | Notes |
|---|---|---|---|
| RDP | FreeRDP 3 | 3389 | Audio, clipboard, shared folder drive, auto display scaling. |
| VNC | TigerVNC viewer | 5900 | VNC password only (no username). Optional view-only mode. The remote desktop follows the window size if the server supports it (e.g. TigerVNC/x11vnc); otherwise it keeps its own size. |
| SSH | ssh in an xterm |
22 | Password (via sshpass), private key, or type the password in the terminal. Host keys are trusted on first use and stored in /config/.ssh/known_hosts; a changed key is refused. Selecting text copies it to the clipboard. |
Passwords and SSH keys are stored in /config/profiles.json (readable only by the container user) and are never passed on a command line. SSH keys and VNC password files are written to a private temporary directory for the length of the session and deleted afterwards.
A session runs inside the container, not the browser, so closing the tab leaves it running: Windows stays logged in, and a Windows PC can't be used locally while it is. Set IDLE_DISCONNECT_MINUTES to end the session once no dashboard has been open for that long:
IDLE_DISCONNECT_MINUTES=15An open dashboard (either view, even in a background tab) keeps the session alive. Closing every tab, or the computer going to sleep, starts the countdown, and the Session log notes the disconnect. Unset or 0 (the default) keeps sessions running until you disconnect.
File sharing between your client browser and the remote Windows machine is bidirectional and automatic:
- In Windows:
- Open This PC (File Explorer).
- Under Network locations / Redirected drives, you will see
SharedFolder on <client>(path\\tsclient\SharedFolder). - Any files placed into this folder in Windows are saved into the container's
/shareddirectory.
- In the WebUI:
- Click the 📁 Shared Files button in the top navigation bar or the in-stream floating toolbar.
- Upload: Drag-and-drop any file into the drawer to make it instantly accessible inside Windows.
- Download: Click Download next to any file saved from Windows.
- On the Docker Host:
- Files are stored on your host in
./shared(mounted volume).
- Files are stored on your host in
Images can be pasted into an RDP session from Chrome, Edge or Brave. VNC and SSH sessions carry text only, since the TigerVNC viewer and xterm have no image clipboard.
- Open the WebUI over HTTPS (
https://<your-server-ip>:8443). Browsers only let a page read the clipboard over HTTPS. - Copy an image locally, click into the stream and press Ctrl+V. The first time, the browser asks to let the page see your clipboard. Choose Allow.
- Keep Clipboard enabled in the connection profile (it is on by default).
Over plain HTTP (port 8080) a paste still sends the image, but the keystroke can reach Windows before the image does, so the first Ctrl+V may paste the previous clipboard.
Images reach Windows as bitmaps: Ubuntu's FreeRDP build can't convert PNG itself, so the gateway adds a BMP copy of every pasted image. Transparent areas become white.
Files can't be copied through the clipboard: a browser has no way to put a file on your local clipboard, so FreeRDP's file clipboard is turned off. Move files through the shared folder instead (\\tsclient\SharedFolder in Windows, Shared Files in the WebUI; see How File Sharing Works).
To connect to a Windows machine:
- On the Windows computer, go to Settings -> System -> Remote Desktop and toggle Enable Remote Desktop to On.
- If connecting with a local account without a password, Windows Remote Desktop may block it by default. Ensure the account has a password.
- If Network Level Authentication (NLA) is enabled (default), provide the valid Windows Username and Password in the WebUI.
- If using a self-signed certificate on Windows (standard for Windows Pro), keep the 🛡️ Bypass SSL/NLA Warnings checkbox enabled.
ConnectHub/
├── Dockerfile # Multi-stage image with FreeRDP 3, VA-API & Python
├── docker-compose.yml # CPU, AMD/Intel VA-API, and NVIDIA profiles
├── .env.example # Template for authentication and GPU settings
├── .gitignore
├── root/
│ ├── defaults/
│ │ ├── autostart # Virtual X11 session desktop startup
│ │ └── default.conf # Nginx proxy for WebUI, API, and Selkies stream
│ ├── etc/
│ │ └── s6-overlay/s6-rc.d/
│ │ ├── 02-rdp-webui/ # FastAPI backend service supervisor
│ │ └── user/contents.d/
│ └── app/
│ ├── backend/
│ │ ├── main.py # FastAPI router and static server
│ │ ├── auth.py # Basic Auth, Authentik OIDC & Forward Auth
│ │ ├── session_manager.py # RDP/VNC/SSH process supervisor & key injector
│ │ ├── file_manager.py # Upload/download API for /shared
│ │ └── requirements.txt
│ └── frontend/
│ ├── index.html # Single-Page App (Dashboard & Stream View)
│ ├── login.html # Login portal (Basic Auth / Authentik SSO)
│ ├── css/style.css # Dark glassmorphism styling
│ └── js/app.js # Client state, WebRTC iframe, and toolbar controls
├── config/ # Persistent volume: saved profiles and secrets
├── shared/ # Persistent volume: shared files with Windows
└── README.md
The Connection Logs & Diagnostics panel on the dashboard has three tabs, each keeping its last 1,000 lines:
| Tab | Shows |
|---|---|
| Session | Output of the running client (xfreerdp3, the VNC viewer or SSH) and ConnectHub's messages about it. Cleared on each connect. |
| ConnectHub | The WebUI server's own log: logins, sign-ups, OIDC and profile errors. |
| Selkies | The streaming server: encoder, display resizes, audio and clipboard. |
Everything also goes to docker logs connecthub. The dashboard's status and log requests, which repeat every 2 seconds, are left out of it.
Set CLIPBOARD_DEBUG=true on the container to log clipboard transfers in detail: FreeRDP's clipboard channel in the Session tab (from the next connect) and Selkies' debug log in the Selkies tab (after a restart). Selkies' debug log is verbose, so turn it off again when you're done.
- Black screen after clicking Connect: Check the Session tab of the Connection Logs & Diagnostics panel. Common reasons include incorrect Windows credentials or Remote Desktop not being enabled on the target PC.
- Audio not playing: Ensure the 🔊 Audio Playback toggle is enabled before connecting, and that your browser allows autoplay on the gateway URL.
- Pasting an image does nothing: Use the HTTPS address (port 8443) and an RDP session. If you once blocked clipboard access, re-allow it from the site settings (the icon left of the address bar), then reload the page. A group policy on the Windows machine can also turn off clipboard redirection (Do not allow Clipboard redirection under Remote Desktop Session Host → Device and Resource Redirection).
- Ctrl + Alt + Del:
Click the Ctrl+Alt+Del button on the floating stream toolbar. It sends the key sequence (
Ctrl+Alt+End) recognized by FreeRDP to trigger the Windows security screen without triggering your local host's task manager.
Contributions are welcome! See CONTRIBUTING.md for how to report bugs, set up a development environment and open a pull request.