MediaOps

MediaOps

Docker app from MediaOps' Repository

Overview

MediaOps is a self-hosted, single-tenant Discord automation platform for media-server communities. Each operator creates their own Discord application/bot and connects this container to their selected media and request providers.

Unraid Deployment

MediaOps runs on Unraid from prebuilt GHCR images and stores application-owned runtime data in appdata.

Images

Stable/default:

ghcr.io/miakkia/mediaops:latest

Development:

ghcr.io/miakkia/mediaops:dev

Recommended basics:

  • Network Type: Bridge
  • Privileged: Off
  • Docker socket: not mounted
  • Movies/Series/Downloads shares: not mounted for current provider API workflows
  • MediaOps host ports: none required for current core features
  • Persistent data: /mnt/user/appdata/mediaops -> /data

Provider selection

The current Unraid template supports:

MEDIA_PROVIDER=emby | jellyfin
REQUEST_PROVIDER=ombi | seerr

Only configure credentials for the providers you select.

Existing Emby + Ombi installations

Existing installations remain supported:

MEDIA_PROVIDER=emby
EMBY_URL=http://YOUR-EMBY-HOST:8096
EMBY_API_KEY=REPLACE_ME

REQUEST_PROVIDER=ombi
OMBI_URL=http://YOUR-OMBI-HOST:3579
OMBI_API_KEY=REPLACE_ME

OMBI_AUTO_APPROVE is no longer used by current MediaOps. Ombi's own user/role policy controls approval. An older installation that had OMBI_AUTO_APPROVE=false can safely remove that variable when updating.

Jellyfin + Seerr

MEDIA_PROVIDER=jellyfin
JELLYFIN_URL=http://YOUR-JELLYFIN-HOST:8096
JELLYFIN_API_KEY=REPLACE_ME

REQUEST_PROVIDER=seerr
SEERR_URL=http://YOUR-SEERR-HOST:5055
SEERR_API_KEY=REPLACE_ME

When Jellyfin/Seerr are selected, Emby/Ombi credentials are not required.

Discord setup

Each self-hosted MediaOps deployment uses its operator-owned Discord application/bot. Configure:

DISCORD_TOKEN
DISCORD_CLIENT_ID
DISCORD_GUILD_ID

Use least-privilege Discord permissions rather than Administrator.

Register guild commands after startup:

docker exec MediaOps npm run deploy-commands

Persistent data

Main MediaOps state:

Container path Recommended Unraid host path
/data /mnt/user/appdata/mediaops

Set MEDIAOPS_DATA_DIR=/data.

MediaOps Discord Router

The companion router retains its historical image name for compatibility:

ghcr.io/miakkia/mediaops-ombi-discord-router:latest

It now accepts both provider webhook routes:

POST /ombi
POST /seerr

The router is a messenger/routing addon. It can create a Discord Forum post, persist the provider/media/request-to-thread association, append lifecycle updates to the same thread, and update Forum lifecycle tags.

Recommended router appdata:

/mnt/user/appdata/mediaops-discord-router/data -> /data

Keep the mapped directory writable by the configured non-root router UID/GID. Do not use 777, privileged mode, or root execution as a workaround.

For dynamic Forum tag changes, inject DISCORD_BOT_TOKEN at runtime and grant only the Discord permissions required for the target Forum/thread.

Prefer a private user-defined Docker network between Ombi/Seerr and the router. No public router port is required when the request provider can address the router by container name.

Updating from latest

  1. Force Update/pull ghcr.io/miakkia/mediaops:latest.
  2. Preserve /data and your environment variables.
  3. Keep your selected provider values.
  4. Run /health after restart.
  5. Test /movie, /tv, /latest, and /request.
  6. If using the Router, verify one provider webhook lifecycle and confirm same-thread status/tag changes.

Adding Jellyfin/Seerr support does not require an existing Emby/Ombi operator to migrate providers.

Upgrade acceptance checks

For Emby + Ombi:

  • /health reports Emby and Ombi online;
  • /movie, /tv, /latest still return expected library results;
  • /request creates an Ombi request under Ombi's configured approval policy;
  • Watch Party behavior remains operational when configured.

For Jellyfin + Seerr:

  • /health reports Jellyfin and Seerr online;
  • movie/series search and artwork work;
  • request search/create works;
  • Router /seerr events reach the Discord Forum when enabled.

Security posture

  • non-root runtime;
  • no privileged mode;
  • no Docker socket;
  • no direct media-library mounts for current API workflows;
  • no unnecessary host ports;
  • secrets supplied only at runtime;
  • private Docker/LAN provider communication preferred;
  • appdata access limited to application-owned state.

See SECURITY_MODEL.md and the repository SECURITY.md.

Requirements

Create your own Discord application/bot first (see docs/DISCORD_BOT_SETUP.md), plus outbound Internet access for Discord and private network access to your selected media/request providers. V1 is not a universal hosted multi-tenant bot.

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/miakkia/mediaops:latest
Last Updated2026-10-13
First Seen2026-08-28

Runtime arguments

Network
bridge
Shell
bash
Privileged
false

Template configuration

MediaOps DataPathrw

Persistent MediaOps runtime data and scheduled Watch Party state.

Target
/data
Default
/mnt/user/appdata/mediaops
Bot Display NameVariable

Public-facing bot name used by MediaOps panels and messages. This does not rename your Discord application itself.

Target
MEDIAOPS_BOT_NAME
Default
MediaOps Bot
Media Server Display NameVariable

Friendly media-server/community name used in public MediaOps panels.

Target
MEDIAOPS_SERVER_NAME
Default
My Media Server
Demo ModeVariable

Set true for public/demo deployments. MediaOps keeps Watch Party controls visible but disables join links so configured Watch Party URLs are never emitted into Discord.

Target
MEDIAOPS_DEMO_MODE
Default
false
Media ProviderVariable

Media provider backend. Supported values: emby or jellyfin.

Target
MEDIA_PROVIDER
Default
emby
Discord TokenVariable

Secret token for the Discord bot/application you created for this MediaOps deployment.

Target
DISCORD_TOKEN
Discord Client IDVariable

Application/client ID for your Discord application.

Target
DISCORD_CLIENT_ID
Discord Guild IDVariable

Discord server/guild ID for this self-hosted deployment and guild-scoped command registration.

Target
DISCORD_GUILD_ID
Emby URLVariable

Emby base URL reachable from this container. Required only when MEDIA_PROVIDER=emby.

Target
EMBY_URL
Emby API KeyVariable

Emby API key used by MediaOps. Required only when MEDIA_PROVIDER=emby.

Target
EMBY_API_KEY
Jellyfin URLVariable

Jellyfin base URL reachable from this container. Required only when MEDIA_PROVIDER=jellyfin.

Target
JELLYFIN_URL
Jellyfin API KeyVariable

Jellyfin API key used by MediaOps. Required only when MEDIA_PROVIDER=jellyfin.

Target
JELLYFIN_API_KEY
Request ProviderVariable

Optional request backend. Supported values: none, ombi or seerr.

Target
REQUEST_PROVIDER
Default
none
Ombi URLVariable

Ombi base URL reachable from MediaOps. Required only when REQUEST_PROVIDER=ombi.

Target
OMBI_URL
Ombi API KeyVariable

Ombi API key used to search and create requests. Required only when REQUEST_PROVIDER=ombi.

Target
OMBI_API_KEY
Seerr URLVariable

Seerr base URL reachable from MediaOps. Required only when REQUEST_PROVIDER=seerr.

Target
SEERR_URL
Seerr API KeyVariable

Seerr API key used to search and create requests. Required only when REQUEST_PROVIDER=seerr.

Target
SEERR_API_KEY
Media Requests Forum IDVariable

Optional Discord Forum channel used for request lifecycle synchronization.

Target
MEDIA_REQUESTS_FORUM_ID
Media Requests Webhook IDVariable

Exact Discord webhook ID allowed to drive Forum status changes. This is an ID, not the secret webhook URL/token.

Target
MEDIA_REQUESTS_WEBHOOK_ID
Requested Status Tag IDVariable

Discord Forum tag ID for newly submitted requests.

Target
MEDIA_TAG_REQUESTED
Processing Status Tag IDVariable

Discord Forum tag ID for approved/in-process requests.

Target
MEDIA_TAG_PROCESSING
Available Status Tag IDVariable

Discord Forum terminal tag ID for available media.

Target
MEDIA_TAG_AVAILABLE
Failed Status Tag IDVariable

Discord Forum terminal tag ID for failed processing/acquisition.

Target
MEDIA_TAG_FAILED
Denied Status Tag IDVariable

Discord Forum terminal tag ID for denied/rejected requests.

Target
MEDIA_TAG_DENIED
Movie Media Tag IDVariable

Discord Forum tag ID identifying movie requests.

Target
MEDIA_TAG_MOVIE
Series Media Tag IDVariable

Discord Forum tag ID identifying TV-series requests.

Target
MEDIA_TAG_SERIES
Watch Party URLVariable

Public URL of the Watch Party service. Demo Mode prevents this URL from being emitted into Discord.

Target
WATCHPARTY_URL
Watch Party Internal URLVariable

Optional LAN/internal Watch Party URL used by MediaOps API calls.

Target
WATCHPARTY_INTERNAL_URL
Watch Party Emby UserVariable

Dedicated non-admin Emby account used to create and host Watch Parties.

Target
WATCHPARTY_EMBY_USER
Watch Party Emby PasswordVariable

Password for the dedicated Watch Party Emby account.

Target
WATCHPARTY_EMBY_PASSWORD
Watch Party Retention DaysVariable

Days to keep completed/cancelled Watch Party history.

Target
WATCHPARTY_RETENTION_DAYS
Default
30
MediaOps LocaleVariable

Default language for automated messages. Supported: en or fr.

Target
MEDIAOPS_LOCALE
Default
en
MediaOps TimezoneVariable

IANA timezone used for Watch Party scheduling input without an explicit offset.

Target
MEDIAOPS_TIMEZONE
Default
America/Toronto
MediaOps Data DirectoryVariable

Container path for persistent runtime data.

Target
MEDIAOPS_DATA_DIR
Default
/data