Maintainerr disables image features on VMs without x86-64-v2
Looks and smells like Seerr, does the opposite. A library maintenance tool for Plex, Jellyfin and Emby.
At a glance
- What is it?
- Maintainerr is a self-hosted library janitor for Plex, Jellyfin and Emby that gathers rule matches into a collection, waits out a grace period, then deletes them across the arr applications and Seerr. Its container silently degrades on older x86 CPUs.
- Who is it for?
- Maintainerr suits a self-hoster with a Plex, Jellyfin or Emby library who wants cleanup decisions made by written rules instead of manual sweeps through a growing library. It does not suit a host on a CPU without x86-64-v2 if collection posters matter, and it does not replace the arr applications themselves.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Rules become a collection, then a deletion across four systems
Maintainerr describes itself as the janitor a media server does not have, and the framing is about a decision rather than a deletion. Libraries fill up: a movie is requested, watched once, and left alone, while half-finished shows accumulate for years. The tool's job is to make that call. You write rules about the stuff that is only taking up space, unwatched, unrequested or gathering dust, and those titles are gathered into a collection. Everyone then gets a grace period to catch up before anything is removed. The removal is not confined to the media server: it clears titles from the media server, from the arr applications such as Radarr and Sonarr, and from Seerr or Ombi. That last part matters more than it sounds. A delete that leaves a request record behind simply re-adds the item, so talking to the request system is what stops the janitor from fighting the requester. The project was founded in 2021, and its own description in the repository positions it against Seerr directly, saying it looks and smells like Seerr and does the opposite.
Everything lives in /opt/data and belongs to 1000:1000
The one operational fact that decides whether a Maintainerr install works is its data directory. State is stored at /opt/data inside the container, and you have to mount a persistent volume there in a docker run command, a Compose file or a Quadlet unit. Permissions are spelled out too: the directory must be readable and writable by the configured user, and if you set no user it has to be reachable as UID:GID 1000:1000. All three deployment methods in the README use the same convention, with the Compose file setting user: 1000:1000 explicitly and the Quadlet unit using UserNS=keep-id:uid=1000,gid=1000 so your own user maps to that pair inside the container. That last detail is the clever one: with keep-id you do not have to chown the host directory to 1000:1000, because your own user is 1000:1000 inside. Forget the mount and you lose your rules and your history on the next container replacement.
A CPU without x86-64-v2 costs you posters, silently
There is a hardware note in the README that deserves more attention than its position in the install section. Image features, meaning overlays and collection posters, use sharp, whose prebuilt binaries require a CPU with the x86-64-v2 microarchitecture. Maintainerr still starts on an older CPU, but those features are disabled. So the failure mode is not a crash, it is a quiet downgrade: the application runs, your rules run, and the artwork does not appear. The common case is a virtual machine. The guidance is to set the VM CPU type to host, x86-64-v2 or newer, and it calls out Proxmox and QEMU specifically, because their default kvm64 model does not expose x86-64-v2. That is a configuration change to the hypervisor rather than to the container, which is why it is easy to miss and hard to diagnose from inside the application. The note does not apply to arm64, where the images are published too, so on Apple silicon the constraint disappears entirely.
Three deployment recipes, one port and one volume
The README offers a docker run form for people who want a single command:
docker run -d \
--name maintainerr \
-e TZ=Europe/Brussels \
-v ./data:/opt/data \
-u 1000:1000 \
-p 6246:6246 \
--restart unless-stopped \
ghcr.io/maintainerr/maintainerr:latestThe Compose form is the one most self-hosters will use, and it carries the optional settings as commented lines so you can see what exists without turning it on: BASE_PATH for serving from a subdirectory, UI_HOSTNAME to listen on IPv6 instead of the default 0.0.0.0, UI_PORT to change from the default 6246, GITHUB_TOKEN for higher API rate limits, listed as 60 per hour without a token and 5000 per hour with one, and TELEMETRY=off to switch off the anonymous weekly usage report. A SPORTARR_NET=on line reads league artwork from sportarr.net. If you do set BASE_PATH, you must add it to the start of the health path as well, for example /maintainerr/api/health/ready. The Compose file also carries a healthcheck that is already baked into the image, with a 30 second interval, 5 second timeout, 40 second start period and 3 retries, included so you can tune it rather than because you must.
The codebase is a Yarn 4 monorepo that asks for Node 26
The repository layout is a modern TypeScript monorepo and the manifests say how strict it is about versions. package.json is private, pins packageManager to [email protected], and declares engines with node set to >=26.0.0. Workspaces are apps/ui, apps/server and packages/*, and the scripts route through turbo, so build, dev, lint, format, check-types and test are all turbo tasks rather than commands in the root package. There is a check-node-version script that gates on the engine requirement, which is how a Node 22 host finds out it is unsupported before anything else breaks. The Dockerfile matches that floor, starting from node:26.3.0-alpine3.22 pinned by digest, then installing build-base, python3, cairo-dev, pango-dev, jpeg-dev, giflib-dev, pixman-dev and librsvg-dev for the node-canvas and sharp native dependencies. It copies the manifests before the source to keep the dependency layer cacheable, runs yarn turbo build, then prunes to production dependencies with yarn workspaces focus --all --production and recreates the empty node_modules folders that hoisting removes.
Two toolchains in package.json: publishing and locales
Two clusters of tooling in package.json tell you how the project is maintained. The first publishes the package: semantic-release with a commit analyzer, a changelog step writing to CHANGELOG.md, npm publishing and an exec step, with NEXT_RELEASE_VERSION and LAST_RELEASE passed into a generateNotes command, which is how the release notes get assembled from the commit range. A release.sh at the repository root and the semantic-release dev dependencies complete that picture. The second cluster is translation work, with three scripts: i18n:validate checks the catalogs, i18n:verify exercises the locale switch, and i18n:back-translate produces back-translations. There is also knip for unused dependency detection and format:changed to format only what moved. A version field of 3.29.0 sits in the root package, matching the current published tag, while the default branch is development and the compose file offers a :development image tag for testing unreleased changes.
Where Maintainerr is the wrong tool
Three cases rule it out or narrow it sharply. The first is hosting on a virtual machine whose CPU model does not expose x86-64-v2, since collection posters and image overlays are the features that break and they break quietly. That is a hypervisor configuration problem you may not be able to solve on a managed host. The second is a library you do not want automated deletion in at all, or one where deletion decisions are legally or contractually constrained, because the entire design is that rules produce a collection that becomes a deletion after a grace period, and the grace period is the only speed bump in the path. The third is expectations about what it replaces. Maintainerr talks to Plex, Jellyfin, Emby, the arr applications and Seerr or Ombi, and it is a cleanup layer on top of them rather than a replacement for any of them; if your library is not already in one of those systems, there is nothing for it to clean. It is also worth noting the shape of the project around it: a Discord community, documentation built with Material for MkDocs, Open Collective funding, and repository tooling that includes .claude/, .codex/, .cursor/ and .mcp.json directories alongside AGENTS.md and ARCHITECTURE.md, which tells you the maintainers run AI-assisted workflows against this codebase as part of the normal build.
Editorial conclusion
Maintainerr suits a self-hoster with a Plex, Jellyfin or Emby library who wants cleanup decisions made by written rules instead of manual sweeps through a growing library. It does not suit a host on a CPU without x86-64-v2 if collection posters matter, and it does not replace the arr applications themselves. Before deploying, mount a persistent volume at /opt/data owned by UID:GID 1000:1000, check that your virtual CPU type exposes x86-64-v2, and prefix the health endpoint path when you set BASE_PATH.
Frequently asked questions
What does Maintainerr connect to?
It is built for Plex, Jellyfin and Emby libraries, and it clears titles from the media server, the arr applications and Seerr or Ombi. It works with all three media server types rather than one.
Where does Maintainerr store its data?
At /opt/data inside the container. You must mount a persistent volume there, and the directory must be readable and writable by the configured user, or by UID:GID 1000:1000 if no user is set.
Does Maintainerr need a particular CPU?
On x86-64, image features such as collection posters use sharp, whose prebuilt binaries need the x86-64-v2 microarchitecture. The app still starts on older CPUs with those features disabled, and the note does not apply to arm64.
How do I serve Maintainerr from a subdirectory?
Set BASE_PATH, and add the same prefix to the start of the health path, for example /maintainerr/api/health/ready. UI_HOSTNAME and UI_PORT are also available for IPv6 listening and a non-default UI port.
What Node version does building Maintainerr from source need?
The root package.json declares node >=26.0.0 and pins [email protected] through packageManager. The Dockerfile starts from node:26.3.0-alpine3.22 and builds with turbo before focusing production dependencies.