Skip to content

Install with Docker

Marc Pope edited this page Jul 25, 2026 · 5 revisions

Install with Docker

The image is self-contained: php-fpm, nginx, the scheduler, the native web client, and a WebSocket bridge to your RustDesk server. It listens on 8080.

Images are published on every release:

docker pull marcpope/cortendesk:0.9.0
docker pull ghcr.io/marcpope/cortendesk:0.9.0

latest and the <major>.<minor> tag (e.g. 0.9) move only on stable releases, never on a pre-release.

Before you start

You need a running open-source RustDesk server (hbbs/hbbr) and three values from it:

Value Where it comes from
ID server hbbs.example.com:21116
Relay server hbbs.example.com:21117
Public key contents of id_ed25519.pub on the server

CortenDesk does not replace hbbs/hbbr. It runs alongside them and speaks the same HTTP API the RustDesk clients already use.

Quick trial (SQLite, no database to set up)

docker run -d --name cortendesk \
  -p 8080:8080 \
  -v cortendesk-data:/data \
  -e CORTENDESK_ID_SERVER=hbbs.example.com:21116 \
  -e CORTENDESK_RELAY_SERVER=hbbs.example.com:21117 \
  -e CORTENDESK_PUBLIC_KEY="<contents of id_ed25519.pub>" \
  marcpope/cortendesk:0.9.0

Open http://localhost:8080 and sign in as admin / changeme. Change that password immediately.

Good enough to evaluate. For anything real, use MySQL.

Production (Docker Compose with MySQL)

services:
  cortendesk:
    image: marcpope/cortendesk:0.9.0
    ports:
      - "8080:8080"
    environment:
      APP_URL: https://console.example.com
      SESSION_SECURE_COOKIE: "true"

      DB_CONNECTION: mysql
      DB_HOST: db
      DB_DATABASE: cortendesk
      DB_USERNAME: cortendesk
      DB_PASSWORD: use-a-real-password

      CORTENDESK_ID_SERVER: hbbs.example.com:21116
      CORTENDESK_RELAY_SERVER: hbbs.example.com:21117
      CORTENDESK_PUBLIC_KEY: "<contents of id_ed25519.pub>"

      # Web client endpoints as the BROWSER sees them (your TLS proxy):
      CORTENDESK_NATIVE_WEBCLIENT: "true"
      CORTENDESK_WS_ID_URL: wss://console.example.com/ws/id
      CORTENDESK_WS_RELAY_URL: wss://console.example.com/ws/relay
    volumes:
      - cortendesk-data:/data
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: cortendesk
      MYSQL_USER: cortendesk
      MYSQL_PASSWORD: use-a-real-password
      MYSQL_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - cortendesk-db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
      interval: 5s
      timeout: 3s
      retries: 20
    restart: unless-stopped

volumes:
  cortendesk-data:
  cortendesk-db:
docker compose up -d
docker compose logs -f cortendesk

Environment variables

Variable Default Notes
APP_URL http://localhost:8080 Your public URL. Generated links and the OIDC callback depend on it.
APP_KEY generated Left unset, one is generated on first boot and kept in /data/.app_key. Keep that volume.
SESSION_SECURE_COOKIE false Set true when served over HTTPS.
DB_CONNECTION sqlite mysql for anything real.
DB_HOST / DB_DATABASE / DB_USERNAME / DB_PASSWORD Standard Laravel.
CORTENDESK_ID_SERVER host:21116. Also the default host for the ws bridge.
CORTENDESK_RELAY_SERVER host:21117.
CORTENDESK_PUBLIC_KEY Contents of id_ed25519.pub.
CORTENDESK_NATIVE_WEBCLIENT false Enables the in-browser client.
CORTENDESK_WS_ID_URL / CORTENDESK_WS_RELAY_URL wss:// URLs your proxy exposes. Browsers refuse ws:// from an HTTPS page.
RUSTDESK_WS_HOST host of CORTENDESK_ID_SERVER Override only if hbbs/hbbr are reachable at a different address from inside the container.
CORTENDESK_ADMIN_USER admin First-boot account only.
CORTENDESK_ADMIN_PASSWORD changeme First-boot account only.
TRUSTED_PROXIES private ranges Override if your proxy sits outside RFC1918.

Most settings can also be changed later in Settings without restarting.

What happens on first boot

  1. Generates APP_KEY into /data if you did not supply one.
  2. Waits for the database, then runs migrations.
  3. Creates the admin account — a no-op once any user exists.
  4. Renders the nginx config, pointing /ws/id and /ws/relay at your RustDesk server.
  5. Starts php-fpm, nginx, and the Laravel scheduler.

Migrations run on every start, so upgrading is just a new image tag.

TLS and the web client

Put a TLS-terminating proxy in front (Traefik, Caddy, nginx, Cloudflare). The app honours X-Forwarded-*, so no extra configuration is needed beyond setting APP_URL and SESSION_SECURE_COOKIE=true.

The in-browser client needs wss://. The container already bridges /ws/id and /ws/relay to hbbs/hbbr — your proxy just has to forward those two paths with WebSocket upgrade headers, and CORTENDESK_WS_*_URL must be the wss:// URLs as the browser sees them, not the container's internal address.

Caddy, which needs nothing else:

console.example.com {
    reverse_proxy localhost:8080
}

Pointing devices at the console

On each RustDesk client, Settings → Network: set ID Server, Relay Server, Key, and API Server to your console URL. Devices appear within one heartbeat (about fifteen seconds). The console's Settings screen shows copy-paste values for all four.

For unattended deployment, use an API token and the client's --assign flag.

Upgrading

docker compose pull
docker compose up -d

Migrations run automatically on start. Back up the database first — see How to Upgrade.

Backups

Two things matter:

  • The database — everything except the app key.
  • /data — holds the generated APP_KEY. Lose it and encrypted values (SMTP password, OIDC client secret, 2FA secrets) cannot be decrypted, even with a good database backup.
docker compose exec db mysqldump -u cortendesk -p cortendesk > cortendesk.sql
docker run --rm -v cortendesk-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/cortendesk-data.tar.gz /data

Troubleshooting

No devices appear. The clients' API Server is not set to the console, or they cannot reach it. Check Logs → Connections and the container log.

Web client will not connect. CORTENDESK_WS_*_URL must be wss:// and must be reachable from the browser. Check the browser console for a blocked mixed-content or upgrade error.

"Please provide a valid cache path" or permission errors on start. The /data volume is not writable. Recreate it rather than chowning by hand.

Sign-in redirects back to the login page over HTTPS. Set SESSION_SECURE_COOKIE=true and make sure APP_URL uses https://.

Locked out of SSO or 2FA. Break-glass paths exist: CORTENDESK_OIDC_DISABLED=true in the environment, and docker compose exec cortendesk php artisan cortendesk:2fa-reset <username>.

Clone this wiki locally