Self-hosted RustDesk server (hbbs ID/rendezvous + hbbr relay) for your own remote desktop network, with Docker Compose.
Last updated: Oct 2026
RustDesk is an open-source remote desktop app similar to TeamViewer or AnyDesk. By running your own server, the clients use your hbbs (ID and rendezvous) and hbbr (relay) instead of the public servers. Connections are end-to-end encrypted with a key pair generated on first start.
Why you'd want it: Help family members or reach your own PCs remotely without relying on, or paying for, a third-party service.
| Item | Value |
|---|---|
| Stack folder | /opt/stacks/rustdesk |
| Category | Identity and security |
| Main image | rustdesk/rustdesk-server:latest |
| Services | 2 |
| Web UI | None (configure the RustDesk clients) |
| Official docs | https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/docker/ |
.env/.secret files from Docker Folders and EnvironmentFolder layout:
/opt/stacks/
├── _shared/
│ ├── .env # shared, non-secret settings
│ └── .secret # passwords, tokens and keys (chmod 600, never commit)
└── rustdesk/
└── compose.yaml
/opt/appdata/rustdesk/ # persistent application data
/opt/stacks/rustdesk/compose.yaml:
services:
hbbs: # ID / signaling server
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs -r <RELAY_HOST>:21117 # public DNS name or IP of this server
env_file:
- /opt/stacks/_shared/.env
- /opt/stacks/_shared/.secret
logging:
driver: json-file
options:
max-file: ${DOCKERLOGGING_MAXFILE:-10} # Max number of log files
max-size: ${DOCKERLOGGING_MAXSIZE:-200k} # Max file size
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
- "21118:21118"
volumes:
- /opt/appdata/rustdesk/data:/root
depends_on:
- hbbr
restart: unless-stopped
hbbr: # Relay server
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr
env_file:
- /opt/stacks/_shared/.env
- /opt/stacks/_shared/.secret
logging:
driver: json-file
options:
max-file: ${DOCKERLOGGING_MAXFILE:-10} # Max number of log files
max-size: ${DOCKERLOGGING_MAXSIZE:-200k} # Max file size
ports:
- "21117:21117"
- "21119:21119"
volumes:
- /opt/appdata/rustdesk/data:/root
restart: unless-stopped
Compose reads these from the shared files with --env-file. Replace every <PLACEHOLDER> with your own value.
/opt/stacks/_shared/.env:
PUID=1000
PGID=1000
TZ=<TZ>
DOCKERLOGGING_MAXFILE=10
DOCKERLOGGING_MAXSIZE=200k
| Variable | File | Purpose | Example |
|---|---|---|---|
PUID |
.env |
User ID the container runs as (see Docker Folders) | 1000 |
PGID |
.env |
Group ID the container runs as | 1000 |
TZ |
.env |
Time zone in tz database format, e.g. America/New_York | <TZ> |
DOCKERLOGGING_MAXFILE |
.env |
Number of rotated container log files to keep | 10 |
DOCKERLOGGING_MAXSIZE |
.env |
Maximum size of each container log file | 200k |
| Host port | Container port | Protocol | Service | Purpose |
|---|---|---|---|---|
| 21115 | 21115 | tcp | hbbs |
NAT type test (hbbs) |
| 21116 | 21116 | tcp | hbbs |
ID registration and hole punching (TCP + UDP) |
| 21116 | 21116 | udp | hbbs |
ID registration and hole punching (TCP + UDP) |
| 21118 | 21118 | tcp | hbbs |
Web client support (hbbs) |
| 21117 | 21117 | tcp | hbbr |
Relay (hbbr) |
| 21119 | 21119 | tcp | hbbr |
Web client support (hbbr) |
| Host path | Container path | Service | Purpose |
|---|---|---|---|
/opt/appdata/rustdesk/data |
/root |
hbbs |
Server keys (id_ed25519, id_ed25519.pub) and database |
/opt/appdata/rustdesk/data |
/root |
hbbr |
Server keys (id_ed25519, id_ed25519.pub) and database |
No limits are set. This app is light, so add deploy.resources.limits only if you need to cap it.
sudo mkdir -p /opt/stacks/rustdesk /opt/appdata/rustdesk
sudo chown -R $USER: /opt/stacks/rustdesk
/opt/stacks/_shared/.env and /opt/stacks/_shared/.secret (chmod 600 .secret)./opt/stacks/rustdesk/compose.yaml.<RELAY_HOST> in the hbbs command with the public DNS name or IP your clients will use.cd /opt/stacks/rustdesk
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret config --quiet
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret up -d
docker compose logs -f./opt/appdata/rustdesk/data.cat /opt/appdata/rustdesk/data/id_ed25519.pub.<RELAY_HOST> as the ID server and relay server, plus the public key.Tip: create a small wrapper such as
alias dc='docker compose --env-file /opt/stacks/_shared/.env --env-file /opt/stacks/_shared/.secret'so every stack uses the same env files.
cd /opt/stacks/rustdesk
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret pull
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret up -d
docker image prune -f
Back up /opt/appdata/rustdesk before major upgrades. Watchtower can automate updates for stateless apps.
docker compose ps and docker compose logs --tail=100.id_ed25519.pub; do not delete the data folder or every client must be updated.<RELAY_HOST> resolves to your public IP.id_ed25519 (the private key).:?err): run the config command above to see which variable is not set in your env files.Educational example. Review every path, port and credential before you deploy, and never publish your .secret file.