← Blog

MediaCMS für Video-Hosting selbst hosten

Railyard

16. September 2026 · 7 Min. Lesezeit

Folge dem auf deinem eigenen Server. Kostenlos starten, keine Karte nötig

Ein eigenes Video-Portal zu betreiben bedeutet meist, zwischen einem dünnen Wrapper um die Speicherkosten eines anderen Anbieters und einem echten Media-CMS zu wählen, das Uploads, Transcoding und Wiedergabe selbst übernimmt. MediaCMS ist die zweite Sorte: eine Open-Source-Django-App, die Video, Audio und Bilder entgegennimmt, sie mit Celery-Workern im Hintergrund in mehrere Auflösungen transcodiert, und ein gebrandetes Portal mit eigener Suche und Wiedergabelisten ausliefert. Am Ende dieser Anleitung laufen die Web-App, ein separater Transcoding-Worker, Postgres und Redis zusammen hinter HTTPS, mit Backups und einem echten, Ende-zu-Ende getesteten Upload.

Was du brauchst

  • Einen Server mit Docker und dem Compose-Plugin. Ein Hetzner CX33 (4 vCPU, 8 GB RAM, 80 GB NVMe, 8,99 €/Monat exkl. MwSt.) ist die realistische Untergrenze, sobald Transcoding dabei ist; ein CX23 betreibt die Web-App technisch, aber gleichzeitige Uploads konkurrieren mit dem uWSGI-Webserver um dieselbe begrenzte CPU, die ein dedizierter Worker-Prozess zum Arbeiten braucht.
  • Einen Domainnamen, auf den du einen A-Record zeigen lassen kannst.
  • Echte Festplatten-Planung: MediaCMS behält das Original-Upload plus jede transcodierte Auflösung plus HLS-Segmente für Streaming, eine grobe Faustregel des Projekts selbst ist das Dreifache der Größe dessen, was du tatsächlich hochlädst, über ein Jahr an Uploads bei deiner erwarteten Rate.
  • SSH-Zugang zum Server.

Schritt 1: DNS setzen und die Firewall öffnen

ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable

Lege einen A-Record für deine Domain an, der auf die öffentliche IP des Servers zeigt, und gib ihm ein paar Minuten vor Schritt 5.

Schritt 2: Die Secrets generieren

openssl rand -hex 24   # POSTGRES_PASSWORD
openssl rand -hex 24   # ADMIN_PASSWORD
openssl rand -hex 32   # SECRET_KEY
mkdir -p /opt/mediacms
cat > /opt/mediacms/.env <<'EOF'
POSTGRES_PASSWORD=paste-the-first-value-here
ADMIN_PASSWORD=paste-the-second-value-here
SECRET_KEY=paste-the-third-value-here
EOF
chmod 600 /opt/mediacms/.env

SECRET_KEY ist hier wichtiger als in einer typischen Django-App: Bleibt er ungesetzt, fällt MediaCMS’ eigenes local_settings.py auf einen hartcodierten Standardwert zurück, der in jeder Kopie des Images steckt, was jedem erlauben würde, signierte Cookies oder Passwort-Reset-Tokens gegen jedes Deployment zu fälschen, das ihn ungesetzt gelassen hat. Ihn explizit zu setzen und denselben Wert über Web- und Worker-Prozesse zu teilen, ist nicht optional.

Schritt 3: Die Compose-Datei schreiben

MediaCMS braucht tatsächlich vier Prozesse, die alle dasselbe Environment lesen: die Web-App, einen Celery-Worker für Transcoding, einen Celery-Beat-Scheduler für periodische Aufgaben, und die Datenbank und den Cache, die sie sich teilen.

services:
  mediacms:
    image: mediacms/mediacms:7.2.0
    restart: unless-stopped
    env_file: .env
    command: ./deploy/docker/prestart.sh
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      FRONTEND_HOST: https://videos.yourdomain.com
      REDIS_LOCATION: redis://redis:6379/1
      POSTGRES_HOST: db
      POSTGRES_NAME: mediacms
      POSTGRES_USER: mediacms
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      ENABLE_CELERY_SHORT: "no"
      ENABLE_CELERY_LONG: "no"
      ENABLE_CELERY_BEAT: "no"
      ADMIN_USER: admin
      ADMIN_EMAIL: admin@yourdomain.com
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      SECRET_KEY: ${SECRET_KEY}
    volumes:
      - mediacms_media:/home/mediacms.io/mediacms/media_files
    expose:
      - "80"

  worker:
    image: mediacms/mediacms:7.2.0
    restart: unless-stopped
    env_file: .env
    depends_on:
      - mediacms
    environment:
      REDIS_LOCATION: redis://redis:6379/1
      POSTGRES_HOST: db
      POSTGRES_NAME: mediacms
      POSTGRES_USER: mediacms
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      SECRET_KEY: ${SECRET_KEY}
      ENABLE_UWSGI: "no"
      ENABLE_NGINX: "no"
      ENABLE_CELERY_BEAT: "no"
      ENABLE_MIGRATIONS: "no"
    volumes:
      - mediacms_media:/home/mediacms.io/mediacms/media_files

  beat:
    image: mediacms/mediacms:7.2.0
    restart: unless-stopped
    env_file: .env
    depends_on:
      - mediacms
    environment:
      REDIS_LOCATION: redis://redis:6379/1
      POSTGRES_HOST: db
      POSTGRES_NAME: mediacms
      POSTGRES_USER: mediacms
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      SECRET_KEY: ${SECRET_KEY}
      ENABLE_UWSGI: "no"
      ENABLE_NGINX: "no"
      ENABLE_CELERY_SHORT: "no"
      ENABLE_CELERY_LONG: "no"
      ENABLE_MIGRATIONS: "no"

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

  redis:
    image: redis:7.2.5-alpine
    restart: unless-stopped
    volumes:
      - mediacms_redis_data:/data

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - mediacms

volumes:
  mediacms_media:
  mediacms_db_data:
  mediacms_redis_data:
  caddy_data:
  caddy_config:

Diese Anleitung pinnt 7.2.0, das aktuelle stabile Release zum Zeitpunkt der Erstellung. Der mediacms-Dienst führt prestart.sh aus, MediaCMS’ eigenes Entrypoint-Skript, das Migrationen anwendet und den eigentlichen Webserver startet, weshalb ENABLE_MIGRATIONS dort ungesetzt bleibt, aber bei worker und beat explizit "no" ist; Migrationen von drei Prozessen gleichzeitig bei jedem Boot auszuführen ist ein echter Weg, in eine Race Condition zu laufen. ENABLE_CELERY_SHORT, ENABLE_CELERY_LONG und ENABLE_CELERY_BEAT sind beim mediacms-Dienst speziell alle "no", damit der Web-Container nicht auch selbst versucht, Celery auszuführen; diese drei Rollen sind stattdessen zwischen worker und beat aufgeteilt, wobei jeder die Rolle deaktiviert, die er nicht besitzt. mediacms_media ist ein einziges Volume, das zwischen mediacms und worker geteilt wird, da der Worker liest, was der Web-Prozess angenommen hat, und die transcodierte Ausgabe schreibt, die beide sehen müssen.

Prüfen, ob die Datei korrekt parst:

docker compose config

Schritt 4: Caddy hinzufügen und den Stack starten

videos.yourdomain.com {
	reverse_proxy mediacms:80
}
docker compose up -d
docker compose ps
curl -I https://videos.yourdomain.com

Du solltest jeden Dienst als Up sehen (db als healthy) und curl sollte HTTP/2 200 zurückgeben.

Schritt 5: Einloggen und ein Test-Video hochladen

Besuche https://videos.yourdomain.com und melde dich mit admin und dem ADMIN_PASSWORD aus Schritt 2 an. Lade über die UI ein kurzes Test-Video hoch und prüfe dann, dass es tatsächlich transcodiert wurde, nicht nur hochgeladen:

docker compose logs -f worker

Du solltest Celery-Task-Ausgaben sehen, die auf die ID des neuen Medieneintrags verweisen und in einem abgeschlossenen Zustand enden, und das Video selbst sollte eine Minute oder zwei später im Browser abspielbar sein, sobald der Worker nachgezogen hat, länger bei einer größeren Datei.

Schritt 6: Postgres und das Medien-Volume sichern

cat > /opt/mediacms/backup.sh <<'EOF'
#!/bin/sh
cd /opt/mediacms
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
docker compose exec -T db pg_dump -U mediacms mediacms | gzip > backups/db-$TIMESTAMP.sql.gz
find backups -mtime +14 -delete
EOF
mkdir -p /opt/mediacms/backups
chmod +x /opt/mediacms/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/mediacms/backup.sh") | crontab -

Der Datenbank-Dump deckt Metadaten, Konten und Wiedergabelisten ab, nicht die eigentlichen Videodateien auf mediacms_media, was mit ziemlicher Sicherheit der größere und wertvollere Teil dieser Installation ist. Sichere dieses Volume mit einem Full-Disk-Snapshot deines Server-Anbieters, oder einem geplanten rsync auf eine andere Maschine; ein tar über laufende Medien zu fahren, während Uploads im Gange sind, riskiert ein inkonsistentes Archiv, plane es also für eine ruhige Zeit, genauso wie Schritt 6 in anderen Anleitungen eine aktive Datenbank behandelt.

Schritt 7: Upgrade

docker compose pull
docker compose up -d

Das prestart.sh des mediacms-Dienstes wendet Migrationen beim Neustart automatisch erneut an; worker und beat übernehmen das neue Image ebenfalls, da sie es sich teilen, überspringen Migrationen aber absichtlich selbst, wie in Schritt 3 gesetzt. Prüfe docker compose logs mediacms auf einen sauberen Start, bevor du das Upgrade als abgeschlossen betrachtest.

Die Abkürzung: mit Railyard

Alles oben, Postgres und Redis an alle drei Prozesse verdrahtet, die Worker- und Beat-Rollen korrekt aufgeteilt, damit sie Celery nicht doppelt ausführen oder bei Migrationen kollidieren, ein geteiltes Medien-Volume, ist auch das, was Railyards MediaCMS-Stack-Template für dich einrichtet. Verbinde einen Server und wähle dann MediaCMS im Template-Katalog: app.railyard.run/deploy?stack=mediacms. SECRET_KEY und ADMIN_PASSWORD werden für dich generiert statt als Platzhalter gelassen, das generierte Admin-Passwort erscheint im Dashboard der App, statt nur einmal in ein Log gedruckt zu werden, so wie es MediaCMS’ eigenes prestart.sh tut, wenn es ungesetzt bleibt.

Siehe mit einem Button deployen und Datenbanken und Backups für mehr darüber, wie Templates und persistente Daten auf der Plattform allgemein funktionieren.

Troubleshooting

Uploads gelingen, aber Videos werden nie fertig verarbeitet. Prüfe zuerst docker compose logs worker; ein Worker, der in einer Crash-Loop ist oder OOM-gekillt wird, zeigt sich als neustartender Container statt als klarer Fehler im Log, in diesem Fall ist die Lösung mehr Speicher oder CPU für die Box, keine Konfigurationsänderung.

Der Admin-Login lehnt ein Passwort ab, von dem du sicher bist, dass es korrekt ist. Bestätige, dass ADMIN_PASSWORD in der .env exakt dem entspricht, was du eingibst; wenn der Stack schon lief, bevor du es gesetzt hast, wurde das Konto mit dem Wert erstellt, der beim ersten Start vorhanden war, ändere es über das Django-Admin statt die .env nachträglich erneut zu bearbeiten.

Migrationen scheinen doppelt zu laufen oder beim Start zu kollidieren. Bestätige, dass ENABLE_MIGRATIONS: "no" tatsächlich bei sowohl worker als auch beat in deiner Compose-Datei vorhanden ist; fehlt es bei einem der beiden, siehst du sporadische Migrationsfehler, die unabhängig von der tatsächlichen Ursache wirken.

HTTPS wird nicht ausgestellt. Wenn docker compose logs caddy wiederholte ACME-Fehler zeigt, bedeutet das fast immer, dass DNS aus Schritt 1 noch nicht propagiert ist oder auf die falsche IP zeigt.

Festplatte läuft viel schneller voll als erwartet. MediaCMS hält das Original-Upload, transcodierte Varianten und HLS-Segmente nebeneinander; du -sh gegen das Medien-Volume zeigt die tatsächliche Nutzung, und die Dreifach-Faustregel aus „Was du brauchst” lohnt sich, gegen deine eigene tatsächliche Upload-Rate zu überprüfen, statt einmal angenommen und vergessen zu werden.

FAQ

Kann ich Transcoding deaktivieren und nur die Originaldatei ausliefern? Nicht sinnvoll, adaptives Streaming (die richtige Auflösung für die Verbindung des Betrachters ausliefern) ist ein Kernfeature, kein Add-on; wenn du nur eine Handvoll vorab encodierter Dateien ohne Transcoding hosten musst, ist ein reiner statischer Datei-Host die bessere Wahl als MediaCMS.

Braucht MediaCMS einen SMTP-Anbieter? Nur für optionale Features wie E-Mail-Benachrichtigungen bei Kommentaren; sowohl Konto-Erstellung als auch das Kern-Video-Hosting funktionieren ohne ihn, weniger strikt als Apps, die SMTP brauchen, um ein neues Konto zu bestätigen.

Kann der Worker auf einem separaten, günstigeren Server als der Web-Prozess laufen? Ja, solange er dasselbe Postgres, Redis und den geteilten Medien-Speicher erreichen kann, ist es ein vernünftiger nächster Schritt, den Worker auf eine eigene Maschine auszulagern, sobald die Transcoding-Last wirklich mit dem Web-Traffic um CPU konkurriert.

Welche Videoformate akzeptiert es? MediaCMS akzeptiert gängige Formate wie MP4, MOV, MKV, AVI, WebM und mehr beim Upload, und transcodiert sie unabhängig vom Quellformat in standardmäßige, webfreundliche Ausgabe.

Gibt es eine Größenbegrenzung für Uploads? MediaCMS’ eigener Standard ist großzügig, aber konfigurierbar; der Festplattenplatz eines kleinen Servers ist in einem selbst gehosteten Setup die praktischere Grenze als alles, was MediaCMS standardmäßig erzwingt.

Sobald Postgres, Redis und der Worker alle an dieselben Secrets verdrahtet sind, ist das Hochladen und Transcodieren eines Videos dieselben paar Klicks, die es auf einer gehosteten Plattform wäre. Die eigentliche laufende Arbeit ist die Backup-Routine aus Schritt 6 und das Festplattenwachstum im Blick zu behalten, bevor der Server tatsächlich an seine Grenzen kommt.

Warum Leute sich dafür Railyard entscheiden

  • Dein eigenes Cloud-Konto. Deploye auf DigitalOcean, Hetzner, Vultr, AWS, OVHcloud oder jedem Linux-Server per SSH, direkt von deinem Anbieter abgerechnet, ohne Aufschlag.
  • Ein fester Preis pro Team. Free ist für immer kostenlos: ein Server plus ein Datenbankserver, drei Apps, keine Karte. Pro kostet 19 $ im Monat für bis zu fünf Server und unbegrenzte Apps. Team kostet 49 $ im Monat mit unbegrenzten Servern. Keine Kosten pro App, pro Deployment, pro Sitz oder pro Gigabyte.
  • Templates mit einem Klick. Deploye Open-Source-Apps, wie Datenbanken, Analytics und Dev-Tools, mit einem Klick auf deinem eigenen Server.
  • Verwaltete Datenbanken. Postgres, MySQL und Redis mit geplanten, wiederherstellungsgeprüften Backups und Point-in-Time-Recovery.
  • Push-to-Deploy. Deploye von GitHub, GitLab oder der CLI, mit automatischem HTTPS und Releases ohne Ausfallzeit.

MediaCMS mit einem Klick deployen

Wähle MediaCMS in Railyards Template-Katalog und bekomme Postgres, Redis und den Transcoding-Worker schon fertig verdrahtet.

Kostenloser Plan, keine Kreditkarte, und 14 Tage Pro für jedes neue Team. Ein fester Preis pro Team.