Motrix Server (Docker)
Motrix Server packages the Motrix download core and aria2 into a non-root, multi-architecture container image for NAS boxes and home servers. It is the same download engine, task model, and settings as the desktop app — only the Electron shell is gone. You reach it from a browser instead.
Tagged releases publish the identical image to both registries:
- Docker Hub —
docker.io/motrixapp/motrix-server - GitHub Container Registry —
ghcr.io/agalwood/motrix-server
Every release image contains linux/amd64 and linux/arm64, and Docker picks the matching manifest for you. 32-bit ARM is not supported.
What the container exposes
Server mode publishes two separate services on two ports. Both are HTTP, and they are not interchangeable.
| Address | What it serves |
|---|---|
http://NAS_HOST:8080 | Web UI, operator API, and the public GET /healthz probe |
http://NAS_HOST:16801 | MDXP endpoint — unary POST /mdxp, the event stream GET /mdxp/events, and device-code pairing for the command-line tool and AI agents |
Before you start
- A 64-bit host (
amd64orarm64) with Docker and the Compose plugin. - Two directories on persistent storage: one for state, one for downloads. Mount them at
/dataand/downloads. - Both directories owned by the numeric UID/GID the container runs as —
1000:1000unless you override it.
Warning
/data holds the SQLite database, settings, aria2 session and DHT state, torrent metadata, the operator token, and installed plugins. Recreating the container without that mount loses all of it. Back up /data; back up /downloads according to your own policy.
Quick start with Docker Compose
Use the compose.yaml shipped in the repository:
name: motrix
services:
server:
image: "${MOTRIX_IMAGE:-motrixapp/motrix-server:latest}"
init: true
read_only: true
user: "${MOTRIX_UID:-1000}:${MOTRIX_GID:-1000}"
security_opt:
- no-new-privileges:true
restart: unless-stopped
stop_grace_period: 2m
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
environment:
MOTRIX_DATA_DIR: /data
MOTRIX_TEMP_DIR: /data/tmp
MOTRIX_PLUGIN_DIR: /data/plugins
MOTRIX_DEFAULT_SAVE_DIR: /downloads
MOTRIX_ALLOWED_SAVE_DIRS: /downloads
MOTRIX_MDXP_HOST: 0.0.0.0
MOTRIX_MDXP_PORT: 16801
MOTRIX_PUBLIC_URL: "${MOTRIX_PUBLIC_URL:-}"
ports:
- "${MOTRIX_WEB_BIND_IP:-${MOTRIX_BIND_IP:-0.0.0.0}}:${MOTRIX_HTTP_PORT:-8080}:8080"
- "${MOTRIX_MDXP_BIND_IP:-${MOTRIX_BIND_IP:-0.0.0.0}}:${MOTRIX_MDXP_PUBLIC_PORT:-16801}:16801"
volumes:
- ./motrix-data:/data
- ./downloads:/downloads
Then prepare the directories and start the service:
mkdir -p motrix-data downloads
# Use a dedicated non-root account; these commands use the current user.
export MOTRIX_UID="$(id -u)"
export MOTRIX_GID="$(id -g)"
chown "$MOTRIX_UID:$MOTRIX_GID" motrix-data downloads
export MOTRIX_PUBLIC_URL='http://nas.example.lan:8080'
docker compose pull server
docker compose up -d --wait
docker compose ps
MOTRIX_IMAGE selects the registry, tag, or digest without editing the file — for example ghcr.io/agalwood/motrix-server:2.0.0, or a @sha256: digest for a fully reproducible deployment. Floating tags such as :latest are convenient on a NAS; an immutable SemVer tag or digest is what you want for controlled upgrades and rollback.
Caution
Never set the runtime UID to 0 or enable privileged mode to work around a mount permission error. Fix the ownership of the two directories instead. Startup write-tests every path it needs and fails with the exact absolute path when one is wrong.
The same deployment with docker run
docker run -d \
--name motrix-server \
--init \
--restart unless-stopped \
--stop-timeout 120 \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 \
--security-opt no-new-privileges:true \
--user "$(id -u):$(id -g)" \
-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
The image already defines its own health check, non-root user, data paths, and graceful SIGTERM handling. MOTRIX_MDXP_HOST=0.0.0.0 is the listener inside the container; the -p options decide which host interfaces can reach it.
First login: the operator token
On first start Motrix generates a random operator token at /data/operator-token, mode 0600. The same token survives restarts and image replacement.
With the bind-mount layout above, read it from the host:
cat motrix-data/operator-token
Open http://NAS_HOST:8080. The web UI shows an Unlock Motrix screen with an Operator token field. Paste the token and click Unlock.
You can set MOTRIX_OPERATOR_TOKEN yourself instead, but environment variables are readable from container metadata — the generated file is the safer default on a single host.
Tip
If unlocking fails, re-read the current /data/operator-token. A token copied from another deployment will never work.
Environment variables you are likely to set
| Variable | Image default | What it does |
|---|---|---|
PORT | 8080 | Web/API listen port inside the container |
MOTRIX_DATA_DIR | /data | State directory; must be absolute and writable |
MOTRIX_DEFAULT_SAVE_DIR | /downloads | Where new tasks save by default |
MOTRIX_ALLOWED_SAVE_DIRS | /downloads | Colon-separated list of absolute roots tasks may write to, enforced server-side |
MOTRIX_PUBLIC_URL | unset | The externally reachable web approval URL handed to pairing clients |
MOTRIX_OPERATOR_TOKEN | generated file | Operator credential, if you’d rather supply it than read the file |
MOTRIX_FFMPEG_PATH | auto-detect | Absolute path to an FFmpeg binary you provide |
LOG_LEVEL | info | Log level written to container stdout |
To add a second download root, mount it and allow it — both, or the task will be rejected:
environment:
MOTRIX_ALLOWED_SAVE_DIRS: /downloads:/archive
volumes:
- /srv/archive:/archive
The full environment reference — plugin sources, secret seed, bind addresses, MDXP listener details — is in the deployment guide linked at the end of this page.
Pairing the CLI and AI agents
The local-socket shortcut the desktop app uses does not exist here, so a remote motrix CLI or agent pairs over MDXP with a device code.
MOTRIX_PUBLIC_URL is the web approval URL returned to that client. It has no localhost default: set it to the URL other machines actually use — the web port (or its reverse-proxy URL), never the MDXP port, and never localhost, 127.0.0.1, or 0.0.0.0. Leaving it unset does not disable pairing, but the client has no useful link to show you.
Start pairing on the client, then approve it one of two ways:
- In the web UI — open Settings → Integration → Command-line tools. The request appears under Pending approvals with its verification code; click Approve (or Deny).
- Over SSH — approve the exact code from inside the running container:
docker compose exec server motrix-admin pairing pending
docker compose exec server motrix-admin pairing approve ABCD-EFGH
docker compose exec server motrix-admin pairing deny ABCD-EFGH
motrix-admin talks to the running server over container loopback only, and never prints the operator credential or the client token. It deliberately has no approve-latest, approve-all, or remote endpoint — you always type the code the client showed you. Web approval remains the normal path; this is the recovery path for headless deployments.
Security boundaries
Plain HTTP is a reasonable choice on a trusted LAN, and Motrix does not force HTTPS on you.
Warning
Do not expose either port to the internet as plain HTTP. Internet access requires TLS termination at a trusted reverse proxy and a firewall in front of the origin ports — for both services. Forwarding only 8080 does not publish MDXP; forwarding only 16801 does not serve the approval UI. Configure the proxy to preserve cookies, authorization headers, and streaming responses.
Never implement that protection by disabling pairing. Remote CLI and agent pairing stays an operator-approved workflow.
The container itself is hardened by default: it runs as a non-root user with a read-only root filesystem, no-new-privileges, and a small noexec tmpfs for /tmp. Keep it that way — do not mount the Docker socket or hand the container your whole filesystem.
How server mode differs from the desktop app
Note
The web UI shows “This web edition is updated by its deployment administrator.” in Settings → About. There is no in-app update button: you upgrade by pulling a new image and recreating the container. Back up /data first, record the digest you are replacing, and read the release notes before crossing a major version.
Other honest differences:
- Browser-extension pairing is desktop-only. First-time extension pairing is a native-messaging flow, so the headless server does not offer it. See Browser extension.
- No FFmpeg in the official image. A plugin that needs it requires a derived image with Alpine’s
ffmpegpackage plusMOTRIX_FFMPEG_PATH=/usr/bin/ffmpeg. - One-click registry install is coming soon in the web build. You can browse the plugin directory, but for now install by uploading a
.moextpackage in the UI, or declare sources withMOTRIX_PLUGIN_INSTALL_URLS/MOTRIX_PLUGIN_IMPORT_DIRSat startup. Installed packages, grants, configuration, and secrets persist under/dataand survive a container replacement. See Plugins.
Everything else — HTTP and BitTorrent tasks, speed limits, trackers, proxies — behaves as documented in the rest of this manual.
NAS platforms
Synology DSM 7 Container Manager and fnOS both import compose.yaml as a project. The shape is the same on either: create the two directories on a real storage pool, give them to a dedicated non-administrator account’s numeric UID/GID, set MOTRIX_UID / MOTRIX_GID to match, set MOTRIX_PUBLIC_URL to the URL your other devices will use, then start the project and wait for the health status before opening the web UI. Do not enable “high privilege” and do not let the NAS build a local image — it should pull the published one. Step-by-step instructions for both platforms are in the deployment guide.
Next steps
- Motrix CLI — install
@motrix/cliand pair it with this server. - Plugins — what plugins can do and how to install them.
- Troubleshooting — when a download won’t start.
For image signing and provenance, tag policy, named volumes, reverse-proxy topologies, upgrade and rollback procedure, /api/diagnostics, the DSM and fnOS walkthroughs, and the complete environment table, read the full deployment guide.