SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

Self-host Chaptarr for audiobooks on a VPS

Readarr retired in 2025. Chaptarr is the fork that runs audiobooks and ebooks in one instance: a Compose service, PUID and PGID, and the metadata break.

What Chaptarr is, and why Readarr users need it

Chaptarr is a fork of Readarr that manages audiobooks and ebooks from a single instance. It watches for new releases, sends them to your download client, then renames the results and files them into your library. It does not play anything, so you pair it with a player such as Audiobookshelf.

Readarr was retired on 27 June 2025. The Servarr team's own notice gives the reason: the project's metadata had become unusable, and the community effort to move to Open Library stalled. The repository is archived. That left book and audiobook collections without a maintained manager, and Chaptarr picked the job up. It keeps the shape you already know from Sonarr and Radarr (indexers, download clients, quality profiles, root folders) and adds audiobook handling: narrator-aware organisation, multiple editions of one title, M4B and chaptered MP3 support, and MP3 to M4B conversion.

This walkthrough used the image tag chaptarr/chaptarr:0.9.925, which was the newest release on 9 August 2026. Chaptarr calls itself beta software. Read the maintenance section near the end before you point it at a library you cannot replace.

What you need before you start

A VPS running Docker and the Compose plugin, and enough disk for the library. Audiobooks are large, and an import that cannot use hardlinks keeps two copies of a file for a while, which the volume section below explains. If Docker is not on the box yet, start with Docker installed and running on a VPS and come back.

Chaptarr ships as a Docker image only right now. A native Windows build is listed as in progress, and there is no distribution package. The container stores its database in /config as SQLite by default, and it can use an external PostgreSQL server through Chaptarr__Postgres__* environment variables if you already run one. SQLite is the right choice for one user on one box.

The Compose service for Chaptarr

This service slots into an existing stack. It pins a released tag, publishes the web UI on loopback only, and joins the network your download client already uses.

services:
  chaptarr:
    image: chaptarr/chaptarr:0.9.925
    container_name: chaptarr
    environment:
      - PUID=1000
      - PGID=1000
      - UMASK=002
      - TZ=Europe/Berlin
    volumes:
      - ./config:/config
      - /srv/media/audiobooks:/audiobooks
      - /srv/media/ebooks:/ebooks
      - /srv/media/downloads:/downloads
    ports:
      - 127.0.0.1:8789:8789
    restart: unless-stopped
    networks:
      - arr

networks:
  arr:
    external: true

The external: true line means "this network already exists, attach to it". Use it when Prowlarr and your torrent client come from a different Compose project, because a second Compose file otherwise creates its own isolated network and Chaptarr can then never resolve qbittorrent by name. Get the real name from docker network ls. If your stack lives in one file already, add the chaptarr: service to that file and delete the whole networks: block instead. The wider layout is covered in a full arr stack under Docker Compose, and the naming rules in how Compose networks and service names resolve.

Create the config directory yourself, then start it.

mkdir -p ./config
sudo chown 1000:1000 ./config
docker compose up -d
docker compose ps
docker compose logs -f chaptarr

docker compose ps should show the container as Up. A container listed as Restarting has failed to start and is being retried, and the cause is almost always the config directory. The log stops scrolling once the app is listening on port 8789.

PUID, PGID, and the directory Docker creates as root

Chaptarr defaults to PUID=99 and PGID=100 when you leave them unset. Those are unRAID's values, and on a plain Ubuntu VPS they belong to nobody useful, so files land with an owner your login cannot write. Read your own numbers with id -u and id -g and put those in the file.

Every container that touches the same files needs the same pair. The download client writes into /srv/media/downloads, Chaptarr moves the file into /srv/media/audiobooks, and the player reads it there. If the download client writes as 1000:1000 and Chaptarr runs as 99:100, the import fails because Chaptarr cannot delete or move a file it does not own. UMASK=002 makes new files group-writable, which is what you want when several containers share one media group. The full mapping is in how PUID and PGID map a container user onto host files.

The README warns about one specific trap, and it is worth repeating. If ./config does not exist when you run docker compose up, Docker creates it for you, owned by root:root. The container then runs as UID 1000 and cannot write its own database, so it exits and restarts forever. Check with ls -ln ./config, which prints numeric owners instead of names. Two zeros mean root owns it. Fix it with sudo chown -R 1000:1000 ./config and start the container again.

The layout above mounts /audiobooks, /ebooks and /downloads as separate binds, matching the project's own run command. It is easy to read, and it has one real cost: hardlinks stop working.

A hardlink is a second name for the same data on disk. It uses no extra space and it is instant, which is why the arr family prefers it over copying. A hardlink only works inside one filesystem. Inside the container these are three separate mount points, so the kernel refuses the link even when the host paths sit on the same disk. Test it yourself.

docker exec chaptarr sh -c 'touch /downloads/linktest && ln /downloads/linktest /audiobooks/linktest'

The command fails with an error ending in Invalid cross-device link. That is the kernel refusing to link across mount points, and it is the exact reason Chaptarr falls back to copying the file. The copy is correct but slower, and the audiobook then exists twice until you remove the torrent, which you will not do while you are still seeding it. Delete /srv/media/downloads/linktest afterwards.

To keep hardlinks, mount one parent directory instead:

    volumes:
      - ./config:/config
      - /srv/media:/data

Then set the root folders inside Chaptarr to /data/audiobooks and /data/ebooks, and give the download client the same /srv/media:/data mount so both containers see one identical path. Confirm the host side is a single filesystem first: df -h /srv/media/downloads /srv/media/audiobooks must print the same value in the Filesystem column for both. Different values mean different disks, and no mount layout can hardlink across them. The trade-off between this and named storage is covered in bind mounts against named volumes for media.

Reaching the web UI without exposing it

The port line publishes on 127.0.0.1 for a reason. ufw deny 8789 does not protect a published Docker port, because Docker writes its own NAT (network address translation) rules into a chain the kernel reaches before ufw's, so the traffic is forwarded before your rule is ever consulted. That behaviour catches people out constantly, and it is explained in why a published Docker port ignores your ufw rules. Binding to loopback sidesteps it entirely.

Reach the UI over an SSH tunnel from your own machine:

ssh -N -L 8789:127.0.0.1:8789 you@your-server

Leave that running and open http://127.0.0.1:8789 in your browser. Set up authentication on first run. Only after that should you consider a reverse proxy with TLS (transport layer security) in front of it.

Connect indexers and the download client

Chaptarr speaks the standard arr indexer and download client protocols, so Prowlarr pushes indexers into it the same way it does for Sonarr, and the usual torrent and usenet clients connect with no special handling.

One setting trips up almost everyone. When Chaptarr asks for the download client host, do not type localhost or 127.0.0.1. Inside a container that address is the container itself, so Chaptarr tries to talk to its own port 8080 and reports that it cannot connect. Use the container name, qbittorrent, with port 8080. Confirm both containers are on one network with docker network inspect arr, which lists every attached container by name.

If your download client runs through a VPN container with network_mode: "service:gluetun", it has no name of its own on the network, because it shares Gluetun's network namespace. Address it as gluetun on the port Gluetun exposes. That arrangement, and the routing that goes with it, is in routing a download client through Gluetun.

The Readarr break: what a migration really costs

Chaptarr is not compatible with Readarr's metadata sources. It resolves titles, authors and editions through its own pipeline across several providers, so the identifiers Readarr stored mean nothing here. There is no database import and no drop-in upgrade path.

For an existing library, that means the files are safe and the settings are not. Nothing in this process touches what is already on disk. You add a root folder, run a library import, and Chaptarr matches the files it finds against its own metadata. What you rebuild by hand: quality profiles, naming format, indexer and client settings, and every match Chaptarr guesses wrong. A large library will need a pass of manual corrections, so budget an evening rather than ten minutes.

Do it in this order. Stop the Readarr container but keep its config volume, so you can still read your old settings while you retype them. Point Chaptarr at one small folder first and check the matches before you import everything. Only remove the old container once you are happy.

One privacy detail worth knowing before you scan a whole library: metadata lookups go to api2.chaptarr.com. The README states those requests can carry provider IDs, search text, media type, tags and filenames, and that they exclude full paths, user identity and credentials. Filenames leave your server. That is normal for a metadata service, and you should still decide it on purpose.

Hand the audiobooks to a player

Chaptarr organises files. Playing them is another program's job, and Audiobookshelf is the usual partner because it tracks your listening position across devices and has phone apps. Its official image is ghcr.io/advplyr/audiobookshelf:latest, and its documented Compose example publishes host port 13378 onto container port 80.

  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    ports:
      - 127.0.0.1:13378:80
    volumes:
      - ./abs/config:/config
      - ./abs/metadata:/metadata
      - /srv/media/audiobooks:/audiobooks
    environment:
      - TZ=Europe/Berlin
    restart: unless-stopped

Mount the same host path Chaptarr writes to, then add /audiobooks as a library inside the web UI. A new import appears after the next scan.

If you already run Jellyfin, you can add the folder as a library there and it will play the files, though resume behaviour on a single long audiobook file is weaker than a purpose-built audiobook server. Setting that side up is covered in running Jellyfin as a media server on a VPS. For the ebook half, hand /srv/media/ebooks to a reader application; Chaptarr's job ends once the file is named and filed.

Maintenance risk: licence, runtime, and a fast-moving tag

Chaptarr is GPL-3.0 licensed, copyright the Chaptarr contributors with portions from the Servarr team, so the code stays open and anyone can fork it again if this maintainer stops. It builds on .NET 10, the current long term support release of the runtime as of August 2026, which means the base is supported for years rather than months. Both facts matter if you are judging whether this project will still exist next year.

The version numbers move fast. Releases are published as pre-releases, and 0.9.925 landed on the same day as this walkthrough. Pin an exact tag. Using latest means an unattended docker compose pull can move you several versions in a week, and a fork this young can change its API between releases, which breaks any script or dashboard you wrote against it.

Back up before every upgrade, then upgrade on purpose.

docker compose stop chaptarr
sudo tar czf chaptarr-config-backup.tgz ./config
docker compose start chaptarr
docker compose pull chaptarr
docker compose up -d chaptarr

The project reports no data loss events across roughly six months and more than eleven thousand users, and it still advises keeping backups and not pointing it at a library you cannot afford to lose. Take both halves of that seriously. Copy the config archive off the server, because a backup living on the same disk as the thing it protects is not a backup.

Failure modes, with the strings you will see

The container restarts in a loop. docker compose ps shows Restarting. Run ls -ln ./config. Two zeros in the owner columns mean Docker created the directory as root and the container user cannot write its database. Run sudo chown -R 1000:1000 ./config.

Imports never complete and files stay in downloads. Chaptarr can read the download but cannot write into the library. Compare ls -ln /srv/media/audiobooks against your PUID and PGID. A directory owned by a different UID, or owned by your group without group write, stops the move. UMASK=002 prevents the second case for new files.

Disk usage doubles after every import. No hardlink was made, so the file was copied. Run the ln test from the volumes section. An error ending in Invalid cross-device link confirms it, and the single-parent mount is the fix.

The download client will not connect. You entered localhost as the host. Inside the container that is Chaptarr itself. Use the container name and check docker network inspect arr lists both containers.

Compose refuses to start the service. Bind for 127.0.0.1:8789 failed: port is already allocated means something else holds the port. Find it with sudo ss -lntp | grep 8789.

The browser shows nothing at all. With the port bound to 127.0.0.1, there is nothing for your laptop to connect to across the internet. That is the intended behaviour. Open the SSH tunnel first.

FAQ

Can I migrate my Readarr library to Chaptarr?

Not as an import. Chaptarr is not compatible with Readarr's metadata sources and uses its own provider pipeline, so Readarr's stored identifiers carry no meaning and there is no database conversion. Your files on disk are untouched. You add the same paths as root folders, run a library import, and let Chaptarr match the files itself. Quality profiles, naming format, indexer settings and any wrong matches are manual work, so start with one small folder before importing everything.

Why can Chaptarr not write to my audiobook folder?

The container's user does not own the files. Chaptarr falls back to PUID=99 and PGID=100 when those variables are unset, which are unRAID's values and wrong on a normal Ubuntu VPS. Set them to your own id -u and id -g, use the same pair on the download client, and set UMASK=002 so new files stay group-writable. Check ownership with ls -ln on the library directory, since it prints the numbers rather than names you cannot compare.

Why did my disk usage double after an import?

Chaptarr copied the file because it could not hardlink it. Mounting /downloads and /audiobooks as separate binds makes them separate mount points inside the container, and the kernel refuses a hardlink across mount points with Invalid cross-device link. Mount one parent directory such as /srv/media:/data and use /data/downloads and /data/audiobooks inside the app. Both paths must also sit on one host filesystem, which df -h confirms.

Does Chaptarr play my audiobooks?

No. It finds, downloads, renames and files them, and playback is a separate program. Audiobookshelf is the common pairing because it remembers your position across devices, using the official image ghcr.io/advplyr/audiobookshelf:latest with the same host audiobook path mounted. Jellyfin will also play the files if you add the folder as a library, with weaker resume behaviour on long single-file audiobooks.

Is Chaptarr safe to run on a library I care about?

It is beta software from a young fork, and the project says so itself while reporting no data loss events over about six months and more than eleven thousand users. The reassuring parts are the GPL-3.0 licence, which keeps the code forkable, and the .NET 10 base, a long term support runtime as of August 2026. Pin an exact image tag such as 0.9.925 rather than latest, back up /config before each upgrade, and keep that archive off the server.