Repository navigation
Installation
The one-command systemd installer, production upgrades, a system-wide unit and Docker.
From the repository root:
./install.shThe 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 keptWith --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.jsonanddata/indexarr.dbwith 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 -fWithout --linger the service starts with the user session and stops at
logout; --linger keeps it active.
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 backupWith --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.shThe outcomes of the three tests (successful upgrade, broken build, failed health with automatic rollback) are in scripts/README.md.
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 -fThe configuration file and the database are created in /var/lib/indexarr.
docker compose up -d --buildThe 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" \
indexarrThe compose file also sets:
-
stop_grace_period: 30s, enough for the HTTP server and the FlareSolverr browsers to close cleanly; -
read_only: truewith atmpfson/tmp,cap_drop: ALLandno-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.
indexarr
Guides
APIs
Sources
Operations
Project