Self-hosted service
mediacms-io/mediacms avatar
mediacms-io/mediacms

MediaCMS: a self-hosted video portal you install with Docker Compose

MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API.

5,133 stars961 forksJavaScriptAGPL-3.0

At a glance

What is it?
MediaCMS is a Django and React video and media CMS under AGPL-3.0, aimed at schools, organisations and community portals that need to publish and share media on their own servers. Its Docker Compose file is the fastest route in, and its transcoding and storage model is the part to understand before you commit.
Who is it for?
Adopt MediaCMS if you need a self-hosted portal for video, audio, image and PDF publishing with role-based access, and you are willing to run PostgreSQL, Redis, Celery and FFmpeg alongside it. Do not adopt it if you only want to stream a personal library to your own devices, because that is a media server job, not a publishing CMS job.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What MediaCMS is for, and who should run it

MediaCMS is a publishing platform for video and other media, written mostly in Python with Django and React, and shipped with a REST API. The README frames it as a way to "build a small to medium video and media portal within minutes," and lists universities, schools and organisations handling sensitive content among its example cases. The distinction that matters is between publishing and playback. A media server streams files you already own to devices you control. MediaCMS is the other thing: it takes uploads, transcodes them into multiple resolutions and profiles, applies publishing workflows and permissions, and serves the result through a website with search, playlists and embed codes. If your problem is "we need a place where staff and students upload and watch each other's material without sending it to a third party," this is the category of tool you are looking for. If your problem is "I want to watch my film collection on the television," it is not.

The stack behind an upload: Django, Celery, FFmpeg and HLS

The architecture is visible in the repository layout rather than in prose. The top level holds cms/, uploader/, users/, rbac/, lti/, saml_auth/ and frontend/, with a manage.py at the root and a requirements.txt that pins Django 5.2.6, djangorestframework, Celery, django-redis, psycopg and gunicorn. The README's technology list adds PostgreSQL, Redis, Nginx, FFmpeg and Bento4. So the data flow is roughly this: the React frontend and the REST API talk to Django, uploads land through the chunked upload path in uploader/, and transcoding is handed to Celery workers that call FFmpeg. The README states that transcoding runs through priorities, which is the mechanism that keeps a long 1080p job from blocking a short one. Finished output is served as HLS for adaptive streaming, and the README notes the system keeps originals, encoded versions and HLS renditions, which is where the storage multiplier comes from. Automatic transcription runs through Whisper locally, and the README explicitly says that supporting it means considering more CPUs. That is a real architectural consequence: transcription is not an API call to someone else's service, it is compute on your own box.

Installing MediaCMS with Docker Compose

The README gives two installation routes: Docker Compose, documented in docs/admins_docs.md under the Docker installation section, and an automation script that installs and configures the services on a server. The Compose file at the repository root is the one to read first, because it shows what actually has to run. It defines separate services for migrations, web, celery_beat, celery_worker and db, plus redis, and the image is mediacms/mediacms:latest. The web service publishes port 80 and the database service uses postgres:17.2-alpine. Each service switches capabilities on or off with environment variables such as ENABLE_UWSGI, ENABLE_NGINX, ENABLE_CELERY_SHORT, ENABLE_CELERY_LONG, ENABLE_CELERY_BEAT and ENABLE_MIGRATIONS, so the same image acts as web server, worker or one-shot migration runner depending on which flags are set.

The migrations service is where the first admin account is created. The Compose file sets ADMIN_USER and ADMIN_EMAIL, and leaves ADMIN_PASSWORD commented out with the instruction to uncomment and set a password:

yaml
    environment:
      ENABLE_UWSGI: 'no'
      ENABLE_NGINX: 'no'
      ENABLE_CELERY_SHORT: 'no'
      ENABLE_CELERY_LONG: 'no'
      ENABLE_CELERY_BEAT: 'no'
      ADMIN_USER: 'admin'
      ADMIN_EMAIL: 'admin@localhost'
      # ADMIN_PASSWORD: 'uncomment_and_set_password_here'
    command: "./deploy/docker/prestart.sh"

Set that password before the first run rather than after, then bring the stack up. The README does not spell out the exact command in the section reproduced here, but Compose reads the file at the root by default:

bash
docker compose -f docker-compose.yaml up -d

What you should see is the migrations container run prestart.sh once, exit, and the web container come up bound to port 80. The Compose file also ships docker-compose-dev.yaml and docker-compose.full.yaml, so check which one matches the mode you want before assuming the root file is the right target. Once the portal answers, the practical first task is uploading a short clip and watching the transcoding profiles appear, because that is the step that exercises Celery, FFmpeg and the storage layout together. If the upload sits in a pending state, the worker is the thing to look at, not the web container.

Where the hardware and disk numbers bite

The README is unusually direct about sizing, and the numbers are worth taking literally. For a small to medium installation with a few hours of video uploaded daily and a few hundred active daily users, it suggests 4GB RAM and 2 to 4 CPUs as a minimum, and says larger installations should add more of both. For disk, it gives a rule: multiply expected uploaded video size by three, because the system keeps originals, encoded versions and HLS. The worked example is 1G of video per day retained for a year, which lands at roughly 1T of disk. That multiplier is the single most underestimated part of running this. A portal that accepts 1080p uploads and generates 144p through 1080p renditions in h264, h265 and vp9 will not consume three times the source size in every case, but the README's own guidance is the number to plan against. Whisper transcription adds CPU pressure on top of that, and the README says so plainly rather than treating it as free. The honest limitation here is that MediaCMS is not a lightweight single-binary application. You are operating PostgreSQL, Redis, a Celery worker pool, a Celery beat scheduler, Nginx and Gunicorn, and the failure modes are the failure modes of that stack: a stuck worker queue, a full disk from renditions, a database that needs a backup strategy you have not written yet.

MediaCMS compared with Jellyfin and PeerTube

The comparison people search for is MediaCMS against Jellyfin, and the difference is categorical rather than a matter of features. Jellyfin is a media server: it indexes a library of files you already have, scrapes metadata, and streams to clients. It has no concept of a user uploading a video, waiting for transcoding, choosing a publishing workflow, or generating an embed code. MediaCMS inverts that. The media arrives through the platform, gets processed by it, and is published by it, with the README listing public, private, unlisted and custom workflows and role-based access control as first-class features. PeerTube is the closer comparison, since both are self-hosted video publishing platforms with transcoding and federation ambitions. The README does not describe federation for MediaCMS, so if inter-instance following is a requirement, that is the axis to check before choosing. What MediaCMS does carry that a plain media server does not is the institutional feature set: SAML support with mappings to system roles and groups, LTI 1.3 plus a Moodle plugin for embedding media in an LMS, and configurable actions for downloads, comments, likes and reports. Those are the features that make it a plausible fit for a university rather than a hobbyist.

Licence and the cost of staying current

MediaCMS is released under the GNU Affero General Public License v3.0, copyright Markos Gogoulos. The AGPL is the variant that treats network use as distribution, so if you modify MediaCMS and let users interact with it over a network, the licence's source-availability obligation is the thing your legal team needs to read, not this article. Running it unmodified as a portal is the ordinary case. On maintenance, the repository is not archived and the last push was on 2026-09-10, with releases v8.4.0 and v8.3.5 on 2026-08-25 and v8.3.4 on 2026-07-15. The release tooling in package.json is semantic-release with conventional commits, which tells you how versions are cut, not how much work each one contains. Upgrading means pulling a new mediacms/mediacms image and running migrations through the same prestart path, and because the Compose setup bind-mounts the repository directory into every container, a local checkout and a pulled image can drift apart in ways that are easy to miss. The README does not document rollback. If you need a rollback story, you are writing it yourself, and that should shape how you take database backups before an upgrade rather than after.

Editorial conclusion

Adopt MediaCMS if you need a self-hosted portal for video, audio, image and PDF publishing with role-based access, and you are willing to run PostgreSQL, Redis, Celery and FFmpeg alongside it. Do not adopt it if you only want to stream a personal library to your own devices, because that is a media server job, not a publishing CMS job. Before you commit, verify that your disk budget covers the roughly three times multiplier the README describes for originals, encoded versions and HLS, and confirm which of the two installation paths, Docker Compose or the server automation script, matches the environment you actually operate.

Frequently asked questions

How do I install MediaCMS?

The README gives two routes: Docker Compose, documented in docs/admins_docs.md under the Docker installation section, and an automation script that installs and configures the required services on a server. The Docker route uses the image mediacms/mediacms:latest with the Compose file at the repository root.

Is MediaCMS free?

It is open source under the GNU Affero General Public License v3.0, so there is no licence fee. The README separately mentions paid services for custom installations, extra development, migrations, training and support, and a commercial hosting option through Elestio.

What is MediaCMS?

MediaCMS is a self-hosted video and media CMS written mostly in Django and React, with a REST API documented through Swagger. It supports video, audio, image and PDF media types, and the README describes it as suitable for building a small to medium video and media portal.

How does MediaCMS compare with Jellyfin?

They solve different problems. Jellyfin is a media server for streaming a library you already have, while MediaCMS is a publishing platform where media is uploaded, transcoded into multiple resolutions and profiles, and released through public, private, unlisted or custom workflows with role-based access control.

Official sources

  1. License: AGPL-3.0
  2. mediacms-io/mediacms on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mediacms-io-mediacms.svg)](https://hysenlabs.com/projects/mediacms-io-mediacms)