← Blog

Hospede o MediaCMS para hospedagem de vídeo

Railyard

16 de setembro de 2026 · 8 min de leitura

Siga isso no seu próprio servidor. Comece grátis, sem cartão

Rodar seu próprio portal de vídeo geralmente significa escolher entre um wrapper fino em volta da conta de armazenamento de outra pessoa e um CMS de mídia de verdade que cuida de uploads, transcodificação e reprodução por conta própria. O MediaCMS é o segundo tipo: um app Django open-source que recebe vídeo, áudio e imagens, transcodifica para múltiplas resoluções com workers Celery rodando em segundo plano, e serve um portal com marca própria, busca e playlists. No final deste guia, você vai ter o app web, um worker de transcodificação separado, Postgres e Redis rodando juntos atrás de HTTPS, com backups e um upload real testado de ponta a ponta.

O que você vai precisar

  • Um servidor com Docker e o plugin Compose. Um Hetzner CX33 (4 vCPUs, 8 GB de RAM, 80 GB NVMe, €8,99/mês sem VAT) é o piso realista quando a transcodificação entra em cena; um CX23 tecnicamente roda o app web, mas uploads simultâneos competem com o servidor web uWSGI pela mesma CPU limitada, que um processo de worker dedicado precisa de espaço para usar.
  • Um domínio no qual você possa apontar um registro A.
  • Planejamento real de disco: o MediaCMS guarda o upload original mais cada resolução transcodificada mais os segmentos HLS para streaming, uma regra de bolso do próprio projeto é três vezes o tamanho do que você de fato envia, ao longo de um ano de uploads na sua taxa esperada.
  • Acesso SSH ao servidor.

Etapa 1: Aponte o DNS e abra o firewall

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

Crie um registro A para o seu domínio apontando para o IP público do servidor, e dê alguns minutos antes da Etapa 5.

Etapa 2: Gere os segredos

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 importa mais aqui do que num app Django típico: se ficar sem definir, o próprio local_settings.py do MediaCMS volta para um valor padrão fixo embutido em toda cópia da imagem, o que deixaria qualquer um forjar cookies assinados ou tokens de redefinição de senha contra qualquer deploy que tenha deixado sem definir. Defini-lo explicitamente, e compartilhar o mesmo valor entre os processos web e worker, não é opcional.

Etapa 3: Escreva o arquivo Compose

O MediaCMS de fato precisa de quatro processos que leem o mesmo ambiente: o app web, um worker Celery para transcodificação, um agendador Celery beat para tarefas periódicas, e o banco de dados e cache que eles compartilham.

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:

Este guia fixa a 7.2.0, a release estável atual no momento em que isso foi escrito. O serviço mediacms roda o prestart.sh, o próprio script de entrada do MediaCMS que aplica migrações e inicia o servidor web em si, e é por isso que ENABLE_MIGRATIONS fica sem definir nele, mas explicitamente "no" em worker e beat: rodar migrações a partir de três processos ao mesmo tempo em cada boot é um jeito real de cair numa condição de corrida. ENABLE_CELERY_SHORT, ENABLE_CELERY_LONG e ENABLE_CELERY_BEAT estão todos "no" no serviço mediacms especificamente para que o container web não tente rodar o Celery também; esses três papéis ficam divididos entre worker e beat, cada um desligando o papel que não lhe pertence. mediacms_media é um único volume compartilhado entre mediacms e worker, já que o worker lê o que o processo web aceitou e escreve a saída transcodificada que os dois precisam ver.

Confira se o arquivo é interpretado corretamente:

docker compose config

Etapa 4: Adicione o Caddy e inicie a stack

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

Você deve ver todos os serviços Up (db como healthy) e o curl retornando HTTP/2 200.

Etapa 5: Faça login e envie um vídeo de teste

Acesse https://videos.yourdomain.com e faça login com admin e o ADMIN_PASSWORD da Etapa 2. Envie um vídeo curto de teste pela UI, depois confira se de fato transcodificou, não só subiu:

docker compose logs -f worker

Você deve ver a saída de tarefas do Celery referenciando o ID da nova mídia, terminando num estado concluído, e o vídeo em si deve ficar reproduzível no navegador um ou dois minutos depois, quando o worker alcançar, mais tempo para um arquivo maior.

Etapa 6: Faça backup do Postgres e do volume de mídia

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 -

O dump do banco de dados cobre metadados, contas e playlists, não os arquivos de vídeo em si no mediacms_media, que é quase certamente a parte maior e mais valiosa desta instalação. Faça backup desse volume com um snapshot de disco completo do seu provedor de servidor, ou um rsync agendado para outra máquina; rodar um tar sobre a mídia ativa enquanto uploads estão em andamento arrisca um arquivo inconsistente, então agende para um período tranquilo, da mesma forma que a Etapa 6 em outros guias trata um banco de dados ativo.

Etapa 7: Faça upgrade

docker compose pull
docker compose up -d

O prestart.sh do serviço mediacms reaplica migrações automaticamente no restart; worker e beat também recebem a nova imagem já que a compartilham, mas pulam migrações de propósito, como definido na Etapa 3. Confira docker compose logs mediacms em busca de uma inicialização limpa antes de considerar o upgrade concluído.

O caminho curto: faça com o Railyard

Tudo isso acima, Postgres e Redis ligados aos três processos, os papéis de worker e beat divididos corretamente para não rodarem o Celery em dobro nem disputarem migrações, um volume de mídia compartilhado, é também o que o template de stack do MediaCMS no Railyard configura para você. Conecte um servidor e escolha MediaCMS no catálogo de templates: app.railyard.run/deploy?stack=mediacms. SECRET_KEY e ADMIN_PASSWORD são gerados para você em vez de deixados como placeholder, a senha de admin gerada aparece no painel do app em vez de só ser impressa uma vez num log, do jeito que o próprio prestart.sh do MediaCMS faz quando deixado sem definir.

Veja deploy with a button e databases and backups para entender como templates e dados persistentes funcionam na plataforma de forma mais geral.

Solução de problemas

Os uploads têm sucesso, mas os vídeos nunca terminam de processar. Confira docker compose logs worker primeiro; um worker em crash-loop ou morto por falta de memória aparece como o container reiniciando em vez de um erro claro no log, caso em que a correção é mais memória ou CPU para a máquina, não uma mudança de configuração.

O login de admin rejeita uma senha que você tem certeza que está correta. Confirme que ADMIN_PASSWORD no .env corresponde exatamente ao que você está digitando; se a stack já estava rodando antes de você definir, a conta foi criada com o valor presente no primeiro boot, mude pelo admin do Django em vez de editar o .env depois do fato.

As migrações parecem rodar duas vezes ou conflitar na inicialização. Confirme que ENABLE_MIGRATIONS: "no" está de fato presente em worker e beat no seu arquivo compose; se estiver faltando em algum dos dois, você vai ver erros intermitentes de migração que parecem não relacionados à causa real.

O HTTPS não é emitido. docker compose logs caddy mostrando erros repetidos de ACME quase sempre significa que o DNS da Etapa 1 não propagou ainda ou aponta para o IP errado.

O disco enchendo muito mais rápido do que esperado. O MediaCMS guarda o upload original, as versões transcodificadas e os segmentos HLS lado a lado; du -sh contra o volume de mídia mostra o uso real, e a regra de bolso de três vezes o tamanho do upload em “O que você vai precisar” vale revisitar contra sua própria taxa real de upload, em vez de assumida uma vez e esquecida.

Perguntas frequentes

Posso desabilitar a transcodificação e só servir o arquivo original? Não de forma significativa, streaming adaptativo (servir a resolução certa para a conexão de quem está vendo) é um recurso central, não um extra; se você só precisa hospedar um punhado de arquivos já pré-codificados sem transcodificação, um host de arquivo estático simples é uma escolha melhor do que o MediaCMS.

O MediaCMS precisa de um provedor de SMTP? Só para recursos opcionais como notificações por e-mail em comentários; a criação de conta e a hospedagem de vídeo principal funcionam sem ele, menos rígido do que apps que exigem SMTP para confirmar uma conta nova.

O worker pode rodar num servidor separado e mais barato do que o processo web? Sim, desde que consiga acessar o mesmo Postgres, Redis e armazenamento de mídia compartilhado, separar o worker na própria máquina é um próximo passo razoável depois que a carga de transcodificação de fato competir com o tráfego web por CPU.

Que formatos de vídeo ele aceita? O MediaCMS aceita formatos comuns como MP4, MOV, MKV, AVI, WebM e mais no upload, e os transcodifica para uma saída padrão amigável à web independente do formato de origem.

Existe um limite de tamanho nos uploads? O padrão do próprio MediaCMS é generoso, mas configurável; o espaço de disco de um servidor pequeno é o limite mais prático numa configuração self-hosted do que qualquer coisa que o MediaCMS imponha por padrão.

Depois que Postgres, Redis e o worker estiverem todos ligados aos mesmos segredos, enviar e transcodificar um vídeo são os mesmos poucos cliques que seriam numa plataforma hospedada. O trabalho contínuo real é o hábito de backup da Etapa 6 e manter o crescimento do disco na frente do que o servidor de fato tem.

Por que escolhem o Railyard para isso

  • Sua própria conta de cloud. Faça deploy no DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou em qualquer servidor Linux via SSH, cobrado direto pelo seu provedor, sem markup.
  • Um preço fixo por equipe. O Free é grátis para sempre: um servidor mais um servidor de banco de dados, três apps, sem cartão. O Pro custa US$19 por mês para até cinco servidores e apps ilimitados. O Team custa US$49 por mês com servidores ilimitados. Sem cobrança por app, por deploy, por assento ou por gigabyte.
  • Templates de um clique. Faça deploy de apps open source, como bancos de dados, analytics e ferramentas de dev, no seu próprio servidor em um clique.
  • Bancos de dados gerenciados. Postgres, MySQL e Redis com backups programados, verificados por restauração, e recuperação point-in-time.
  • Push-to-deploy. Faça deploy a partir do GitHub, GitLab ou da CLI, com HTTPS automático e releases sem downtime.

Suba o MediaCMS com um clique

Escolha MediaCMS no catálogo de templates do Railyard e já tenha Postgres, Redis e o worker de transcodificação ligados.

Plano grátis, sem cartão, e 14 dias de Pro para cada equipe nova. Um preço fixo por equipe.