# 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://<domain>:21115`                   | 21115   | NAT test                                 |
| `tcp+udp://<domain>:21116`               | 21116   | ID / rendezvous (desktop clients)        |
| `tcp://<domain>:21117`                   | 21117   | Relay (hbbr)                             |
| `ws://<domain>:21118`                    | 21118   | Browser-facing rendezvous WebSocket      |
| `ws://<domain>:21119`                    | 21119   | Browser-facing relay WebSocket           |
| `http://<domain>:21114/admin/`           | 21114   | Admin dashboard (pro-features)           |
| `http://<domain>: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.
