# How to Deploy Motrix as a Headless Docker Server with Device-Code Pairing

> Deploy Motrix headless Docker server for remote clients with device-code pairing. This guide simplifies headless setup for efficient management.

- Repository: [Dr_rOot/Motrix](https://github.com/agalwood/Motrix)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Motrix ships an official multi-architecture Docker image that exposes a Web UI on port 8080 and an MDXP pairing service on port 16801, enabling you to run a fully headless server that authenticates remote clients via a browser-based device-code approval flow.**

The agalwood/Motrix repository provides a production-ready server container that eliminates the need for a desktop environment. When you deploy Motrix as a headless Docker server with device-code pairing, you enable the `@motrix/cli` client to securely exchange credentials through the MDXP (Motrix Download eXchange Protocol) JSON-RPC endpoint.

## Architecture and Network Requirements

The container runtime defined in the `Dockerfile` exposes three core services documented in [`docs/docker-server.md`](https://github.com/agalwood/Motrix/blob/main/docs/docker-server.md):

- **Port 8080**: Serves the Web UI dashboard and the HTTP API consumed by browser clients and CLI agents.
- **Port 16801**: Exposes the MDXP JSON-RPC 2.0 endpoint dedicated exclusively to device-code pairing and remote client initialization.
- **Health endpoint**: The `/healthz` route on port 8080 supports Docker’s built-in health checks to verify container readiness.

Persistent state—including the SQLite database, aria2 session files, plugins, and the operator token—resides in `/data`. Downloaded files are written to `/downloads`. The image executes as the non-root `node` user with UID/GID 1000, requiring both mount points to be writable by this identity.

## Preparing Persistent Storage

Before starting the container, create host directories and align ownership with the container’s user to prevent permission errors when the server writes the operator token to `/data/operator-token`.

```bash
mkdir -p motrix-data downloads
export MOTRIX_UID="$(id -u)"
export MOTRIX_GID="$(id -g)"
chown "$MOTRIX_UID:$MOTRIX_GID" motrix-data downloads

```

## Essential Environment Variables

The pairing flow relies on the `MOTRIX_PUBLIC_URL` variable to generate valid approval URLs that clients can reach.

Set `MOTRIX_PUBLIC_URL` to the externally reachable address of your Web UI (e.g., `http://nas.example.lan:8080`). This value must resolve from the client machine; localhost addresses will break the browser redirect during pairing. Additionally, bind the MDXP service to all interfaces by setting `MOTRIX_MDXP_HOST=0.0.0.0` so remote agents can connect to port 16801.

## Deployment Options

You can launch the server using Docker Compose configurations included in the repository or a standalone `docker run` command.

### Docker Compose (Bind Mount)

The [`compose.yaml`](https://github.com/agalwood/Motrix/blob/main/compose.yaml) file at the repository root mounts host directories directly into the container.

```bash
export MOTRIX_PUBLIC_URL='http://nas.example.lan:8080'
docker compose pull server
docker compose up -d --wait
docker compose ps

```

### Docker Compose (Named Volumes)

For NAS environments or scenarios preferring Docker-managed storage, use [`compose.named-volumes.yaml`](https://github.com/agalwood/Motrix/blob/main/compose.named-volumes.yaml):

```bash
export MOTRIX_PUBLIC_URL='http://nas.example.lan:8080'
docker compose -f compose.named-volumes.yaml pull server
docker compose -f compose.named-volumes.yaml up -d --wait

```

### Standalone Docker Run

For custom orchestration, run the container directly with security-hardened flags:

```bash
docker run -d \
  --name motrix-server \
  --init \
  --restart unless-stopped \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 \
  --security-opt no-new-privileges:true \
  --user "$MOTRIX_UID:$MOTRIX_GID" \
  -e MOTRIX_PUBLIC_URL='http://nas.example.lan:8080' \
  -e MOTRIX_MDXP_HOST=0.0.0.0 \
  -p 8080:8080 \
  -p 16801:16801 \
  -v "$(pwd)/motrix-data:/data" \
  -v "$(pwd)/downloads:/downloads" \
  motrixapp/motrix-server:latest

```

## Device-Code Pairing Workflow

Once the server is healthy, authenticate remote clients using the three-step protocol implemented in `dist/server/motrix-admin.mjs` and the `@motrix/cli` package.

**1. Initiate pairing on the client:**

```bash
motrix pair --name my-nas

```

The CLI contacts the MDXP endpoint at port 16801 and displays a short device code (e.g., `ABCD-EFGH`).

**2. Approve via browser:**

Open the URL specified in `MOTRIX_PUBLIC_URL` in any browser, log in using the operator token found at `motrix-data/operator-token`, and submit the device code shown in the terminal.

**3. Automatic token issuance:**

Upon approval, the server returns a long-lived authentication token that the CLI stores locally for subsequent API calls to port 8080. The client can now execute commands like `motrix add`, `motrix list`, and `motrix watch --stats`.

### CLI-Based Approval

If you have SSH access to the Docker host, you can approve pending requests directly via `motrix-admin` without opening a browser:

```bash
docker compose exec server motrix-admin pairing pending
docker compose exec server motrix-admin pairing approve ABCD-EFGH

```

Deny suspicious requests using:

```bash
docker compose exec server motrix-admin pairing deny ABCD-EFGH

```

## Health Checks and Diagnostics

Verify container readiness using the built-in health endpoint:

```bash
curl --fail http://127.0.0.1:8080/healthz

```

Retrieve server diagnostics by authenticating with the operator token:

```bash
TOKEN="$(cat motrix-data/operator-token)"
curl --fail --header "Authorization: Bearer ${TOKEN}" http://127.0.0.1:8080/api/diagnostics

```

## Security Considerations

By default, MDXP and Web traffic travels over unencrypted HTTP, which is acceptable for trusted LANs. The pairing mechanism itself does not enforce HTTPS, so for Internet-facing deployments you should terminate TLS at a reverse proxy and restrict origin ports appropriately. The container runs with `--read-only` and `--security-opt no-new-privileges:true` to minimize attack surface, storing mutable state only in the mounted `/data` and `/downloads` volumes.

## Summary

- Deploy the agalwood/Motrix server image using [`compose.yaml`](https://github.com/agalwood/Motrix/blob/main/compose.yaml) or `docker run`, exposing ports 8080 and 16801.
- Mount host directories to `/data` and `/downloads`, ensuring ownership matches UID/GID 1000.
- Set `MOTRIX_PUBLIC_URL` to a non-localhost address so clients can complete browser-based pairing.
- Execute `motrix pair --name <device>` on remote machines, then approve codes via the Web UI or `motrix-admin pairing approve`.
- Retrieve the operator token from `motrix-data/operator-token` to administer the server or query diagnostic endpoints.

## Frequently Asked Questions

### Where is the operator token stored after the first start?

The server generates the operator token at `/data/operator-token` inside the container, which maps to `motrix-data/operator-token` on your host when using bind mounts. Inspect it with `cat motrix-data/operator-token` and use this value to log into the Web UI or authorize diagnostic API requests.

### Can I run the container as root or with a different user ID?

The official image is designed to run as the `node` user (1000:1000). While you can override this with `--user`, doing so may cause permission mismatches with pre-existing files in `/data`. It is safer to adjust host directory ownership to match UID 1000 rather than changing the container user.

### Why does my client show a device code but the Web UI never sees the request?

This usually indicates that `MOTRIX_PUBLIC_URL` is set to `http://localhost:8080` or an internal Docker network address that the browser cannot resolve. The MDXP service on port 16801 issues the code, but the browser must reach the Web UI on port 8080 at the exact URL defined in `MOTRIX_PUBLIC_URL` to complete the OAuth-style approval flow.

### Is HTTPS required for device-code pairing to work?

No. The pairing protocol functions over plain HTTP, making it suitable for local networks. However, if exposing port 8080 or 16801 to the Internet, you should place a reverse proxy handling TLS termination in front of the container to encrypt traffic and protect the operator token and authentication flows.