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 own Emby, Ombi and Watch Party services.

Unraid Deployment

MediaOps is designed to run on Unraid without copying the source repository to the server. Unraid pulls prebuilt images from GHCR and stores application-owned runtime data in appdata.

V1 Discord bot model

MediaOps v1 is self-hosted and single-tenant. Before installing the container, create your own Discord application/bot using DISCORD_BOT_SETUP.md.

The public MediaOps Community demo bot is not a universal bot for other operators. Do not reuse one v1 MediaOps instance across unrelated Discord guilds with different Emby/Ombi/Watch Party backends because provider configuration is global to the container.

MediaOps image

Stable/default image:

ghcr.io/miakkia/mediaops:latest

Development image:

ghcr.io/miakkia/mediaops:dev

Recommended container settings

  • Name: MediaOps
  • Repository: ghcr.io/miakkia/mediaops:latest
  • Network Type: Bridge
  • Privileged: Off
  • WebUI: none currently
  • Ports: none currently required

Persistent appdata

Field Value
Name MediaOps Data
Container Path /data
Host Path /mnt/user/appdata/mediaops
Access Mode Read/Write

Set MEDIAOPS_DATA_DIR=/data.

Core variables

Name Key Default / value Notes
Bot Display Name MEDIAOPS_BOT_NAME MediaOps Bot Public-facing name; does not rename the Discord application
Media Server Display Name MEDIAOPS_SERVER_NAME My Media Server Friendly server/community name
Media Provider MEDIA_PROVIDER emby Current supported provider
Discord Token DISCORD_TOKEN empty Secret for your own Discord bot
Discord Client ID DISCORD_CLIENT_ID empty Application ID for your own Discord app
Discord Guild ID DISCORD_GUILD_ID empty Discord server ID for this deployment
Emby URL EMBY_URL empty URL reachable from the container
Emby API Key EMBY_API_KEY empty Secret
Request Provider REQUEST_PROVIDER none Use ombi to enable requests
Ombi URL OMBI_URL empty Required for Ombi requests
Ombi API Key OMBI_API_KEY empty Secret
Ombi Auto Approve OMBI_AUTO_APPROVE false Request approval behavior
Watch Party URL WATCHPARTY_URL empty Public/base Watch Party URL
MediaOps Locale MEDIAOPS_LOCALE en en or fr
MediaOps Timezone MEDIAOPS_TIMEZONE America/Toronto IANA timezone
MediaOps Data Directory MEDIAOPS_DATA_DIR /data Leave at /data

Additional Watch Party host credentials and optional Forum settings are exposed by the template and documented in .env.example.

Existing customized installations

A Force Update preserves existing Unraid environment variables. Older customized installations can retain their names with:

MEDIAOPS_BOT_NAME=Cinema Helper
MEDIAOPS_SERVER_NAME=Example Cinema

Discord setup before first start

Follow DISCORD_BOT_SETUP.md to:

  1. create your Discord application and bot;
  2. enable the required gateway intents;
  3. grant least-privilege bot permissions (not Administrator);
  4. install the bot into the intended guild;
  5. copy the application ID, bot token, and server ID;
  6. optionally disable public installation after the bot is installed.

For normal v1 self-hosting, use an operator-owned bot identity for this MediaOps deployment.

Optional Media Request Forum

The Forum integration is optional. Configure the complete identifier set to enable it; incomplete configuration remains fail-closed.

Required identifiers include MEDIA_REQUESTS_FORUM_ID, MEDIA_REQUESTS_WEBHOOK_ID, Requested/Processing/Available/Failed/Denied tag IDs, and Movie/Series tag IDs.

See REQUEST_FORUM.md.

Companion Ombi Discord Router

The optional Forum workflow uses a separate companion container:

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

Create a user-defined Docker network and attach Ombi plus the router:

docker network create mediaops-backend

Ombi can then use:

http://ombi-discord-router:8080/ombi

Do not use a changing container IP address.

Router appdata

Field Value
Name Router Data
Container Path /data
Host Path /mnt/user/appdata/ombi-discord-router/data
Access Mode Read/Write

The router's /data/media-threads.json belongs to that router instance and Discord Forum destination. Separate demo/production router instances should use separate appdata and separate webhook/tag configuration.

Router security defaults

The template applies a non-root user, read-only root filesystem, /tmp tmpfs, dropped Linux capabilities, no-new-privileges, a 256 MB memory limit, and a 64 PID limit. The router publishes no host port; keep /ombi private to the Docker/LAN trust boundary.

Router health validation

docker exec ombi-discord-router \
  python -c 'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:8080/health").read().decode())'

First start

Before starting MediaOps, stop any other instance using the same Discord bot token.

Expected startup:

MediaOps Bot connected as <bot tag>
Loaded 15 Discord commands: ...
Watch Party lifecycle scheduler started.
Runtime-aware Watch Party expiry scheduler started.

Register guild commands directly from the running container:

docker exec MediaOps npm run deploy-commands

The runtime executes compiled dist/deploy-commands.js; tsx, TypeScript, Git, and a source checkout are not required.

Recommended Discord setup panels

  • /mediaops-setup — user-facing media guide;
  • /watchparty-setup — Watch Party self-service panel;
  • /mediaops-admin-setup — private diagnostics guide.

/ping, /health, /mediaops-setup, /watchparty-setup, and /mediaops-admin-setup require Manage Server for the invoking human member by default. Manage Server is not a required bot permission.

Acceptance test

For a clean public-release validation, test at minimum:

  1. create/secure a new operator-owned Discord bot from the public guide;
  2. container startup and persistent /data;
  3. npm run deploy-commands from inside the running container;
  4. /health as a server manager;
  5. /mediaops-setup and /watchparty-setup;
  6. /movie, /tv, and /latest;
  7. /request when Ombi is enabled;
  8. one Ombi -> router -> Forum lifecycle when Forum synchronization is enabled;
  9. /watchpartyrandom and one manually scheduled Watch Party;
  10. RSVP and T-15 reminder;
  11. automatic room opening and direct join link;
  12. organizer cancellation after opening;
  13. tracked Discord message cleanup;
  14. /watchparty-upcoming and /watchparty-status;
  15. container recreation/restart with persistent state retained.

Updating

For MediaOps:

  1. pull/Force Update ghcr.io/miakkia/mediaops:latest;
  2. keep appdata and environment variables;
  3. restart/recreate if required;
  4. run docker exec MediaOps npm run deploy-commands when command definitions changed;
  5. verify logs and /health.

For the router, preserve /data, network, webhook/tag configuration, then validate /health and a lifecycle notification after material updates.

Community Apps

The repository contains ca_profile.xml, main/companion templates, icon, GPLv3 licensing, project/documentation links, and public GHCR image paths.

Before Community Apps submission:

  • verify the Unraid template links users to DISCORD_BOT_SETUP.md;
  • install once as a clean self-hosted operator;
  • run the current Validate and Scan workflows;
  • resolve every reported issue;
  • verify secret fields are masked where supported.

Security posture

  • operator-owned Discord bot/token per v1 deployment;
  • no public universal demo bot for unrelated guilds;
  • no privileged mode;
  • no Docker socket;
  • no Movies/Series/Downloads mounts;
  • no unnecessary host ports;
  • secrets supplied only at runtime;
  • persistent access limited to application-owned appdata;
  • optional Forum automation scoped to configured Forum/integration source;
  • least-privilege Discord permissions/intents;
  • router webhook credential isolated from the MediaOps bot token.

See SECURITY.md and SECURITY_MODEL.md.

Watch Party lifecycle notes

The lifecycle scheduler refreshes state every minute, sends one reminder approximately 15 minutes before start, persists reminder state across restarts, automatically opens the room at start time, and ignores terminal sessions for future reminders.

Active expiry uses Emby runtime plus a 45-minute grace period. If runtime information is unavailable, the fallback is 4.5 hours. Organizer cancellation remains available after activation. Discord message cleanup may occur on the following scheduler pass.

Install MediaOps on Unraid in a few clicks.

Find MediaOps in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for MediaOps Review the template variables and paths Click Install

Requirements

Create your own Discord application/bot first (see docs/DISCORD_BOT_SETUP.md), plus outbound Internet access for Discord and network access to your configured Emby, Ombi and Watch Party services. 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-08-28
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
Media ProviderVariable

Media provider backend. Emby is currently supported.

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.

Target
EMBY_URL
Emby API KeyVariable

Emby API key used by MediaOps.

Target
EMBY_API_KEY
Request ProviderVariable

Optional request backend. Use ombi to enable /request after configuring Ombi URL and API key.

Target
REQUEST_PROVIDER
Default
none
Ombi URLVariable

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

Target
OMBI_URL
Ombi API KeyVariable

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

Target
OMBI_API_KEY
Ombi Auto ApproveVariable

true automatically approves newly created requests; false leaves them pending for an Ombi administrator.

Target
OMBI_AUTO_APPROVE
Default
false
Media Requests Forum IDVariable

Optional Discord Forum channel used for Ombi 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.

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