Skip to content

Installation

buzzqw edited this page Oct 30, 2026 · 1 revision

Installation

The one-command systemd installer, production upgrades, a system-wide unit and Docker.

systemd (user service)

One-command installer (recommended)

From the repository root:

./install.sh

The installer builds bin/indexarr when Go and make are available (otherwise it uses the existing binary), writes data/indexarr.json with private permissions, installs and starts ~/.config/systemd/user/indexarr.service, and checks /healthz.

./install.sh --listen 127.0.0.1:8788   # local only
./install.sh --binary /path/indexarr   # prebuilt binary, no Go needed
./install.sh --linger                  # stays active after logout
./install.sh --dry-run                 # print the plan and the unit
sudo ./install.sh --system             # system service in /var/lib/indexarr
./install.sh --status
./install.sh --uninstall               # data is kept

With --system the script creates the indexarr account when missing, installs /usr/local/bin/indexarr, writes /etc/systemd/system/indexarr.service and keeps the data in /var/lib/indexarr. scripts/install-user-service.sh is the same installer; the root install.sh is only the convenient path.

It creates:

  • ~/.config/systemd/user/indexarr.service, enabled at login;
  • data/indexarr.json and data/indexarr.db with private permissions (700/600);
  • starts the service and checks /healthz.

The default is :8788 on all interfaces: on the LAN the address is http://<server-IP>:8788/. The script also prints the detected LAN address and checks that the port responds there, not only on 127.0.0.1. The address passed with --listen is written into the unit and takes precedence over listen in the configuration file. If you want to limit it to the local machine only, use --listen 127.0.0.1:8788 and run the script again.

The UI has no authentication until you set a username and password in Settings: do that before exposing it to the LAN.

Logs and status:

systemctl --user status indexarr
journalctl --user -u indexarr -f

Without --linger the service starts with the user session and stops at logout; --linger keeps it active.

Production upgrade (data preserved)

scripts/upgrade.sh updates the binary without wiping the database. By default it is a dry run: it validates the prerequisites, builds cmd/indexarr in a temporary directory and prints the plan, without stopping the service.

./scripts/upgrade.sh                       # dry run
./scripts/upgrade.sh --apply --yes         # production upgrade
./scripts/upgrade.sh --rollback --yes      # restore the latest backup

With --apply the build happens before stopping the service. Then it creates a consistent backup with VACUUM INTO in data/backups/pre-upgrade-<timestamp>.db, saves the previous binary as bin/indexarr.old-<timestamp>, stops the service, installs the new binary and starts again. It checks /healthz with the new version, t=caps and a Torznab search with the API key read from the DB (never printed). If a check fails it automatically restores the DB and the binary and restarts the previous version.

The rehearsal on a copy uses --data-dir, --port 18510, --no-systemd and temporary binaries; it does not touch :8788, data/, bin/indexarr or systemd:

./scripts/upgrade_test.sh

The outcomes of the three tests (successful upgrade, broken build, failed health with automatic rollback) are in scripts/README.md.

System-wide systemd service

If you prefer a global service, use the example examples/indexarr.service. Create the user and the directory, copy the binary and the unit:

sudo useradd --system --home /var/lib/indexarr --shell /usr/sbin/nologin indexarr
sudo install -d -o indexarr -g indexarr /var/lib/indexarr
sudo install -m 0755 bin/indexarr /usr/local/bin/indexarr
sudo install -m 0644 examples/indexarr.service /etc/systemd/system/indexarr.service
sudo systemctl daemon-reload
sudo systemctl enable --now indexarr
journalctl -u indexarr -f

The configuration file and the database are created in /var/lib/indexarr.

Docker

docker compose up -d --build

The image is multi-stage (golang:1.26-alpine builds a pure-Go binary, the runtime is alpine:3.21), runs as the non-root UID/GID 10001, and the entry point sets umask 077: indexarr.db and the vault key are created 0600, not 0644.

The named volume indexarr-data contains indexarr.json and the database and is initialized with the right owner. A bind mount must be prepared by hand:

mkdir -p ./data && sudo chown -R 10001:10001 ./data
docker run -d --name indexarr -p 8788:8788 \
  -v "$PWD/data:/data" \
  indexarr

The compose file also sets:

  • stop_grace_period: 30s, enough for the HTTP server and the FlareSolverr browsers to close cleanly;
  • read_only: true with a tmpfs on /tmp, cap_drop: ALL and no-new-privileges;
  • INDEXARR_LISTEN=:8788: change it to move the internal port. When you do, update the port mapping and the healthcheck URL too.

Build another architecture with buildx (the SQLite driver is pure Go, so no emulation is needed for the compile):

docker buildx build --platform linux/amd64,linux/arm64 -t indexarr .

The default port is 8788, chosen to avoid 8484 used by qBittorrent in many installations. The default applies to new installations: an existing indexarr.json keeps the listen value written at first start. If you upgrade a container created with the old image, update the mapping to 8788:8788 or set INDEXARR_LISTEN=:8484 to stay on the previous port.

The volume does not contain the vault key when you use INDEXARR_MASTER_KEY_FILE with a mounted secret (example in Vault or INDEXARR_MASTER_KEY. Without a key the service starts, but indexers with encrypted secrets are not readable.


Part of the indexarr documentation.

Clone this wiki locally