Self-host do Castopod pra publicar um podcast
Siga isso no seu próprio servidor. Comece grátis, sem cartão
A maioria dos hosts de podcast alugam um feed RSS pra você: pare de pagar e os seus episódios, as suas análises e a sua própria URL de feed podem todos desaparecer junto com a conta. O Castopod é um host de podcast open-source, construído sobre o CodeIgniter, que te dá o mesmo workflow de publicação, análises reais de ouvintes, e funcionalidades do Podcasting 2.0 como transcrições e capítulos, com o feed morando no seu próprio domínio em vez do de um fornecedor. No final deste guia você vai ter ele rodando com MariaDB e Redis atrás de HTTPS, o seu primeiro programa publicado, e um backup que de fato cobre os arquivos de áudio, não só o banco de dados.
O que você vai precisar
- Um servidor com Docker e o plugin Compose. Um Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe, €5,99/mês sem IVA) roda o Castopod confortavelmente; planeje disco real além dos 40 GB padrão quando você estiver hospedando mais do que um punhado de episódios, arquivos de áudio se acumulam mais rápido do que a maioria das aplicações self-hosted.
- Um nome de domínio pro qual você pode apontar um registro A. O Castopod exige HTTPS pra federar corretamente com o fediverso (seguidores e comentários de podcast), então isso não é opcional do jeito que poderia ser pra uma ferramenta interna.
- Acesso SSH ao servidor.
Passo 1: aponte o DNS e abra o firewall
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable
Crie um registro A pro seu domínio apontando pro IP público do servidor, e dê alguns minutos antes do Passo 5.
Passo 2: gere os secrets
openssl rand -hex 24 # MYSQL_ROOT_PASSWORD
openssl rand -hex 24 # MYSQL_PASSWORD
openssl rand -hex 24 # REDIS_PASSWORD
openssl rand -hex 16 # CP_ANALYTICS_SALT
mkdir -p /opt/castopod
cat > /opt/castopod/.env <<'EOF'
MYSQL_ROOT_PASSWORD=paste-the-first-value-here
MYSQL_PASSWORD=paste-the-second-value-here
REDIS_PASSWORD=paste-the-third-value-here
CP_ANALYTICS_SALT=paste-the-fourth-value-here
EOF
chmod 600 /opt/castopod/.env
CP_ANALYTICS_SALT é o que o Castopod usa pra fazer hash dos endereços IP dos ouvintes antes de contar um download, te dando análises agregadas reais sem armazenar IPs crus; isso precisa manter o mesmo valor pela vida da instância, mudar ele reseta como ouvintes recorrentes são deduplicados.
Passo 3: escreva o arquivo Compose
services:
castopod:
image: castopod/castopod:1.15.5
restart: unless-stopped
env_file: .env
security_opt:
- no-new-privileges:false
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
environment:
CP_BASEURL: https://podcast.yourdomain.com
CP_DATABASE_HOSTNAME: db
CP_DATABASE_NAME: castopod
CP_DATABASE_USERNAME: castopod
CP_DATABASE_PASSWORD: ${MYSQL_PASSWORD}
CP_REDIS_HOST: redis
CP_REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- castopod_media:/app/public/media
expose:
- "8080"
db:
image: mariadb:12.1
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: castopod
MYSQL_USER: castopod
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- castopod_db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7.2.5-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- castopod_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:
- castopod
volumes:
castopod_media:
castopod_db_data:
castopod_redis_data:
caddy_data:
caddy_config:
Este guia fixa 1.15.5, o release estável atual no momento em que isso foi escrito, e mariadb:12.1, o motor de banco de dados que a própria documentação do Castopod documenta em vez do Postgres. security_opt: no-new-privileges:false é obrigatório, não opcional: a imagem do Castopod roda como um usuário não-root www-data e precisa fazer chown em /run na própria inicialização dela, que a proteção padrão no-new-privileges do kernel bloqueia, fazendo o container falhar fatalmente com s6-overlay-suexec: child failed with exit code 100 se deixado ativo. O próprio script de entrypoint do Castopod, não um arquivo de configuração que você escreve, gera o .env real dele internamente a partir das variáveis CP_* que este guia define, em toda inicialização.
Cheque se o arquivo é interpretado:
docker compose config
Passo 4: adicione o Caddy e inicie a stack
podcast.yourdomain.com {
reverse_proxy castopod:8080
}
docker compose up -d
docker compose ps
curl -I https://podcast.yourdomain.com
Você deve ver todo serviço Up (db como healthy) e o curl retornar HTTP/2 200.
Passo 5: crie a sua conta e o primeiro podcast
Visite https://podcast.yourdomain.com/cp-install e siga o assistente de configuração pra criar a primeira conta de administrador, nada vem pré-populado. No painel de admin, crie um podcast, preencha os metadados dele, e publique o seu primeiro episódio enviando um arquivo de áudio; o Castopod transcodifica e gera a entrada RSS automaticamente. O seu feed é publicado em https://podcast.yourdomain.com/@<your-podcast-handle>/feed.xml, a URL pra enviar pro Apple Podcasts, Spotify e qualquer outro diretório.
Passo 6: faça backup do banco de dados e do volume de mídia
Arquivos de áudio e um banco de dados MariaDB crescendo os dois importam aqui, e o volume de mídia geralmente é o maior:
cat > /opt/castopod/backup.sh <<'EOF'
#!/bin/sh
cd /opt/castopod
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
docker compose exec -T db mariadb-dump -u root -p"${MYSQL_ROOT_PASSWORD}" castopod | gzip > backups/db-$TIMESTAMP.sql.gz
find backups -mtime +14 -delete
EOF
mkdir -p /opt/castopod/backups
chmod +x /opt/castopod/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/castopod/backup.sh") | crontab -
Rode manualmente uma vez e cheque se ls -lh backups mostra um arquivo com data. O dump do banco de dados por si só não cobre o áudio enviado em castopod_media; faça backup disso com qualquer ferramenta de snapshot de disco completo que o seu provedor de servidor oferece, ou um rsync periódico pra outra máquina, já que um tar de mídia sendo servida ativamente enquanto episódios estão no ar é o tipo de coisa que vale agendar pra horários de baixo tráfego.
Passo 7: faça o upgrade
docker compose pull castopod
docker compose up -d castopod
O Castopod roda as próprias migrations de banco de dados automaticamente na inicialização quando a versão da imagem muda. Cheque as notas de release da versão pra qual você está indo, e confirme que docker compose logs castopod mostra uma inicialização limpa sem erros de migration antes de considerar o upgrade concluído.
O caminho mais rápido: faça isso com a Railyard
Tudo acima, MariaDB e Redis conectados automaticamente, a correção de no-new-privileges que a própria imagem do Castopod precisa pra subir corretamente, um volume dedicado pro áudio enviado, também é o que o template de stack Castopod da Railyard configura pra você. Conecte um servidor, depois escolha Castopod no catálogo de templates: app.railyard.run/deploy?stack=castopod. O HTTPS é tratado do mesmo jeito que o Passo 4 faz manualmente, que também satisfaz o próprio requisito do Castopod pra federação no fediverso.
Veja deploy with a button e databases and backups pra como templates e dados persistentes funcionam na plataforma de forma mais geral.
Resolução de problemas
O container sai com s6-overlay-suexec: child failed with exit code 100. no-new-privileges ainda está ativado, cheque se security_opt no Passo 3 está de fato definido como no-new-privileges:false especificamente no serviço castopod, esse é o único padrão de hardening que precisa ser relaxado pra essa imagem.
O MariaDB registra “Host X is not allowed to connect” só na primeira inicialização. Isso aparece se a CPU do servidor está pesadamente carregada exatamente quando o MariaDB está criando os usuários e grants iniciais dele, interrompendo esse processo. Pare a stack, remova o volume castopod_db_data, e rode docker compose up -d de novo num momento mais tranquilo; não existe dado real pra perder ainda se isso acontecer numa instalação genuinamente nova.
Áudio enviado falha com um erro de permissão. Cheque a propriedade dentro do container com docker compose exec castopod ls -la /app/public/media; isso aparece mais frequentemente depois de restaurar um backup como root quando o container roda como www-data.
HTTPS não é emitido. docker compose logs caddy mostrando erros de ACME repetidos quase sempre significa que o DNS do Passo 1 ainda não se propagou ou aponta pro IP errado.
O disco enchendo mais rápido do que esperado. Arquivos de áudio se acumulam rápido entre episódios; du -sh castopod_media (ou o caminho equivalente de docker volume inspect) mostra o que de fato está consumindo espaço, e redimensionar o disco do servidor antes de acabar é mais simples do que migrar um volume completo sob pressão.
Perguntas frequentes
O Castopod exige um provedor de SMTP como algumas outras aplicações self-hosted exigem? Não pra publicar um podcast ou pra moderação central funcionar; o SMTP só é necessário se você ativar notificações de email opcionais, que é um requisito muito mais leve do que aplicações onde a própria confirmação de conta depende disso.
Eu consigo migrar o feed RSS de um podcast existente pro Castopod? Sim, o Castopod suporta importar episódios de uma URL de feed existente durante a criação do podcast, que preserva o histórico de episódios em vez de começar do zero.
A integração com o fediverso é obrigatória? Não, podcasts funcionam como um feed RSS e player web normal sem ela; a federação (seguidores e comentários de apps estilo Mastodon) é uma funcionalidade adicional em cima, não um requisito pra publicar.
Como funciona análise real sem rastrear ouvintes individualmente? O Castopod implementa a especificação de analytics do Podcasting 2.0, fazendo hash de dados identificadores como endereços IP com o salt do Passo 2 antes de contar um download, então você tem números reais de download e geográficos sem armazenar nada que identifique um ouvinte individual.
Qual é o crescimento realista de disco pra um programa ativo? Um episódio semanal de uma hora num bitrate razoável é dezenas de megabytes por episódio; isso se acumula lentamente pra maioria dos programas, mas vídeo ou áudio de bitrate muito alto muda essa conta rápido, cheque os tamanhos reais de arquivo contra o servidor de “O que você vai precisar” antes de assumir que os 40 GB padrão são suficientes pra um ano.
Depois que MariaDB, Redis e HTTPS estão no lugar, publicar um episódio é os mesmos poucos cliques que seria numa plataforma hospedada, o trabalho contínuo real é o hábito de backup do Passo 6 e ficar de olho no espaço em disco conforme o programa cresce.
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.