Skip to main content

Seerr Common — Shared Application Configuration

Seerr_Common is the shared application layer for Seerr. It is not deployed on its own; instead it supplies the Seerr-specific configuration that both Seerr_GKE and Seerr_CloudRun build on, so the two platform variants behave identically where it matters. End users never configure this layer directly — it has no deployment UI inputs of its own — but understanding what it provides explains the defaults you see in the platform docs.

For the infrastructure that actually provisions and runs Seerr, see the platform guides (Seerr_GKE, Seerr_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).


1. What Seerr is

Seerr is the February 2026 merger of Jellyseerr and Overseerr into a single project — a MIT-licensed, ~11.9k-star (pre-merger figure) request UI that sits in front of a Jellyfin, Plex, or Emby media server. Users browse and request titles; an admin approves the request, and Seerr calls Sonarr's and Radarr's APIs to trigger acquisition. The official image is ghcr.io/seerr-team/seerr — this catalogue correctly uses that path, not the older, now-superseded ghcr.io/fallenbagel/jellyseerr.

2. What this layer provides

AreaProvided by Seerr_CommonWhere it surfaces
Container imageThe genuinely prebuilt ghcr.io/seerr-team/seerr image — no custom buildcontainer_image output of the platform deployment
Database enginePostgreSQL 15, with the DB_TYPE=postgres env var set unconditionally§3 below
AuthenticationNo credentials seeded at all — Seerr's first admin comes from its own web setup wizardsecret_ids output (empty {})
Object storageDeclares the Cloud Storage storage bucket that backs /app/config, with a GKE-specific permission fixstorage_buckets output; §5 below
Health checksSupplies the default startup/liveness probes targeting /api/v1/status§6 below

3. The DB_TYPE trap

Seerr's datasource selection logic, confirmed by reading /app/dist/datasource.js inside the actual running image:

exports.isPgsql = process.env.DB_TYPE === 'postgres';

If DB_TYPE is not set to exactly postgres, Seerr silently falls back to an in-container SQLite database file — no error, no warning, and a deployment that otherwise looks completely healthy. Every write, including Seerr's own first-run setup, goes to a file wiped clean on the next restart or cold start. This is the same class of "deploy looks successful, data silently goes nowhere durable" bug this catalogue has documented in other modules (Wallabag's SYMFONY__ENV__DATABASE_DRIVER, Nextcloud's and Twenty's weak "is this installed?" checks).

Seerr_Common closes the gap unconditionally:

environment_variables = merge(
{ DB_TYPE = "postgres" },
var.environment_variables
)

DB_TYPE is listed first in the merge() call so a caller's own environment_variables cannot silently drop it unless it explicitly sets DB_TYPE to something else.

Seerr's connection variables otherwise follow the Foundation's standard naming — DB_HOST / DB_PORT (default 5432) / DB_USER / DB_NAME (default seerr) — except the password, which Seerr reads as DB_PASS, not DB_PASSWORD (confirmed against the same datasource.js source). Both Application Modules set db_password_env_var_name = "DB_PASS" to match. Migrations run automatically: Seerr's dist/index.js calls dbConnection.runMigrations() on every boot, so no separate db-init/ migrate job exists in this module.

4. Two distinct pieces of state

This is the single most important, non-obvious fact about Seerr's storage model, discovered by direct container inspection rather than from documentation:

docker exec <container> ls /app/config
# settings.json settings.old.json db/ logs/

Even with PostgreSQL fully configured and connected, Seerr still writes its own application settings — connected media servers (Jellyfin/Plex/Emby), discovery sliders, notification agents — to a plain settings.json file under CONFIG_DIRECTORY (default /app/config). PostgreSQL holds request and user data; settings.json holds everything else, and this is true regardless of the database backend. A persistent volume at /app/config is required in addition to the Postgres connection, or every app-level configuration choice is lost on the next cold start even though the request history in Postgres survives untouched.

Seerr_Common mounts a GCS-backed volume at this path whenever enable_gcs_storage_volume = true (the default on both platforms).

5. The GKE GCS-FUSE UID/GID bug

Seerr's container runs as uid=1000/gid=1000 (the node user — confirmed via docker run ghcr.io/seerr-team/seerr id), and on first boot attempts mkdir '/app/config/logs/'.

  • On Cloud Run, the platform's own gcsfuse integration automatically applies uid:1000/gid:1000 to the mounted volume — this works with no extra configuration.
  • On GKE, the GCS FUSE CSI driver does not default to a writable UID. Without an explicit fix, the mount is root-owned and the non-root container crash-loops with EACCES: permission denied.

Seerr_Common fixes this for both platforms uniformly with explicit mount_options on the storage volume it declares:

locals {
_seerr_extra_storage_volumes = var.enable_gcs_storage_volume ? [
{
name = "storage"
mount_path = "/app/config"
read_only = false
mount_options = [
"implicit-dirs", "stat-cache-ttl=60s", "type-cache-ttl=60s",
"uid=1000", "gid=1000", "file-mode=0664", "dir-mode=0775",
]
}
] : []
}

The uid/gid/file-mode/dir-mode options are a harmless no-op on Cloud Run and load-bearing on GKE. This is a known bug class in this catalogue — the fleet-wide "GKE gcsfuse UID/GID permission denied" finding was previously hit and fixed on Paperless, CodeServer, and CloudBeaver's GKE variants; Seerr is the latest confirmed instance, now fixed at this shared layer so both Application Modules inherit it identically.

6. Health probe defaults

Both the startup and liveness probes target GET /api/v1/status, which returns an unauthenticated 200 with JSON ({"version":...,"commitTag":...}) once the app is ready — confirmed via local container testing and a live deployment on both platforms.

  • Startup probeinitial_delay = 20s, timeout = 10s, period = 15s, failure_threshold = 20.
  • Liveness probeinitial_delay = 30s, timeout = 5s, period = 30s, failure_threshold = 3.

7. Prebuilt image — no custom build

Unlike apps in this catalogue that layer a thin custom Dockerfile onto an upstream image, Seerr_Common sets image_source = "prebuilt" and container_build_config.enabled = false. The scripts/ subdirectory is empty — no Dockerfile, no cloud entrypoint, no build step. The Foundation deploys ghcr.io/seerr-team/seerr directly at the requested application_version tag.

8. No credentials seeded — by design

secret_ids outputs an empty map ({}). Unlike many applications in this catalogue that generate an admin password in Secret Manager, Seerr_Common seeds none — Seerr's first admin account is created entirely through the app's own web-based setup wizard on first access, backed by the already-provisioned PostgreSQL database. The only Secret Manager secret associated with a Seerr deployment is the Foundation's own generated database password.

9. Single-writer concurrency

Seerr_Common's own max_instance_count variable defaults to 1, because settings.json is a single mutable file rather than a database with transactional writes — concurrent instances risk a lost-write race on that file.

This default is not what actually reaches a deployment, however: both Seerr_CloudRun and Seerr_GKE's own max_instance_count variables default to 5, and each variant forwards var.max_instance_count into Seerr_Common, overriding its internal default of 1. Operators who need the conservative, single-writer-safe behavior should set max_instance_count = 1 explicitly at the Application Module level.


For the Seerr-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: Seerr_GKE and Seerr_CloudRun.