Maintainerr on Ubuntu 24.04 on Azure User Guide
Overview
Maintainerr is an open source, self-hosted, rules-based media-collection manager. It connects to Plex together with Overseerr or Jellyseerr and the *arr apps (Sonarr and Radarr), then applies rules you define to keep your library tidy automatically: it finds unwatched, unrequested or otherwise stale media and can add it to a Plex collection for review or remove it, all on a schedule. Maintainerr has no built-in user authentication of its own because it is designed to run behind a reverse proxy, so the cloudimg image closes that gap: the application is built from source and runs as a single Node.js service bound to loopback 127.0.0.1:6246, and an nginx reverse proxy on port 80 adds a per-VM HTTP Basic Auth gate in front of it. The nginx Basic Auth credential (user admin, a unique password generated on first boot) is the gate, and an unauthenticated request is refused. Maintainerr's data - its SQLite database, configuration and logs - lives under /opt/data. Backed by 24/7 cloudimg support.
What is included:
- Maintainerr 3.18.0 built from source and running as the
maintainerrsystemd service - The Maintainerr web UI and REST API on
:80, fronted by nginx with the app bound to loopback only - Per-VM HTTP Basic Auth (user
admin) protecting the UI and API, with a unique password generated on first boot - The Node.js service bound to
127.0.0.1:6246- nothing is exposed to the network directly - A fresh per-instance database and internal API key created automatically on first boot
maintainerr.service+nginx.serviceas systemd units, enabled and active- An unauthenticated
/healthzendpoint for Azure Load Balancer health probes - 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. NSG inbound: allow 22/tcp from your management network, 80/tcp for the UI and API, and 443/tcp if you add TLS. Maintainerr serves plain HTTP on port 80; for production use, terminate TLS in front of it with your own domain and restrict access to trusted IP ranges (see Maintenance).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Maintainerr by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name maintainerr \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name maintainerr --port 80 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
systemctl is-active maintainerr.service nginx.service
Both report active. Maintainerr serves its web UI and REST API on the loopback address 127.0.0.1:6246; nginx fronts it on port 80 and adds the per-VM HTTP Basic Auth gate. Maintainerr's data lives under /opt/data.

Step 5 - Retrieve your web UI password
nginx protects the Maintainerr UI and API with HTTP Basic Auth. The username is admin and a unique password is generated on the first boot of your VM and written to a root-only file:
sudo cat /root/maintainerr-credentials.txt
This file contains MAINTAINERR_USERNAME, MAINTAINERR_PASSWORD and the MAINTAINERR_URL to open in a browser. The password is stored on disk only as a bcrypt hash in /etc/nginx/.maintainerr.htpasswd, so no plaintext password ships in the image. Store the password somewhere safe.

Step 6 - Confirm the health endpoint
nginx serves an unauthenticated health endpoint for load balancers and probes:
curl -s http://localhost/healthz
It returns a small JSON document such as {"status":"ok","uptimeSeconds":65,"database":"ok","timestamp":"2026-07-19T09:33:37.855Z"}. This endpoint never requires authentication, so it is safe for an Azure Load Balancer health probe, and its database field confirms Maintainerr's SQLite store is answering.
Step 7 - Confirm authentication and the API
Because a password is set on first boot, an unauthenticated request to the UI returns HTTP 401, so nobody reaches Maintainerr without the password. The following reads the per-VM password from the credentials file and proves the round-trip - unauthenticated is rejected, a wrong password is rejected, the correct password authenticates, and an authenticated call to the readiness API returns 200:
PW=$(sudo grep '^MAINTAINERR_PASSWORD=' /root/maintainerr-credentials.txt | cut -d= -f2-)
echo "unauth : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)"
echo "wrongpw : $(curl -s -o /dev/null -w '%{http_code}' -u admin:wrong-pw http://127.0.0.1/)"
echo "authed : $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/)"
echo "ready : $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/api/health/ready)"
It prints unauth : 401, wrongpw : 401, authed : 200 and ready : 200. The whole UI and API are only reachable with the per-VM password because Maintainerr itself is bound to loopback and nginx is the only way in.

Step 8 - Confirm the loopback binding and data directory
Maintainerr the application listens only on the loopback interface, and nginx is the only public listener. The following shows the Node.js service on 127.0.0.1:6246 and nginx on :80, then lists Maintainerr's data directory:
sudo ss -tlnp | grep -E ':6246|:80 '
sudo ls -la /opt/data
The application is bound to 127.0.0.1:6246 (never a public interface) and nginx listens on 0.0.0.0:80. Under /opt/data you will see maintainerr.sqlite (the SQLite database), a logs directory and an overlays directory. A fresh per-instance database and internal API key are created here on first boot, so no secret is shared between instances.

Step 9 - Sign in to the web UI
Browse to http://<vm-public-ip>/. Your browser prompts for a username and password: enter admin and the password from Step 5. Maintainerr opens on its first-run welcome, which prompts you to connect a media server to finish setup. The left sidebar already shows the full feature set - Overview, Rules, Collections, Calendar, Storage, Overlays and Settings - which unlocks once a media server is connected.

Step 10 - Choose your media server
Select Let's get started to open Settings -> General. Here you set the instance hostname and see the auto-generated internal API key, and under Media Server you choose the server Maintainerr will act on: Plex, Jellyfin or Emby. A fresh per-instance API key is generated on first boot, so no secret is shared between instances.

Step 11 - Connect your media server
Pick your server type to open its connection form - for Jellyfin, enter the Jellyfin URL and an API key (created in Jellyfin under Dashboard -> API Keys) and use Test Connection to confirm it. Plex uses a sign-in flow instead. Once the connection is confirmed the setup guard clears and the rest of Maintainerr unlocks, so you can also connect your request manager (Overseerr or Jellyseerr) and the arr apps (Sonarr and Radarr) from their own Settings tabs.

Step 12 - Build rules, review collections, and read the logs
With a media server connected, open the Rules page to create rule groups that match media by properties across your connected services - for example "added more than 180 days ago", "never watched", or "not in any active request" - and choose whether matching media is added to a managed Plex collection for review or removed. The Collections page then lists the managed collections Maintainerr builds, and the Logs page (available even during setup) records rule evaluations and connection diagnostics, which is the first place to look when confirming a rule behaves as intended.

Maintenance
- Password: the UI password is set on first boot and stored as a bcrypt entry in
/etc/nginx/.maintainerr.htpasswd. To change it, runsudo htpasswd -B /etc/nginx/.maintainerr.htpasswd adminand thensudo systemctl reload nginx. - Restrict access: Maintainerr serves plain HTTP on port 80. For production, restrict access to trusted IP ranges in your Network Security Group, and front it with TLS (for example certbot with your own domain) terminating on
:443. - Loopback binding: the application is bound to
127.0.0.1:6246(viaUI_HOSTNAME=127.0.0.1in/etc/systemd/system/maintainerr.service), so nginx is the only path in. Keep it that way - do not change the bind address to a public interface. - Data and backups: all Maintainerr state (the SQLite database, configuration and logs) lives under
/opt/data; back up that directory to protect your rules and connected-service configuration. - Logs: application logs are written under
/opt/data/logs; service logs are available withjournalctl -u maintainerr. - Updates: this image pins Maintainerr 3.18.0. To move to a newer release, follow the upstream upgrade notes; your
/opt/datadirectory carries your configuration across upgrades. - Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.
Support
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.