How to Self-Host MediaCMS with Docker Compose

What Is MediaCMS?

MediaCMS is a self-hosted video and media CMS built with Django and React. Think of it as your own YouTube — users can upload videos, the platform transcodes them to multiple resolutions, generates HLS streams for adaptive playback, and serves them through a modern web interface. It also handles images, audio, and PDFs.

MediaCMS supports multi-user accounts with permissions, categories, tags, playlists, comments, and an admin dashboard. It’s designed for organizations that need a private video platform — universities, companies, media teams, or anyone who wants video hosting without YouTube’s tracking and terms of service.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 4 GB of RAM minimum (transcoding is memory-intensive)
  • 2+ CPU cores (more is better for video transcoding)
  • Sufficient storage for video files (plan for 3x original file size due to transcoding)
  • A domain name (optional, for remote access)

Docker Compose Configuration

MediaCMS uses 6 services: database initialization (runs once), web application, Celery beat scheduler, Celery worker (handles transcoding), PostgreSQL, and Redis.

Create a docker-compose.yml file:

services:
  migrations:
    image: mediacms/mediacms:7.6.0
    command: ./deploy/docker/prestart.sh
    restart: "no"
    depends_on:
      redis:
        condition: service_healthy
      db:
        condition: service_healthy
    environment:
      ENABLE_UWSGI: "no"
      ENABLE_NGINX: "no"
      ENABLE_CELERY_BEAT: "no"
      ENABLE_CELERY_SHORT: "no"
      ENABLE_CELERY_LONG: "no"
      ENABLE_MIGRATIONS: "yes"
    volumes:
      - mediacms-data:/home/mediacms.io/mediacms

  web:
    image: mediacms/mediacms:7.6.0
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      migrations:
        condition: service_completed_successfully
    environment:
      ENABLE_UWSGI: "yes"
      ENABLE_NGINX: "yes"
      ENABLE_CELERY_BEAT: "no"
      ENABLE_CELERY_SHORT: "no"
      ENABLE_CELERY_LONG: "no"
      ENABLE_MIGRATIONS: "no"
    volumes:
      - mediacms-data:/home/mediacms.io/mediacms

  celery_beat:
    image: mediacms/mediacms:7.6.0
    restart: unless-stopped
    depends_on:
      redis:
        condition: service_healthy
    environment:
      ENABLE_UWSGI: "no"
      ENABLE_NGINX: "no"
      ENABLE_CELERY_BEAT: "yes"
      ENABLE_CELERY_SHORT: "no"
      ENABLE_CELERY_LONG: "no"
      ENABLE_MIGRATIONS: "no"
    volumes:
      - mediacms-data:/home/mediacms.io/mediacms

  celery_worker:
    image: mediacms/mediacms:7.6.0
    restart: unless-stopped
    depends_on:
      migrations:
        condition: service_completed_successfully
    environment:
      ENABLE_UWSGI: "no"
      ENABLE_NGINX: "no"
      ENABLE_CELERY_BEAT: "no"
      ENABLE_CELERY_SHORT: "yes"
      ENABLE_CELERY_LONG: "yes"
      ENABLE_MIGRATIONS: "no"
    volumes:
      - mediacms-data:/home/mediacms.io/mediacms

  db:
    image: postgres:17.2-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: mediacms
      POSTGRES_PASSWORD: changeme_strong_password
      POSTGRES_DB: mediacms
    volumes:
      - mediacms-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mediacms"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - mediacms-redis:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mediacms-data:
  mediacms-db:
  mediacms-redis:

Start the stack:

docker compose up -d

Wait 1-2 minutes for the migrations service to complete database initialization, then access the web interface.

Initial Setup

  1. Access the web interface at http://your-server-ip.
  2. Log in as admin with the default credentials: admin / admin. Change this password immediately.
  3. Configure the site name in the Django admin panel at /admin/.
  4. Set the FRONTEND_HOST to your actual domain or IP in the configuration file (see Configuration below).

Configuration

MediaCMS uses Django settings stored in the container. To customize, create a local_settings.py override:

# Copy the default settings template
docker compose exec web cat /home/mediacms.io/mediacms/deploy/docker/local_settings.py > local_settings.py

Key settings to modify:

# Site URL — set to your actual domain
FRONTEND_HOST = "https://media.example.com"

# Site name displayed in the UI
PORTAL_NAME = "My Media Platform"

# Who can upload media: 'all', 'verified_email', or 'admins'
CAN_ADD_MEDIA = "verified_email"

# Maximum uploads per user
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 100

# Maximum file size in MB
UPLOAD_MAX_SIZE = 4000

# Disable transcoding to save CPU (videos play in original format only)
# DO_NOT_TRANSCODE_VIDEO = True

# Django secret key — generate a unique one for production
SECRET_KEY = "your-random-secret-key-here"

# Debug mode — always False in production
DEBUG = False

Mount the custom settings file by adding to the volumes of web, celery_beat, and celery_worker:

volumes:
  - mediacms-data:/home/mediacms.io/mediacms
  - ./local_settings.py:/home/mediacms.io/mediacms/deploy/docker/local_settings.py:ro

Advanced Configuration (Optional)

Whisper Subtitle Generation

Use the -full image variant for automatic speech-to-text subtitles:

web:
  image: mediacms/mediacms:7.6.0-full  # 4.7 GB — includes Whisper

This adds OpenAI Whisper for automatic subtitle generation on uploaded videos. The full image is 4.7 GB (vs 509 MB standard) and requires additional RAM (2+ GB extra) and CPU.

Email Configuration

For user registration verification emails, add SMTP settings to local_settings.py:

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "[email protected]"
EMAIL_HOST_PASSWORD = "your-password"
DEFAULT_FROM_EMAIL = "[email protected]"

Disable Transcoding

If your server lacks CPU power for transcoding, disable it:

DO_NOT_TRANSCODE_VIDEO = True

Videos will play in their original format. This saves significant CPU but means no adaptive streaming (HLS) and no multi-resolution playback.

Reverse Proxy

The web service includes Nginx internally on port 80. To add HTTPS, place a reverse proxy in front of it.

For Nginx Proxy Manager, create a proxy host pointing to port 80 with SSL enabled.

Important: If your reverse proxy terminates SSL, ensure the FRONTEND_HOST setting uses https:// to prevent mixed content issues.

See Reverse Proxy Setup for detailed instructions.

Backup

Back up three things:

  1. Media files: The mediacms-data volume contains all uploaded media, transcoded versions, and thumbnails.
  2. Database: Contains user accounts, metadata, comments, and settings.
  3. Configuration: Your local_settings.py file.
# Database backup
docker compose exec db pg_dump -U mediacms mediacms > mediacms-db-backup.sql

# Media files are in the named volume — back up with:
docker run --rm -v mediacms-data:/data -v $(pwd):/backup alpine tar czf /backup/mediacms-data.tar.gz -C /data .

See Backup Strategy for automated approaches.

Troubleshooting

Videos Stuck in “Processing”

Symptom: Uploaded videos show “Processing” indefinitely. Fix: Check the Celery worker: docker compose logs celery_worker. If it’s not running or shows errors, the transcoding pipeline is broken. Ensure ENABLE_CELERY_LONG: "yes" is set. Restart: docker compose restart celery_worker

Upload Fails for Large Files

Symptom: Uploads fail for files over a certain size. Fix: The internal Nginx limits uploads to 5.8 GB by default. Check that UPLOAD_MAX_SIZE in Django settings matches or is lower than the Nginx client_max_body_size. If using an external reverse proxy, set its upload limit to match.

Admin Panel Returns 500 Error

Symptom: /admin/ returns a server error. Fix: Check migrations completed: docker compose logs migrations. If the migrations service failed, the database schema is incomplete. Remove the database volume and restart: docker compose down -v && docker compose up -d

ImageMagick Sprite Generation Errors

Symptom: Thumbnail sprite generation fails with “width or height exceeds limit” in celery_worker logs. Fix: This is an ImageMagick security policy limitation for very long videos. The thumbnails still generate — sprite previews for the timeline scrubber may be missing for videos over 4 hours.

Default Admin Password Not Working

Symptom: Can’t log in with admin/admin. Fix: The default admin is created during the migrations step. If you reset the database, the migrations service needs to run again. Check: docker compose logs migrations | grep -i admin

Resource Requirements

  • RAM: 1 GB idle (web + Celery + PostgreSQL + Redis), 2-4 GB during video transcoding
  • CPU: 2 cores minimum, 4+ cores recommended for concurrent transcoding
  • Disk: 500 MB for application, plus 3x storage for each GB of uploaded video (original + transcoded + HLS segments)

Verdict

MediaCMS is the best self-hosted YouTube-like platform. It handles the full video pipeline — upload, transcode, stream, manage — in a single Docker Compose stack. The multi-user support, automatic transcoding, and HLS streaming make it suitable for organizations that need private video hosting.

The resource requirements are real — video transcoding is CPU-intensive and storage multiplies fast. For personal video hosting with lighter requirements, consider Jellyfin (which plays files as-is without transcoding overhead). For YouTube content archival rather than hosting, look at Tube Archivist.

FAQ

How does MediaCMS compare to Jellyfin for video hosting?

Different use cases. MediaCMS is a video hosting platform (upload, transcode, share publicly) — like a private YouTube. Jellyfin is a media library (organize and stream existing files) — like a private Netflix. MediaCMS transcodes uploads into multiple resolutions with HLS streaming; Jellyfin plays files as-is or transcodes on demand. Choose MediaCMS for user-uploaded content; choose Jellyfin for a personal media library.

Can MediaCMS handle live streaming?

No. MediaCMS handles video-on-demand (upload, transcode, stream). For live streaming, use MediaMTX (RTSP/RTMP/HLS proxy) or a dedicated streaming platform. You can record live streams with MediaMTX and upload the recordings to MediaCMS for on-demand viewing.

How much disk space do transcoded videos use?

Roughly 3x the original file size. A 1 GB upload generates ~3 GB total: the original file, transcoded versions at multiple resolutions, HLS segments, and thumbnails. Plan your storage accordingly — a 500 GB disk holds approximately 150 GB of original uploads.

Does MediaCMS support user registration and permissions?

Yes. MediaCMS supports multi-user accounts with configurable permissions: admin, manager, editor, and viewer roles. You can allow public registration, require admin approval, or restrict to invite-only. Each user gets their own upload space and channel.

Can I disable video transcoding?

Yes. Set DO_NOT_TRANSCODE_VIDEO = True in local_settings.py. Videos play in their original format, saving CPU. The downside is no adaptive streaming (HLS) and no multi-resolution playback — viewers need enough bandwidth for the original quality.

What’s the difference between the standard and full Docker images?

The standard image (~509 MB) handles all core functionality. The -full image (~4.7 GB) adds OpenAI Whisper for automatic subtitle generation from speech. Use the standard image unless you need automatic subtitles — the full image requires significantly more RAM and disk space.

Comments