Remap betterdesk at entrypoint start so Synology/NAS bind mounts can match host ownership without Compose user: overrides. Refs #376 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
15 KiB
🐳 Docker Installation Guide - BetterDesk Console
Complete guide for running BetterDesk Console with RustDesk in Docker containers.
Images: Pre-built images are published to GitHub Container Registry (
ghcr.io/unitronix/…) with version tags aligned to CHANGELOG.md. Use DOCKER_QUICKSTART.md anddocker-compose.quick.ymlto pull by tag. For custom changes, build locally withdocker compose buildordocker compose up --build(not on Docker Hub).
Table of Contents
- Quick Start (Recommended)
- Docker Compose Setup
- Manual Docker Setup
- Troubleshooting
- Migration from Native Install
Quick Start (Recommended)
Option 1: Automatic Setup (Easiest)
# Clone repository
git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git
cd Rustdesk-FreeConsole
# Run quick setup
chmod +x docker-quickstart.sh
./docker-quickstart.sh
This script will:
- ✅ Create docker-compose environment
- ✅ Set up data directories
- ✅ Ask about existing RustDesk data import
- ✅ Start all services
- ✅ Show access URLs
Option 2: Custom Installation
# Use the Docker installer with custom options
chmod +x install-docker.sh
sudo ./install-docker.sh
This installer provides:
- ✅ Path selection for existing RustDesk data
- ✅ Container vs volume installation modes
- ✅ Database migration
- ✅ Binary deployment
- ✅ Service configuration
1. Create Project Directory
mkdir -p /opt/betterdesk-docker
cd /opt/betterdesk-docker
2. Create docker-compose.yml
version: '3.8'
services:
# RustDesk HBBS (Signal Server) with BetterDesk API
hbbs:
image: rustdesk/rustdesk-server:latest
container_name: betterdesk-hbbs
command: hbbs -k _ --api-port 21114
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
- "21114:21114" # API port for BetterDesk
volumes:
- ./data:/root
environment:
- ALWAYS_USE_RELAY=N
networks:
- betterdesk-net
restart: unless-stopped
# RustDesk HBBR (Relay Server)
hbbr:
image: rustdesk/rustdesk-server:latest
container_name: betterdesk-hbbr
command: hbbr -k _
ports:
- "21117:21117"
volumes:
- ./data:/root
networks:
- betterdesk-net
restart: unless-stopped
depends_on:
- hbbs
# BetterDesk Web Console
console:
build:
context: ./console
dockerfile: Dockerfile
container_name: betterdesk-console
ports:
- "5000:5000"
volumes:
- ./data:/opt/rustdesk:ro # Read-only access to RustDesk data
- ./console-data:/app/data # Console-specific data
environment:
- DB_PATH=/opt/rustdesk/db_v2.sqlite3
- API_KEY_PATH=/opt/rustdesk/.api_key
- PUB_KEY_PATH=/opt/rustdesk/id_ed25519.pub
- FLASK_SECRET_KEY=${FLASK_SECRET_KEY:?Set FLASK_SECRET_KEY in .env or export it}
networks:
- betterdesk-net
restart: unless-stopped
depends_on:
- hbbs
networks:
betterdesk-net:
driver: bridge
3. Create Console Dockerfile
mkdir -p console
cat > console/Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
RUN apt-get update && apt-get install -y \
sqlite3 \
&& rm -rf /var/lib/apt/lists/*
# Copy application files
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Create data directory
RUN mkdir -p /app/data
EXPOSE 5000
# Run with gunicorn for production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
EOF
4. Create Console Requirements
cat > console/requirements.txt << 'EOF'
flask>=2.0.0
flask-wtf>=1.0.0
flask-limiter>=3.0.0
bcrypt>=4.0.0
markupsafe>=2.1.0
gunicorn>=21.0.0
requests>=2.28.0
EOF
5. Copy Console Files
# Clone repository first if you haven't
git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git /tmp/betterdesk
# Copy web files
cp -r /tmp/betterdesk/web/* console/
6. Start Services
# Generate a random secret key
export FLASK_SECRET_KEY=$(openssl rand -hex 32)
# Start all services
docker-compose up -d
# Check status
docker-compose ps
docker-compose logs -f
7. Access Console
- Web Console: http://your-server-ip:5000
- Default Login: admin / (check logs for password)
Docker Compose Setup (Full)
Complete docker-compose.yml with All Options
version: '3.8'
services:
hbbs:
image: rustdesk/rustdesk-server:latest
container_name: betterdesk-hbbs
hostname: betterdesk-hbbs
command: hbbs -k _ --api-port 21114
ports:
- "21115:21115" # TCP hole punching
- "21116:21116/tcp" # TCP relay
- "21116:21116/udp" # UDP hole punching
- "21114:21114" # HTTP API
volumes:
- betterdesk-data:/root
environment:
- ALWAYS_USE_RELAY=N
- ENCRYPTED_ONLY=1
- DB_URL=./db_v2.sqlite3
networks:
betterdesk-net:
aliases:
- hbbs
restart: unless-stopped
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "21116"]
interval: 30s
timeout: 10s
retries: 3
hbbr:
image: rustdesk/rustdesk-server:latest
container_name: betterdesk-hbbr
hostname: betterdesk-hbbr
command: hbbr -k _
ports:
- "21117:21117" # Relay port
volumes:
- betterdesk-data:/root
networks:
betterdesk-net:
aliases:
- hbbr
restart: unless-stopped
depends_on:
hbbs:
condition: service_healthy
console:
build:
context: ./console
dockerfile: Dockerfile
container_name: betterdesk-console
hostname: betterdesk-console
ports:
- "5000:5000"
volumes:
- betterdesk-data:/opt/rustdesk:ro
- console-data:/app/data
environment:
- DB_PATH=/opt/rustdesk/db_v2.sqlite3
- API_KEY_PATH=/opt/rustdesk/.api_key
- PUB_KEY_PATH=/opt/rustdesk/id_ed25519.pub
- HBBS_API_URL=http://hbbs:21114/api
- FLASK_SECRET_KEY=${FLASK_SECRET_KEY}
- FLASK_ENV=production
networks:
- betterdesk-net
restart: unless-stopped
depends_on:
- hbbs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
betterdesk-data:
driver: local
console-data:
driver: local
networks:
betterdesk-net:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
Using Pre-built BetterDesk Binaries in Docker
If you want to use the enhanced HBBS with ban enforcement:
1. Custom Dockerfile for HBBS
FROM debian:bullseye-slim
WORKDIR /opt/rustdesk
# Install dependencies
RUN apt-get update && apt-get install -y \
ca-certificates \
libsqlite3-0 \
&& rm -rf /var/lib/apt/lists/*
# Copy precompiled binaries
COPY hbbs-v8-api /opt/rustdesk/hbbs
COPY hbbr-v8-api /opt/rustdesk/hbbr
RUN chmod +x /opt/rustdesk/hbbs /opt/rustdesk/hbbr
# Create data directory
RUN mkdir -p /root
WORKDIR /root
EXPOSE 21115 21116 21116/udp 21117 21114
ENTRYPOINT ["/opt/rustdesk/hbbs"]
CMD ["-k", "_", "--api-port", "21114"]
2. Build and Run
# Copy binaries from repository
cp hbbs-patch/bin-with-api/hbbs-v8-api ./hbbs-v8-api
cp hbbs-patch/bin-with-api/hbbr-v8-api ./hbbr-v8-api
# Build custom image
docker build -t betterdesk-hbbs:v8 -f Dockerfile.hbbs .
# Update docker-compose.yml to use custom image
# Replace: image: rustdesk/rustdesk-server:latest
# With: image: betterdesk-hbbs:v8
Manual Docker Setup
Individual Container Commands
# Create network
docker network create betterdesk-net
# Create data volume
docker volume create betterdesk-data
# Run HBBS
docker run -d \
--name betterdesk-hbbs \
--network betterdesk-net \
-p 21115:21115 \
-p 21116:21116 \
-p 21116:21116/udp \
-p 21114:21114 \
-v betterdesk-data:/root \
rustdesk/rustdesk-server:latest \
hbbs -k _ --api-port 21114
# Run HBBR
docker run -d \
--name betterdesk-hbbr \
--network betterdesk-net \
-p 21117:21117 \
-v betterdesk-data:/root \
rustdesk/rustdesk-server:latest \
hbbr -k _
# Run Console (after building)
docker run -d \
--name betterdesk-console \
--network betterdesk-net \
-p 5000:5000 \
-v betterdesk-data:/opt/rustdesk:ro \
-e DB_PATH=/opt/rustdesk/db_v2.sqlite3 \
-e FLASK_SECRET_KEY=$(openssl rand -hex 32) \
betterdesk-console:latest
Troubleshooting
Issue: Installation Script Not Detecting Docker
Symptom:
✗ Could not detect current version
✗ Installation directory not found
Solution: The installation script is for native installations. For Docker, use docker-compose as shown above.
Issue: Devices Show as Offline
Cause: Database missing last_online column.
Solution:
# Access HBBS container
docker exec -it betterdesk-hbbs /bin/sh
# Add missing column
sqlite3 /root/db_v2.sqlite3 "ALTER TABLE peer ADD COLUMN last_online TEXT;"
sqlite3 /root/db_v2.sqlite3 "ALTER TABLE peer ADD COLUMN is_deleted INTEGER DEFAULT 0;"
# Restart container
docker restart betterdesk-hbbs betterdesk-console
Issue: Cannot Connect to API
Solution:
# Check if API is responding
docker exec betterdesk-hbbs curl -s http://localhost:21114/api/health
# Check logs
docker logs betterdesk-hbbs --tail 50
# Verify port mapping
docker port betterdesk-hbbs
Issue: Console Cannot Access Database
Solution:
# Check volume mounts
docker inspect betterdesk-console | grep Mounts -A 20
# Verify database exists
docker exec betterdesk-hbbs ls -la /root/*.sqlite3
# Check permissions
docker exec betterdesk-console ls -la /opt/rustdesk/
Viewing Logs
# All services
docker-compose logs -f
# Specific service
docker-compose logs -f hbbs
docker-compose logs -f console
# Last 100 lines
docker logs betterdesk-hbbs --tail 100
Migration from Native Install
1. Backup Existing Data
sudo cp -r /opt/rustdesk /opt/rustdesk-backup
sudo cp -r /opt/BetterDeskConsole /opt/BetterDeskConsole-backup
2. Stop Native Services
sudo systemctl stop hbbs hbbr betterdesk
sudo systemctl disable hbbs hbbr betterdesk
3. Copy Data to Docker Volume
# Create directory for Docker data
mkdir -p /opt/betterdesk-docker/data
# Copy RustDesk data
sudo cp /opt/rustdesk/db_v2.sqlite3 /opt/betterdesk-docker/data/
sudo cp /opt/rustdesk/id_ed25519* /opt/betterdesk-docker/data/
sudo cp /opt/rustdesk/.api_key /opt/betterdesk-docker/data/
# Set permissions to the container app user (default PUID/PGID 10001)
sudo chown -R 10001:10001 /opt/betterdesk-docker/data
# Or, if you set PUID/PGID in compose/.env:
# sudo chown -R "${PUID}:${PGID}" /opt/betterdesk-docker/data
4. Update docker-compose.yml
Use bind mount instead of named volume (paths are /opt/rustdesk and /app/data, not /root):
volumes:
- /opt/betterdesk-docker/data:/opt/rustdesk
environment:
- PUID=10001
- PGID=10001
5. Start Docker Services
cd /opt/betterdesk-docker
docker-compose up -d
Security Considerations
Production Recommendations
- Use HTTPS - Put a reverse proxy (nginx/traefik) in front
- Limit Network Access - Use firewall rules
- Change Default Password - Immediately after first login
- Regular Backups - Backup the data volume
- Update Regularly - Pull latest images
Nginx Reverse Proxy Example
server {
listen 443 ssl http2;
server_name betterdesk.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/betterdesk.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/betterdesk.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Reverse Proxy with WSS (RustDesk Clients)
When BetterDesk runs in Docker and a reverse proxy (Nginx, Nginx Proxy Manager, Caddy, Traefik) terminates HTTPS on the host, the console and RustDesk WSS endpoints use different upstream ports.
| Public path | Host upstream | Container service | Purpose |
|---|---|---|---|
/ (panel) |
http://HOST:5000 |
console |
Web admin UI |
/ws/id |
http://HOST:21118 |
server (Go) |
RustDesk signal / rendezvous WSS |
/ws/relay |
http://HOST:21119 |
server (Go) |
RustDesk relay WSS |
Docker Compose publishes 21118 and 21119 on the host by default
(see docker-compose.yml). The proxy on the host should target 127.0.0.1
or the host LAN IP — not the container name — when the proxy runs outside the
Docker bridge network.
Do not route /ws/id or /ws/relay to port 5000. The console exposes
different WebSocket paths (/ws/rendezvous, chat, remote viewer, etc.).
Example nginx locations (add to the same server block as the panel):
location = /ws/id {
proxy_pass http://127.0.0.1:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 120s;
}
location = /ws/relay {
proxy_pass http://127.0.0.1:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 120s;
}
Nginx Proxy Manager: create one Proxy Host for your domain with the main
forward to :5000 (Websockets ON), then add Custom Locations for /ws/id →
:21118 and /ws/relay → :21119 (Websockets ON on each). Use http://
backends unless Enterprise TLS is enabled on the Go server.
Verify from the Docker host:
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://127.0.0.1:21118/ws/id
Expected: HTTP/1.1 101 Switching Protocols.
Full troubleshooting (TLS SNI errors, keepalive timeouts): see HTTPS Setup — RustDesk WSS.
Updated: January 2026 Version: v1.5.0