Portainer Templates logo

Portainer Templates

Cantinarr Cantinarr

Container

MediaTools

One app over your whole media stack: household members browse and request movies, TV, and books, and admins get Radarr, Sonarr, and Chaptarr control down to the individual episode, live queues for SABnzbd, qBittorrent, NZBGet, and Transmission, Tautulli activity, Plex invites, plain-English fixes for stuck downloads, and an MCP server for Claude or any MCP client. GitHub: windoze95/cantinarr

Image details

Architecture: amd64, arm64
Image size: 139 MB
User: windoze95

Source details

Stars: 10
Forks: 2
Language: Go
License: AGPL-3.0
Updated: 6 hours ago
Website: cantinarr.com/

Configuration

Type
Container
Platform
linux
Image
ghcr.io/windoze95/cantinarr:latest
Ports
8585:8585/tcp
Volumes
/config : /portainer/Files/AppData/Cantinarr/config
Env vars
CANTINARR_PUSH_GATEWAY_URL=https://push.julian.codesCANTINARR_ARR_CALLBACK_URL=
Restart
unless-stopped

Notes

Web UI and API on port 8585. Radarr, Sonarr, Chaptarr, download clients, Tautulli, and Plex are all optional and are added from the admin UI after first run. Documentation: https://github.com/windoze95/cantinarr

Standalone Install

Select an install method, to see config/commands for deploying Cantinarr

Installation method

Install on Portainer

Import all app templates into your Portainer instance, for easy 1-click deploys

  1. Ensure both Docker and Portainer are installed, and up-to-date
  2. Log into your Portainer web UI
  3. Under Settings → App Templates, paste the below URL
  4. Head to Home → App Templates, and the list of apps will show up
  5. Select Cantinarr, fill in any config options, and hit Deploy

Template Import URL

https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json
Show Me demo

More install options in our documentation, or see windoze95/cantinarr for app-specific guidance.

Cantinarr

Your media server just learned to run itself.
cantinarr.com · Live demo · iPhone beta · Android beta · Request a feature
Discover and request movies, TV shows, and books. Get push notifications. Manage Radarr, Sonarr, Chaptarr, and your download clients. When downloads get stuck, Cantinarr diagnoses the cause and recommends the next step. You set the agent's operating boundaries. Your household gets the simple experience; you keep control of access, approvals, and quality.
┌──────────────────────────────────────────────────────────────┐
│  Cantinarr Server (Go, single container, port 8585)          │
│                                                              │
│  ┌──────────┐ ┌───────────┐ ┌─────────┐ ┌────────────────┐   │
│  │ Auth/JWT │ │ Requests  │ │ Issues +│ │ AI Chat        │   │
│  │ Passkeys │ │+ Approvals│ │ AI Agent│ │ + 38 AI Tools  │   │
│  └──────────┘ └─────┬─────┘ └─────────┘ └────────────────┘   │
│                     │                                        │
│  ┌──────────────────┴───────────────────┐  ┌──────────────┐  │
│  │  ID Bridge: TMDB → Trakt → TVDB      │  │ TMDB/Trakt   │  │
│  │  (cached 30 days)                    │  │ discovery    │  │
│  └───┬──────────┬──────────┬────────────┘  └──────────────┘  │
│      │          │          │                                 │
│  ┌───┴───┐ ┌────┴───┐ ┌────┴─────┐ ┌───────────────────────┐ │
│  │Radarr │ │ Sonarr │ │ Chaptarr │ │ Flutter Web (embedded)│ │
│  └───┬───┘ └────┬───┘ └────┬─────┘ └───────────────────────┘ │
└──────┼──────────┼──────────┼─────────────────────────────────┘
       │          │          │        ▲ webhooks push external
  ┌────▼───┐ ┌────▼───┐ ┌────▼─────┐    changes back instantly
  │ Radarr │ │ Sonarr │ │ Chaptarr │  (+ SABnzbd, qBittorrent,
  └────────┘ └────────┘ └──────────┘   NZBGet, Transmission,
                                       Tautulli, push gateway)

┌───────────────────────────────┐
│  Cantinarr App (Flutter)      │      ┌─────────────────────┐
│  Discovery, Requests, Books,  │─────>│  Cantinarr Backend  │
│  Arr control, AI, Issues,     │ REST │  (the only API the  │
│  Push notifications           │ + WS │   app talks to)     │
└───────────────────────────────┘      └─────────────────────┘

Why Cantinarr?

  • Zero-config requesting -- Your users never see API keys, TVDB IDs, or quality profiles. They browse, they tap, it works.
  • TMDB + Trakt for discovery -- The best metadata, images, and trending data, proxied through the server so keys stay off devices -- and TMDB works out of the box on a built-in key, no signup needed. Sonarr's TVDB dependency is invisible.
  • You choose what "popular" means -- The headline row on the Movies and TV tabs reads TMDB weekly trending, or Trakt trending (ranked by who is actually watching), or TMDB's all-time popularity ranking. Connect Trakt and the rows switch to it automatically -- no second setting to find. An English-only switch keeps the discovery and recommendation rows to English-language originals; it ships on, and search always finds everything either way.
  • Automatic ID bridging -- TMDB-to-TVDB translation with Trakt fallback. The #1 source of failed Sonarr adds, solved.
  • Books too -- A Chaptarr (Readarr-API) module with per-format smarts: tap a book's eBook or Audiobook row to request that format; monitored formats read Requested until they download; owned-aware search and plain per-format controls stay pinned to the selected, authorized Chaptarr instance. Access is granted per user, and the Books tab opens on a Recently Added row so a book that just landed is visible without searching for it.
  • Take available files with you -- Optional, resumable downloads let signed-in users save exact ebook, audiobook, movie, and episode files from their authorized library. Cantinarr re-checks the live arr file record before issuing a short-lived, file-scoped link without putting arr credentials in the URL.
  • Request approvals -- Optional approval queue, globally or per user. Admins also control per-user season choice, quality choice, and default quality profiles. Approve/deny lands as a push notification for the requester.
  • AI assistant -- "What should I watch tonight?" Every user can bring a personal Anthropic, OpenAI, Gemini, or xAI Grok API key, or link a subscription account with a one-time browser code -- OpenAI (OAuth) through ChatGPT, or xAI Grok (OAuth) through SuperGrok / X Premium+ -- even without included access, and their choice never has to match the server's provider. Admins can configure the same providers as an included server profile and grant that shared access per user. A personal provider is an explicit override; Cantinarr never silently spends the shared account when that override needs attention. The assistant searches your library, checks availability, requests for you, and gives admins conversational queue and release control.
  • Local AI -- A first-class Local (OpenAI-compatible) provider runs shared AI against your own server (llama.cpp, vLLM, Ollama): enter a base URL and model ID, pin a reasoning effort, and skip the API key entirely (most local servers ignore auth; an optional token slot covers proxies that don't). Assistant traffic never leaves your network, and the save-time test proves the endpoint before anything is stored.
  • AI remediation agent -- Users tap "Report a problem" (or Cantinarr detects one itself, in the queue or as it imports); each report is bound to the exact Radarr/Sonarr instance and begins with a quiet observation window. Cantinarr gives Sonarr/Radarr time to retry or replace a download before it alerts anyone or starts the agent; a persistent quiet problem then enters the supervised workflow. Recovery cancels stale proposals before dispatch. Automatic resolution requires an exact changed file plus a matching post-incident import record—not queue disappearance or a file that was already there. One whole class of this never needs reporting. The moment Sonarr says it imported an episode that has not aired yet, Cantinarr checks that season against its own air dates: a file your service imported before that episode aired cannot be that episode, and a season already holding files for episodes that have not aired is content that does not exist yet. When that is what happened, the season is already waiting in your issues with a fix attached — one issue for the whole season however many bogus files arrived, and an instance with no instant updates configured gets the same check on a quarter-hour timer instead. One approval fixes it: the agent proposes deleting exactly those files, blocklisting the releases that delivered them, and searching for replacements — only the episodes that have actually aired, leaving the rest of the season for your service to grab as it comes out. One problem, one decision; you are never asked to approve the second half of a fix you already approved. When the fix lands on something you reported, you are the one who says whether it worked -- "This is fixed" closes your own report, so an admin is never asked to adjudicate content they haven't watched. Tired of approving the same fix? Checking "Always approve" on an approval arms a standing rule for that exact problem-and-fix pair (force imports and destructive queue actions stay separate opt-ins): future matches are approved and executed without paging you, and the rule pauses itself the moment a fix fails or an issue closes out unresolved. Some problems should not be repaired again. When the same fault keeps coming back on one service -- on separate days, not just across a dozen titles in one bad minute -- Cantinarr says so once and names the setting that would stop it: the free-space floor, the remote path mapping, the indexer whose torrents have no seeders behind them. That is advice and never an edit; Cantinarr changes no setting on your services. Closing the notice is what mutes it -- for a couple of months if you fixed it, for a year if you told Cantinarr you are not going to -- and it only ever comes back if the problem does. Where there is no honest answer to give, nothing is raised at all. Remediation is server-owned: it always uses the admin's shared API key or shared OAuth connection (OpenAI or xAI Grok) and never a reporter's personal provider or per-user included-access grant. Admins may give remediation its own tested model designation while keeping that global provider and credential.
  • MCP server -- 36 of the 38 in-app AI tools are exposed as a Model Context Protocol endpoint at /mcp, with OAuth discovery, browser/passkey login, dynamic client registration, and persistent rotating refresh tokens. The two quality-profile mutation tools remain in-app-only because their one-use safety handoff depends on authenticated in-app chat-turn provenance. This inbound OAuth lets external clients access Cantinarr; it is separate from the outbound personal/shared OpenAI OAuth used by Codex chat. Every tool can be toggled on/off from Settings > AI Tools.
  • Deep arr control -- SABnzbd, qBittorrent, NZBGet, and Transmission modules with live queue management (an aggregate All view with a master pause across every client when several are configured), plus drill-down Radarr/Sonarr control: series → season → episode with per-item progress, quality, and history; episode multi-select with batch search; long-press action menus; Edit Series; interactive release search everywhere. Admin AI/MCP tools can inspect quality profiles and import or update native/TRaSH custom formats across Radarr, Sonarr, and Chaptarr. After an explicit admin request, in-app AI previews and autonomously applies a narrow profile score, cutoff, or upgrade-policy change in the same authenticated chat turn. AI/MCP profile and custom-format writes are recorded under Settings > Configuration history for later review and live comparison. Each applied quality-profile update can be restored once, only while Cantinarr's instance, profile, and dependency guards still match; the linked restore is final, and custom-format entries are review-only.
  • Import Doctor -- when a download is stuck, Cantinarr explains why in plain English (sample file, un-extracted archive, unconfirmed TheXEM mapping, "not an upgrade", unparseable/invalid file, remote-path-mapping or download-client problems, stalled torrent, permissions...) and offers one-click fixes with full transparency: manual/force import with the candidate files shown, remove + blocklist + re-search, hand-off to a tool like Unpackerr, or rescan. Cantinarr clears the stuck item and leaves the replacement to your service's own settings — with one exception: when the download was only an upgrade for something you already have and nothing asked for it (your service picked it up on its own), it is simply dropped. Your copy stays watchable, and a better version is still picked up whenever one shows up. The same diagnosis backs the app, the AI assistant, the remediation agent, and MCP.
  • Flexible requests -- request a whole title in one tap, or pick exactly which seasons (or book formats) you want; partially-available shows surface per-season availability and a one-tap path to request the rest.
  • Always in sync -- availability is computed live from the arrs (never from a stale snapshot), and server-managed Radarr/Sonarr/Chaptarr webhooks -- installed automatically the moment you add an instance -- push manual imports, deletes, and adds into the app the moment they happen without exposing callback credentials to a device. Books gain the most: an ebook can finish downloading between two polls, so instant updates are what make its "ready to read" alert reliable.
  • Push notifications -- APNs (iOS) and FCM (Android) via a self-hosted push gateway with zero-config auto-enrollment: new-content alerts for movies, episodes, and books, approval/issue alerts for admins, per-user preference toggles, deep links into the right screen.
  • Plex onboarding -- new users request access right from the in-app guide with their Plex email. Link your Plex account once and the server invite is one tap from the Users screen -- or fully automatic, with the user pushed a "check your inbox" the moment it's sent.
  • Tautulli -- watch what's playing on Plex right now: active streams with quality/transcode badges, watch history, and top movies/shows/users stats.
  • Secrets encrypted at rest -- arr API keys, download-client passwords, webhook tokens, shared and personal AI credentials, and OpenAI/xAI OAuth authorizations are AES-256-GCM encrypted in the database.
  • Household-friendly -- Connect links, passwordless by default, role-based access, per-user default instances. Admins manage services; users just browse and request.
  • Guided setup -- a live checklist wizard derived from what's actually configured: every step opens the real settings screen, progress can't go stale, and newly shipped features appear on the list automatically.
  • Single container -- The static Go API/web server plus a pinned Codex app-server helper, with one exposed port. Runs great on a Raspberry Pi or NAS.

Quick Start

git clone https://github.com/windoze95/cantinarr.git
cd cantinarr
docker compose up -d

This pulls the published image (ghcr.io/windoze95/cantinarr:latest); updating later is docker compose pull && docker compose up -d. To build the image from your checkout instead, layer the dev override: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build.
Open http://your-server:8585 -- the setup wizard walks you through creating an admin account. Discovery and search work immediately on the built-in TMDB key. Then connect your services (Radarr, Sonarr, etc.) from
Settings > Providers & Credentials and Settings > Add Instance in the admin UI. Configure an included AI provider there (fresh installs preselect OpenAI OAuth with the fast GPT-5.6 Luna model -- connecting a ChatGPT account is all it takes) and grant it per user, or let each person bring a provider under Settings > AI Access.

Unraid

Search
Cantinarr in the Apps tab. The listing was approved on 2026-08-16 and appears once the next Community Applications build publishes; its template lives in windoze95/cantinarr-unraid.
If it is not there yet, add the file to Unraid's user-template folder yourself, since Unraid removed custom template repositories:
curl -o /boot/config/plugins/dockerMan/templates-user/my-cantinarr.xml \
  https://raw.githubusercontent.com/windoze95/cantinarr-unraid/main/templates/cantinarr.xml

Then Docker > Add Container, pick Cantinarr from the Template dropdown, and Apply. It publishes port 8585 and keeps the database and encryption key in /mnt/user/appdata/cantinarr. Two advanced fields are worth opening: Public URL, the origin your arrs POST webhooks back to, and the read-only Media library mount plus Media roots, which together turn on completed-media downloads.

Prebuilt binaries (no Docker)

Every release attaches cantinarr-linux-amd64.tar.gz and cantinarr-linux-arm64.tar.gz (with .sha256 checksums), extracted from the same build as the published image. Each contains the cantinarr server with the web app embedded, the pinned codex-app-server runtime it can spawn for ChatGPT-subscription AI access, and license notices:
tar -xzf cantinarr-linux-amd64.tar.gz
install -m 0755 cantinarr codex-app-server /usr/local/bin/
mkdir -p /config   # database + generated encryption key live here
cantinarr          # serves everything on 8585 (CANTINARR_PORT overrides)

From Source

# Server (requires Go 1.25+)
cd server
go run ./cmd/server

# App (requires Flutter stable, Dart SDK 3.4+)
cd app
flutter pub get
flutter run

make builds the full stack (Flutter web → embedded in the Go binary).
Published images stamp the web bundle with a build number that Settings > About reports. Building the image yourself, pass your own with docker build --build-arg APP_BUILD_NUMBER=<n> .; leave it out and About falls back to pubspec.yaml's placeholder.

Get the app

The mobile apps are in beta ahead of their public store launch. Every one of these talks only to your own server, so stand one up first (above) -- or open the live demo in a browser to look around.
  • iPhone and iPad -- join the public beta on
TestFlight. No invite needed. testing is closed, so testers are added by hand: email [email protected] with the address associated with your Play Store (Google) account -- that exact address is what Google needs to let you in -- and you'll get the opt-in link back.
  • Any browser -- your server already serves the full app at
http://your-server:8585. Nothing to install.

Repository Structure

cantinarr/
├── server/                 # Go backend -- see server/README.md
│   ├── cmd/server/         # Entry point
│   └── internal/           # ai, api, arr, auth, cache, chaptarr, codexapp,
│                           # config, credentials, db, discover, downloads, grokoauth, instance,
│                           # mcp, mcpserver, mediafiles, mediapath, nzbget, proxy, push, qbittorrent,
│                           # radarr, remediation, request, sabnzbd, secrets,
│                           # sonarr, tautulli, tmdb, trakt, transmission,
│                           # web, webhooks, websocket
│
├── app/                    # Flutter client (iOS, web) -- see app/README.md
│   ├── lib/
│   │   ├── core/           # Models, networking, realtime, theme, widgets
│   │   ├── features/       # auth, discover, request, dashboard, sonarr,
│   │   │                   # radarr, chaptarr, downloads, media_download, tautulli, issues,
│   │   │                   # ai_assistant, notifications, settings, ...
│   │   └── navigation/     # GoRouter with auth guard
│   └── test/
│
├── Dockerfile              # Multi-stage build (Flutter web + Go)
├── docker-compose.yml      # Full-stack deployment (push env pre-wired)
├── AGENTS.md               # Contributor/agent operating manual (CLAUDE.md imports it)
└── README.md               # This file

Configuration

Shared service credentials are managed through the admin UI -- no environment variables are needed for API keys. AI is different from the other integrations: an admin can configure a server profile using an API key or a shared OAuth link (OpenAI or xAI Grok), while every user can independently configure the same choices as a personal override. API keys and OAuth authorization stay encrypted and server-side. Self-hosted AI is its own provider: pick Local (OpenAI-compatible) in Settings > Providers & Credentials, enter the server's base URL (llama.cpp llama-server, vLLM, Ollama, and similar; use the endpoint's final URL, usually ending in /v1 -- redirects are not followed) and the model ID it hosts. No API key is needed (an optional token slot covers proxies that check auth), the same save-time test proves the endpoint before anything is stored, and personal OpenAI keys are unaffected and keep using api.openai.com. Both the Local and hosted OpenAI providers offer a reasoning-effort pin (Auto/None/Minimal/Low/Medium/High) -- None keeps thinking-heavy local models fast, Auto preserves the provider's own default. Every provider, model, remediation-model override, or key save -- and every completed OAuth selection -- must complete one small real, tool-free, low-reasoning message-response turn before Cantinarr activates it. Validation reports a safe actionable category for an invalid credential, unsupported model/access, exhausted quota, or temporary provider outage without exposing upstream secrets. OpenAI OAuth offers the recommended Codex model plus GPT-5.6 Sol, Terra, and Luna. xAI Grok (OAuth) signs in with a SuperGrok or X Premium+ account via xAI's device flow and serves the same Grok models as the API-key path.
The server also runs one small shared-model health turn every 24 hours by default. A failure opens one deduplicated admin-only issue; a later successful turn resolves it. Admins who want zero background AI usage can disable this check in Settings > Providers & Credentials without weakening the mandatory save-time test. The remediation agent remains independent of this monitor and always resolves credentials directly from the admin's shared profile.
Included AI is an explicit per-user entitlement for new accounts; the initial admin starts enabled. Upgrades preserve the previous global-provider behavior for existing users so access does not disappear, after which the admin can revoke or grant it from Settings > Users. Enabling an OpenAI OAuth-backed grant shows the shared-account allowance and cost warning before it is applied.
SettingWhereDescription
TMDB access tokenAdmin UIOptional -- discovery and search ship working on Cantinarr's built-in public key; add your own token to use your TMDB account instead (get one here)
Radarr/Sonarr instancesAdmin UIAdd via Settings > Add Instance
Chaptarr instanceAdmin UIBooks module; grant access per user from the instance editor or user settings -- full walkthrough in docs/books-setup.md
SABnzbd/qBittorrent/NZBGet/TransmissionAdmin UIDownload client modules (queue, history, speeds)
Tautulli instanceAdmin UIPlex activity, watch history, stats
Anthropic/OpenAI/Gemini/xAI API keyAdmin UIEnables shared API-key-backed AI chat and autonomous remediation
OpenAI reasoning effortAdmin UIOptional; pins reasoningeffort for the shared OpenAI provider (none/minimal/low/medium/high). Auto sends no effort field; endpoints that reject the field fall back automatically
Local (OpenAI-compatible)Admin UIFirst-class shared provider for self-hosted OpenAI-compatible servers: required base URL and model ID, optional key/token, own reasoning-effort pin. Shared profile only -- never selectable as a personal provider
OpenAI (OAuth)Personal link under Settings > AI Access, or an admin-managed shared linkUses a ChatGPT account's Codex allowance for the selected personal or included model; the admin-shared link also powers server-owned remediation. Per-user shared chat access is opt-in and carries a quota/cost warning
xAI Grok (OAuth)Personal link under Settings > AI Access, or an admin-managed shared linkUses an xAI account's Grok subscription allowance (SuperGrok or X Premium+) via xAI's device flow instead of a metered API key; the admin-shared link also powers server-owned remediation
Trakt client IDAdmin UIEnhances discovery + fallback ID bridging; required to select the Trakt trending source under Settings > Discovery, which the headline rows then adopt automatically
Discovery row sourceAdmin UISettings > Discovery: which feed backs the headline rows (Trakt when configured, else TMDB trending), plus the English-only filter (on by default)

Instance URLs are dialed only by the Cantinarr server -- phones and browsers never contact them, so cluster-internal names (Docker service names like http://radarr:7878, Kubernetes cluster DNS, Tailscale MagicDNS) are the recommended form, and the arrs never need to be exposed outside their network. One topology exception: a container that shares another container's network stack (network_mode: container:<gateway>, or Unraid's Container network type -- common when routing a service through a VPN gateway) has no address or DNS name of its own, so http://chaptarr:8789 never resolves. Point the instance URL at the gateway that publishes the port instead. The in-app Test Connection button runs from the server too, so it tells the truth about these URLs. Plain http is fully supported on a trusted network; https needs a certificate the server's container trusts (mount an internal CA into the image trust store -- a self-signed cert otherwise fails the connection test with an x509 error). Two service-specific notes: SABnzbd's hostname verification rejects service names it doesn't know, so add the name to its host_whitelist (Config > Special) or set the container's hostname to match; for Transmission enter just scheme://host:port -- Cantinarr appends /transmission/rpc. Poster and fanart images load on devices straight from the TMDB/TVDB CDNs, so client devices still need internet egress to those hosts.
Completed-media downloads are deliberately opt-in because Radarr, Sonarr, and Chaptarr report paths but do not serve those file bytes through their APIs. Configuration has two layers: the deployment makes each wanted library read-only to Cantinarr and lists the Cantinarr-visible boundary in CANTINARR_MEDIA_ROOTS, then the admin maps each media instance's reported path to a folder inside that boundary from the instance editor. The two paths do not have to match, and an arr source may use POSIX, Windows drive, or UNC syntax regardless of the Cantinarr host OS. For Docker, for example, mount - /mnt/nas/media:/media:ro, set CANTINARR_MEDIA_ROOTS=/media, and map Radarr's /data/media/movies to /media/movies; a native server instead uses an absolute local directory readable by its process. A Chaptarr instance may have separate mappings for /ebooks, /audiobooks, /yana-ebooks, and /yana-audiobooks; folder names never determine the book format.
Download controls are enabled per instance: an instance offers downloads only after an admin saves explicit path mappings for it, and every instance starts with media downloads off. Cantinarr accepts only live file IDs from a user's effective Radarr/Sonarr instance or granted Chaptarr instance, refuses files outside that instance's mappings and the global roots, and gives the app a short-lived file-scoped link so large files stream through the browser or operating system without buffering in Flutter. The feature covers the primary files indexed by the arrs, not arbitrary files, subtitles, or extras found on disk.
Optional server env vars for deployment tuning:
VariableDefaultDescription
CANTINARRPORT8585HTTP listen port. Kubernetes service-link values (tcp://…) injected by a Service named cantinarr are ignored in favor of the default; set a numeric value to override
CANTINARRSERVERNAMECantinarrDisplay name shown in clients
CANTINARRARRCALLBACKURLdirect request originOrigin the Radarr/Sonarr/Chaptarr containers POST webhooks back to, so it must be resolvable and reachable from the arrs themselves -- in same-network/cluster deployments a cluster-internal origin like http://cantinarr:8585 is usually the right value. Set it explicitly behind a reverse proxy (forwarded headers are deliberately ignored). Formerly CANTINARRPUBLICURL, which stays accepted forever (the new name wins when both are set); it was renamed because "public URL" suggested the user-facing address, which is the in-app Settings > External Address instead
CANTINARROAUTHISSUERrequest-derived originCanonical external HTTPS origin for inbound MCP OAuth metadata, token audience, and browser-origin checks; setting it also enables stable RFC 9207 authorization-response iss and permits that origin to call /mcp. Set it behind a reverse proxy and keep it stable (changing it makes existing audience-bound MCP tokens reconnect); do not substitute the arr-reachable CANTINARRARRCALLBACKURL
CANTINARRMCPALLOWEDORIGINSunsetComma-separated additional browser origins allowed to call /mcp. If neither this nor CANTINARROAUTHISSUER is configured, requests that supply Origin are rejected; native and server-side MCP clients need no entry
CANTINARRJWTSECRETauto-generatedHMAC secret for signing short-lived access tokens. Device sessions do not depend on it: changing it never signs anyone out
CANTINARRENCRYPTIONKEYauto-generated key fileBase64 32-byte key for secrets-at-rest (default: /config/encryption.key)
CANTINARRAIPROVIDERcodexFallback provider for the included server AI profile when none is saved in the admin UI (anthropic, openai, gemini, grok, codex, or grokoauth)
CANTINARRAIMODELprovider defaultFallback model for the included server AI profile when none is saved in the admin UI
CANTINARRCODEXBINauto-discoveredOptional path to codex-app-server or the full codex CLI; container images bundle the tested 0.144.3 app-server at /usr/local/bin/codex-app-server
CANTINARRCODEXRUNTIMEDIR/dev/shm/cantinarr-codexAbsolute Linux tmpfs/ramfs directory used for server-owned, ephemeral per-session Codex state; if it already exists, it must be owned by the server user with mode 0700
CANTINARRMEDIAROOTSunsetComma-separated absolute paths forming the outer filesystem allowlist for completed-media downloads. Empty disables file downloads. Mount libraries read-only inside these Cantinarr-visible roots, then map each arr-reported prefix to a path beneath them in that instance's settings; / is refused
CANTINARRPUSHGATEWAYURLunsetPush gateway origin -- setting it enables push notifications (auto-enrolls on first start)
CANTINARRPUSHAPIKEYunsetOptional pinned gateway key (blank = auto-enroll)
CANTINARRPUSHENROLLTOKENunsetOnly for gateways with gated enrollment
CANTINARRAPPLEAPPIDSunsetTeamID.BundleID values for native Apple passkeys (/.well-known/apple-app-site-association)
CANTINARRANDROIDPACKAGENAMEcodes.julian.cantinarrAndroid package name for native passkeys
CANTINARRANDROIDCERTSHA256FINGERPRINTSunsetAndroid signing cert fingerprints for /.well-known/assetlinks.json
CANTINARRWEBAUTHNEXTRAORIGINSunsetAdditional WebAuthn origins to trust
CANTINARRDISABLEUPDATECHECKunsetSet to 1 to disable the periodic GitHub release check behind the admin update-status endpoint

Source image builds also accept the Docker build argument CANTINARR_E2E_WEB_SEMANTICS (default false). It exists only for the disposable private lab: setting it to true compiles deterministic Maestro labels into the Flutter web bundle. Official production images keep the default and preserve normal browser accessibility semantics.
OpenAI (OAuth) source deployments use Codex app-server and are supported only on Linux; non-Linux hosts report this provider unavailable even when a Codex binary is installed. The runtime directory's parent must exist, and the directory must be on tmpfs or ramfs—not persistent storage. Give each concurrently running Cantinarr process its own runtime directory; startup removes stale session-* entries from that dedicated root. The official container uses its private Docker /dev/shm tmpfs. Use the tested Codex 0.144.3 release or a protocol-compatible build.
Native app passkeys require a public HTTPS server domain associated with the app (AASA for Apple, Digital Asset Links for Android). Browser passkey setup remains available when native association isn't possible. See server/README.md for details.
By default, users are passwordless and passkeyless: a connect link starts a permanent device session, so household members never deal with credentials. Each link signs one device into the app, once. A session never expires -- not from idle time, server restarts, upgrades, or secret rotation -- and ends only when an admin revokes the device (Settings > Devices) or deletes the user. Admins grant a password and/or passkey per user from Settings > Users when a user needs one -- that is also the durable way to sign in on the web, where clearing browser data wipes the device session a link created. A password is what authorizes MCP clients on deployments served over plain HTTP, where passkeys are unavailable (WebAuthn requires a secure context). Disabling a method is a real revoke -- it clears the stored password or deletes the user's passkeys. To recover access, an admin issues a fresh connect link.
Connect links embed a server address. Set Settings > External Address to the origin people reach your server through (a reverse proxy domain, a public IP) and links are built from it; left unset, a link uses the address the generating admin's own app is connected with, which usually only works on the admin's network -- the invite dialog says so when that happens.

How It Works

For Users

  1. Admin sends you a connect link
  2. Open the link on your device -- it creates your account and connects automatically
  3. Browse movies, TV shows, and books powered by TMDB, Trakt, and Chaptarr
  4. Tap "Request" on anything you want -- pick seasons for a show, or tap a book's eBook or Audiobook row to request that format
  5. Watch download progress live and get push notifications
  6. Something wrong with a file? Tap "Report a problem"; Cantinarr quietly watches for an in-flight Radarr/Sonarr recovery, then investigates only if the problem persists
  7. Ask the AI assistant for recommendations or to make requests for you. Use the included server provider when granted, or choose your own provider under Settings > AI Access

For Admins

  1. Deploy the container and complete the setup wizard
  2. Add your shared API credentials and service instances from Settings; for included AI, either add an Anthropic/OpenAI/Gemini/xAI key or link a shared OpenAI (OAuth) or xAI Grok (OAuth) account
  3. Generate connect links for your household (set Settings > External Address first so links work away from home), grant included AI access where wanted, and pin per-user default instances if you run several
  4. Optionally require approval for requests -- pending ones arrive as push notifications
  5. Instant updates come on by themselves: adding a Radarr/Sonarr/Chaptarr instance installs the server's authenticated webhook automatically (books need it most -- an ebook can finish downloading between two polls). Each instance's edit screen shows the live state and a Configure instant updates button to repair it -- e.g. after changing CANTINARR_ARR_CALLBACK_URL
  6. Manage everything from the app -- queues, stuck imports, issues, agent fixes. No config files.
  7. Updating means pulling the newer image and recreating the container -- see docs/updating.md. Optionally set an Update Portal link (Settings > Admin) so an in-app update warning jumps straight to your container manager.

ID Bridge (TMDB-to-TVDB)

The core technical challenge: TMDB has better metadata and APIs, but Sonarr only accepts TVDB IDs. Cantinarr solves this transparently:
Request: "Add The Last of Us" (TMDB ID 100088)

1. Cache check     -> miss
2. TMDB external_ids API -> tvdb_id: 392256 (hit!)
3. Cache result (30 days)
4. Sonarr lookup by tvdb:392256 -> exact match
5. Add to Sonarr with the user's effective quality profile + root folder

If TMDB doesn't have a TVDB mapping (rare), the bridge falls back to Trakt's cross-reference database, then to a Sonarr title search as a last resort -- accepted only when the candidate's premiere year matches TMDB's (±1), because same-titled series (a reboot vs the original) are distinct records and a request fails rather than fulfilling the wrong one.
Movies don't need bridging -- Radarr natively supports TMDB IDs. Books are keyed by Chaptarr/Readarr foreignBookId directly.

Tech Stack

ComponentTechnology
ServerGo 1.25, Chi router, SQLite (pure Go)
ClientFlutter (Dart), Riverpod, GoRouter
AuthJWT (HS256), bcrypt, connect tokens, WebAuthn passkeys
AIPersonal or admin-shared Anthropic, OpenAI, Gemini, and xAI Grok API credentials, plus personal or shared OpenAI OAuth via the bundled pinned Codex app-server and xAI Grok OAuth via xAI's device flow; SSE app streaming
MCPmcp-go, Streamable HTTP + inbound Cantinarr OAuth
Real-timegorilla/websocket + arr webhooks
PushSelf-hosted push gateway (APNs)
DiscoveryTMDB API v3, Trakt API v2 (server-proxied)
PackagingMulti-stage Docker with a checksum-verified pinned Codex app-server, go:embed, GHCR (ghcr.io/windoze95/cantinarr)

API Reference

Full API documentation is in server/README.md.

Related Projects

  • mam-chaptarr-protonvpn-skill -- an agent skill for building the layer below Cantinarr's books module: a VPN-isolated Gluetun/ProtonVPN stack running Chaptarr and qBittorrent in one network namespace, with forwarded-port sync, separated indexer and tracker-host sessions, and end-to-end verification. Useful if you're standing up book automation from scratch and want the container topology right the first time. Independent project; docs/books-setup.md shows where it fits.

Contributing

Pull requests are welcome. Three ways to shape the project:
  • Pull requests -- fork, branch, and open one. Run the checks first (go vet ./... and go test ./... from server/; flutter analyze --no-fatal-infos and flutter test from app/), and update any doc your change makes untrue. CI runs the same checks on every PR, and a first-time contributor's run needs one approval from the maintainer before it starts. For anything large, open an issue first so the shape can be agreed before you build it.
  • Bugs and technical issues -- open one on the issue tracker.
  • Feature requests -- post and vote at cantinarr.com/roadmap, no account needed.

AGENTS.md is the operating manual for the repo: branch protocol, verification commands, architecture conventions, and the documentation standard. It applies to human contributors and AI agents alike.

Support

If Cantinarr is useful to you, you can support its development through GitHub Sponsors.

Attribution

This product uses the TMDB API but is not endorsed or certified by TMDB.

License

AGPL-3.0 — See LICENSE for details.
Copyright (c) 2026 Julian Dice

Serve Cantinarr on your own domain behind Caddy, Nginx or Traefik. Fill in your domain and copy the result. It's a starting point, some apps need their own base URL or extra headers set too.

Proxying cantinarr.example.com to http://cantinarr:8585

Add this to your Caddyfile

cantinarr.example.com {
	reverse_proxy http://cantinarr:8585
}

Check the logs first

Nine times out of ten the logs tell you exactly what went wrong.

  • In Portainer, go to Containers, click the container, then Logs. Or run docker logs cantinarr
  • Exit codes help too: 137 means killed, usually out of memory. 126 or 127 means the command inside the image is broken.

Port already in use

If deployment fails with "Bind for 0.0.0.0:8585 failed: port is already allocated", something else on your server is using that port.

  • Find what's using it: sudo ss -tlnp | grep :8585
  • Stop the other service, or pick a different host port. In 8585:8585 only the left number is yours to change, the right one belongs to the app.

Running but the page won't load

The container is up but nothing appears in your browser.

  • Use your server's real IP: http://your-server-ip:8585. The 0.0.0.0 link Portainer shows isn't a real address.
  • Give it a minute after first deploy, cantinarr can take a while to initialise.
  • Make sure your firewall allows the port, e.g. sudo ufw allow 8585

Permission denied on volumes

If the logs show "permission denied", the app can't write to its data folder on the host.

  • Fix the ownership: sudo chown -R 1000:1000 /portainer/Files/AppData/Cantinarr/config

Image won't pull

Test the pull directly on the host: docker pull ghcr.io/windoze95/cantinarr:latest

  • "manifest unknown" means the tag no longer exists. This template uses latest, so try pinning a specific version instead.
  • "toomanyrequests" is the Docker Hub rate limit. Log in with docker login to raise it.
  • "no space left on device" means a full disk. Reclaim space with docker system prune

"exec format error"

This means the image was built for a different CPU architecture than your server.

  • This image supports: amd64, arm64
  • Check yours with uname -m: x86_64 is amd64, aarch64 is arm64. Raspberry Pi and other ARM boards are the usual culprits.

Container keeps restarting

The unless-stopped restart policy relaunches the app after every crash, so the real error can scroll past.

  • Check the logs right after a restart, the last few lines before it died are the useful ones.
  • Get the exit code with docker inspect cantinarr --format '{{.State.ExitCode}}'
  • Still stuck? Redeploy once with the restart policy set to no so the failure stays visible.

Required settings are blank

CANTINARR_ARR_CALLBACK_URL has no default value, and cantinarr may crash or misbehave if left empty.

  • Fill it in on the deploy screen before hitting deploy.

Raise an issue

Found something which isn't working as it should? Here's how to report it.

A single container

Cantinarr runs as one container, the simplest kind of app here. Just the one image to pull and nothing else wired up alongside it.

The app image

An image is the app packed up ready to go, everything Cantinarr needs bundled into one download. This template pulls ghcr.io/windoze95/cantinarr:latest, which Docker fetches once (about 139 MB) and then starts your own copy from.

Where the image comes from

Docker pulls its images from registries, public libraries of ready-built apps. Cantinarr's comes from the GitHub Container Registry, published by windoze95.

Version tags

The bit after the colon in the image name is the version tag. Here it's latest, which always points at the newest build, so a redeploy can bump you to a newer release without you asking. Pin a specific tag if you would rather stay on one version.

Which machines it runs on

Every image is built for particular CPU types. This one ships for amd64, arm64, so it runs on both regular x86 servers and ARM boards like a Raspberry Pi.

Ports

A port is the door the app answers on. A mapping like 8585:8585 means it's reachable on port 8585 of your server, where the left number is yours to change and the right one belongs to the app. It opens:

  • 8585:8585

Volumes

A volume is where Cantinarr keeps its files so they survive an update or a restart. Without one, anything it saves would sit inside the container and vanish the moment it's recreated. This template mounts:

  • /config from /portainer/Files/AppData/Cantinarr/config on the host

Environment variables

Environment variables are the settings you hand over when you deploy, things like a password or a timezone. Cantinarr takes 2 of them, and one needs a value before it'll start properly:

  • CANTINARR_PUSH_GATEWAY_URL, defaults to https://push.julian.codes. Push gateway for the companion iOS and Android apps. The server enrolls itself on first start; clear to disable push, or point it at your own gateway.
  • CANTINARR_ARR_CALLBACK_URL, needs a value. Origin your Radarr, Sonarr, and Chaptarr containers POST webhooks back to, for example http://192.168.1.10:8585. Leave blank to use whatever address you browse to. This is not the address people reach Cantinarr at; set that in the app under Settings, External Address. Formerly named CANTINARRPUBLICURL, which existing containers can keep using.

Restart policy

The restart policy here is unless-stopped, so Docker restarts Cantinarr after a crash or reboot, but leaves it off when you stop it on purpose. You can change this on the deploy screen. The choices are no (never restart), on-failure (only after a crash), unless-stopped (restart unless you stop it), and always (bring it back no matter what).

Networking

Nothing custom is set, so Cantinarr sits on Docker's default bridge network: its own private space that reaches the outside world only through the ports it publishes.

Container name

Once it's deployed, Portainer names the container cantinarr. That's what you'll spot in the containers list and use in commands like docker logs cantinarr.

Platform

The platform is linux, the kind of system the container is built to run on. Docker and Portainer handle this on a normal Linux server.

Open source license

Cantinarr is open source, released under the AGPL-3.0 license. In plain terms the code is out in the open, so you're free to run it and change it to fit what you need.

Portainer app templates

Zooming out, this whole page comes from a Portainer app template: a short recipe telling Portainer how to set Cantinarr up. Add the template list to Portainer once, then deploying Cantinarr is a click rather than a wall of config.