# RustDesk Server — Docker Compose Deployment The repo ships a `docker-compose.yml` that **builds the server from source** (this fork's `pro-features` branch) and runs `hbbs` + `hbbr` as two containers. No prebuilt image is pulled — every `docker compose build` clones the configured Git URL and runs `cargo build --release` inside the build stage. For the runtime flag reference (CLI options accepted by `hbbs` itself), see [CONFIGURATION.md](CONFIGURATION.md). This document only covers the Compose glue. --- ## Quick start ```bash cp .env.example .env # edit .env — at minimum set RUSTDESK_DOMAIN, and change # RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD before the first boot docker compose up -d --build ``` The bootstrap admin (default `admin` / `changeme`) is seeded into the `users` table on the **first** boot only — once the row exists, those flags are ignored. If you boot with the default password and forget to change it, rotate it via the admin UI; if you forget the password entirely, delete `./data/db_v2.sqlite3` (loses all server-side state) or edit the `users` row with `sqlite3` directly. First build pulls the Rust toolchain image and compiles the workspace; expect several minutes. Subsequent builds reuse the cargo cache layer unless the Git ref or build args change. After it boots: | Endpoint | Port | Purpose | |------------------------------------------|---------|------------------------------------------| | `tcp://:21115` | 21115 | NAT test | | `tcp+udp://:21116` | 21116 | ID / rendezvous (desktop clients) | | `tcp://:21117` | 21117 | Relay (hbbr) | | `ws://:21118` | 21118 | Browser-facing rendezvous WebSocket | | `ws://:21119` | 21119 | Browser-facing relay WebSocket | | `http://:21114/admin/` | 21114 | Admin dashboard (pro-features) | | `http://:21114/api/*` | 21114 | Management API (pro-features) | Persistent state — including the auto-generated `id_ed25519` keypair and the SQLite database — lives in `./data/` (bind-mounted to `/root` in both containers). --- ## Files | File | Role | |-----------------------------|---------------------------------------------------------------------------------------| | `docker-compose.yml` | Two services (`hbbs`, `hbbr`) sharing one image built from `docker/Dockerfile.source`.| | `docker/Dockerfile.source` | Multi-stage build: clones the repo, runs `cargo build --release`, copies binaries into a `debian:bookworm-slim` runtime. | | `.env.example` | Documented template; copy to `.env`. | | `data/` | Created on first run. Contains keypair + SQLite DB. **Back this up.** | The legacy single-stage `docker/Dockerfile` (busybox + s6-overlay, expects prebuilt binaries) and `docker-classic/Dockerfile` are unrelated to this flow and unused by `docker-compose.yml`. --- ## Environment variables These are read by `docker-compose.yml` from `.env`. Compose ships them through to the container as command-line flags or `environment:` entries, not as raw process env (with the exception of `RUST_LOG` and `ALWAYS_USE_RELAY`, which `hbbs` reads from env directly). ### Runtime | Variable | Default | Effect | |--------------------------------|-----------------|-------------------------------------------------------------------------------------------------------| | `RUSTDESK_DOMAIN` | **required** | Public hostname clients connect to. Passed as `hbbs -r ${RUSTDESK_DOMAIN}:21117`. | | `RUSTDESK_BOOTSTRAP_ADMIN_USERNAME` | `admin` | Seeded as the initial admin on **first boot only** (when the `users` table is empty). Ignored on subsequent restarts. Empty disables the bootstrap. | | `RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD` | `changeme` | Same — bcrypt-hashed at insert. Change this in `.env` before the first `up`, or rotate via the admin UI immediately after. | | `RUSTDESK_KEY` | `-` | Pre-shared key. `-` = auto-generate on first boot (written to `./data/id_ed25519{,.pub}`); `_` = encrypted-only with auto-key; or paste a base64 public key to pin it. Applied to **both** `hbbs` and `hbbr` via `-k`. | | `RUSTDESK_HTTP_PORT` | `21114` | Pro-features admin API + dashboard port. Set to `0` to disable HTTP entirely. The host port published is the same value. | | `RUSTDESK_ALWAYS_USE_RELAY` | `N` | Force every session through the relay even on LAN. Read from env by hbbs (any non-empty/non-`N` value enables). | | `RUST_LOG` | `info` | Log filter. e.g. `debug`, `hbbs=debug,sqlx=warn`. | ### Build source | Variable | Default | Effect | |------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------| | `RUSTDESK_GIT_URL` | `https://gitea.cstudio.ch/mike/rustdesk-server.git` | Repo cloned inside the builder stage. | | `RUSTDESK_GIT_BRANCH` | `pro-features` | Branch / tag / commit to check out (`--branch` so it must be a ref name, not a SHA). | | `DATABASE_URL` | unset (uses the cloned repo's `.env`) | Overrides the `DATABASE_URL` sqlx reads at compile time. Rarely needed — see below. | Build-arg changes only take effect when the image is rebuilt: `docker compose build --no-cache hbbs` (or `up -d --build`). --- ## Why `DATABASE_URL` is a build-time concern `hbbs` uses `sqlx::query!` macros, which verify SQL **at compile time** by running the queries against a real SQLite database. The repo includes a checked-in `db_v2.sqlite3` with the schema pre-applied, and a tracked `.env` file pointing at it (`DATABASE_URL=sqlite://./db_v2.sqlite3`). Cargo automatically reads `.env` from the project root, so `cargo build` inside the builder stage Just Works without any explicit configuration. You only need to set `DATABASE_URL` in the Compose `.env` if you fork the schema or want to point the compile-time check at a different SQLite file. At **runtime** the binary opens its own DB under `/root/` (your `./data/` bind mount) — that path is not configurable via this variable. --- ## Adding extra `hbbs` flags `docker-compose.yml` only wires the most common flags. To pass others (SMTP, OIDC, recording dir, audit retention, …), edit the `command:` block of the `hbbs` service. Example — enable SMTP and set the public base URL needed for OIDC callbacks: ```yaml command: > hbbs -r ${RUSTDESK_DOMAIN:?...}:21117 -k ${RUSTDESK_KEY:--} --http-port ${RUSTDESK_HTTP_PORT:-21114} --admin-ui-dir /opt/rustdesk/admin_ui --bootstrap-admin-username=${RUSTDESK_BOOTSTRAP_ADMIN_USERNAME:-} --bootstrap-admin-password=${RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD:-} --public-base-url https://${RUSTDESK_DOMAIN}:${RUSTDESK_HTTP_PORT:-21114} --smtp-host ${SMTP_HOST} --smtp-user ${SMTP_USER} --smtp-pass ${SMTP_PASS} --smtp-from ${SMTP_FROM} ``` Then add the matching variables to `.env`. The full flag list lives in [CONFIGURATION.md](CONFIGURATION.md). If you mount an `oidc.toml`, drop it into `./data/` and pass `--oidc-config /root/oidc.toml`. --- ## Operational notes **Upgrading.** Pull the latest commit on `pro-features` and rebuild: ```bash docker compose build --pull --no-cache docker compose up -d ``` The `--pull` refreshes the Rust toolchain base image; `--no-cache` forces a fresh `git clone` (otherwise Docker will reuse the cached clone layer). Alternatively, bump `RUSTDESK_GIT_BRANCH` to a tag and rebuild — the changed build arg invalidates the clone layer automatically. **Logs.** `docker compose logs -f hbbs` (or `hbbr`). Both containers run the binary in the foreground. **Persistence.** Everything that matters is in `./data/`: `id_ed25519{,.pub}` (the server keypair — losing this invalidates every existing client) and `db_v2.sqlite3` (users, address books, audit, etc.). Back up the whole directory. **TLS.** The server itself speaks plain HTTP on 21114 and plain WebSocket on 21118 / 21119. Front it with nginx or Caddy for TLS — see the "TLS deployment" section in `CONFIGURATION.md`. When you do, set `--http-listen=127.0.0.1` and `--ws-listen=127.0.0.1` in the `command:` block so the reverse proxy can claim the public ports. **Building behind a proxy.** Pass `HTTP_PROXY` / `HTTPS_PROXY` build args through Compose: ```yaml build: <<: *rustdesk-build args: HTTP_PROXY: http://proxy.internal:3128 HTTPS_PROXY: http://proxy.internal:3128 ``` **Resource hint.** Cold compile takes ~3–5 GB of RAM for the linker step (LTO + `codegen-units = 1`). On a small VPS, build the image on a beefier machine, push to a registry, and pull from the VPS instead — set the `image:` field to a registry tag and drop the `build:` block.