Docker Gratuito no GitHub para Renderizar Vídeos com Python
Se você roda automações no GitHub Actions e cansa de ver aqueles 30 segundos de apt-get install toda vez que o workflow dispara — esse post é exatamente pra você. A solução é simples: você cria uma imagem Docker com tudo já instalado (FFmpeg, Python 3.11, todas as libs) e o GitHub Actions simplesmente baixa e executa, sem instalar nada.
Testei essa abordagem no meu próprio workflow de renderização de vídeo com render.py, integrado ao Google Sheets, Gemini API e Blogger. O resultado: o job passou de ~3 minutos para menos de 40 segundos. E o melhor — tudo gratuito usando o GitHub Container Registry (ghcr.io).
Por Que Criar Uma Imagem Docker Própria?
Workflow até 5x mais rápido
Sem o apt-get install a cada execução, o job pula direto para o que importa: rodar o seu script. 30 segundos de instalação viram 2 segundos de pull da imagem cached.
Ambiente 100% reproduzível
A mesma versão do FFmpeg, Python e todas as libs, sempre. Acabou aquele bug misterioso que só aparecia "às vezes" porque uma dependência mudou de versão no repositório Ubuntu.
Gratuito com ghcr.io
O GitHub Container Registry é gratuito para repositórios públicos e privados. Sem limite de pulls internos entre seus próprios workflows, sem cartão de crédito.
Secrets protegidos nativamente
O GITHUB_TOKEN dá autenticação automática ao ghcr.io. Seus secrets do OAuth, Gemini e Google Sheets continuam sendo injetados pelo Actions — nunca ficam dentro da imagem.
SSH cliente e servidor
O container já vem com OpenSSH configurado nos dois modos. Você consegue tanto receber conexões externas quanto acessar outras máquinas remotas — útil para debug em tempo real via Ngrok ou similar.
Imagem enxuta e específica
Construída exatamente com o que os seus logs mostram sendo instalado — nada a mais, nada a menos. Menos camadas = pull mais rápido = workflow mais ágil.
Como Funciona o Fluxo Completo
Você cria e publica a imagem Docker uma única vez
Um workflow separado (build-docker.yml) roda apenas quando o Dockerfile muda. Ele constrói a imagem com Python 3.11, FFmpeg completo, todas as libs de codec dos seus logs e todos os pacotes pip — e publica no ghcr.io autenticado pelo GITHUB_TOKEN. A partir daí, a imagem fica disponível para todos os seus workflows.
O GitHub Actions usa a imagem como container de execução
O seu run_notebook.yml aponta para a imagem publicada com container: ghcr.io/rodrigoqbqb/render-engine:latest. O runner faz o pull (que fica em cache entre runs) e executa tudo dentro do container — sem instalar nada, sem apt-get, sem pip install. O código do seu repositório privado é montado via actions/checkout e os secrets são injetados como variáveis de ambiente, exatamente como antes.
Para debug: SSH bidirecional via Ngrok
O container inclui OpenSSH configurado como servidor (porta 22) e cliente. Quando precisar debugar um job em tempo real, um step opcional inicia o Ngrok e expõe a porta SSH — você conecta do seu computador direto no container rodando no runner do GitHub. Nenhuma porta exposta permanentemente, tudo sob demanda.
Para Quem É Este Tutorial
🎬 Criadores com automação de vídeo
Quem usa Python + FFmpeg para renderizar, cortar ou processar vídeos automaticamente e roda isso no GitHub Actions.
🤖 Desenvolvedores de bots e scripts
Quem tem scripts que integram Google Sheets, Gemini API, Blogger e quer um ambiente estável e rápido para executar.
💸 Quem quer CI/CD gratuito
Quem não quer pagar por servidores dedicados e aproveita os minutos gratuitos do GitHub Actions de forma inteligente.
🔧 Quem debugou workflow às 2h da manhã
Quem já perdeu horas tentando entender por que o job falhou — e quer poder entrar no container via SSH e ver o que está acontecendo ao vivo.
Tutorial Passo a Passo
Passo 1 — Estrutura de arquivos no repositório
Crie esta estrutura no seu repositório automacao_renderiza:
# Repositório: rodrigoqbqb/automacao_renderiza automacao_renderiza/ ├── Dockerfile # ← imagem Docker (novo) ├── .github/ │ └── workflows/ │ ├── run_notebook.yml # ← seu workflow atual (atualizado) │ └── build-docker.yml # ← build da imagem (novo) ├── render.py # ← seu script (inalterado) └── requirements.txt # ← dependências pip (novo)
Passo 2 — requirements.txt
Crie o arquivo com exatamente os pacotes que seu script usa:
gspread
google-api-python-client
google-auth
google-auth-oauthlib
google-auth-httplib2
gdown
google-genai
Passo 3 — O Dockerfile (enxuto e completo)
Este é o coração do projeto. Cada decisão aqui foi tomada para minimizar o tamanho da imagem enquanto instala exatamente o que seus logs mostram sendo necessário — nada a mais:
# Base: Python 3.11 slim (Ubuntu 24.04 under the hood) # Versão slim = sem extras desnecessários — reduz ~200MB vs python:3.11 FROM python:3.11-slim-bookworm # Metadados da imagem LABEL maintainer="@CanalQb" LABEL description="Render engine: Python 3.11 + FFmpeg + Google APIs + SSH" # Variáveis de ambiente: Python não cria .pyc e não bufferiza stdout ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ DEBIAN_FRONTEND=noninteractive # Instala FFmpeg + OpenSSH (cliente e servidor) em uma única camada # apt-get clean e rm -rf /var/lib/apt/lists/* reduzem o tamanho final RUN apt-get update -qq && \ apt-get install -y --no-install-recommends \ ffmpeg \ openssh-client \ openssh-server \ curl \ git \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Configura SSH Server # - Cria diretório de run (necessário para sshd) # - Desativa PAM para funcionar em container # - Permite login root com senha (apenas para debug — nunca em produção permanente) RUN mkdir /var/run/sshd && \ echo 'root:canalqb_debug' | chpasswd && \ sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \ sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \ sed -i 's/UsePAM yes/UsePAM no/' /etc/ssh/sshd_config # Instala dependências Python # --no-cache-dir: não armazena cache do pip na imagem = menor tamanho COPY requirements.txt /tmp/requirements.txt RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r /tmp/requirements.txt # Diretório de trabalho padrão WORKDIR /workspace # Expõe porta SSH (apenas para uso manual/debug — o Actions não precisa disso) EXPOSE 22 # CMD padrão: inicia o SSH server # O GitHub Actions vai sobrescrever esse CMD com o comando do step CMD ["/usr/sbin/sshd", "-D"]
slim já instala o Python puro sem ferramentas de compilação extras. O FFmpeg instalado via apt nesta base já inclui todas as codecs que apareceram nos seus logs (libx264, libx265, libvpx, libopus, libmp3lame e todas as outras) — porque o pacote ffmpeg do Debian Bookworm puxa as dependências automaticamente.
Passo 4 — Workflow de build da imagem (build-docker.yml)
Este workflow roda apenas quando o Dockerfile ou o requirements.txt mudam. É ele que publica a imagem no ghcr.io:
name: Build e Publica Imagem Docker on: push: paths: - 'Dockerfile' - 'requirements.txt' workflow_dispatch: # permite rodar manualmente pelo GitHub UI jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write # necessário para publicar no ghcr.io steps: - name: Checkout do repositório uses: actions/checkout@v4 - name: Login no GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build e push da imagem uses: docker/build-push-action@v5 with: context: . push: true tags: ghcr.io/rodrigoqbqb/render-engine:latest
ghcr.io/rodrigoqbqb/render-engine:latest usa seu nome de usuário do GitHub em letras minúsculas. Se o seu usuário tiver maiúsculas no GitHub, o ghcr.io converte automaticamente para lowercase — mas é boa prática já escrever em minúsculo no YML.
Passo 5 — Atualizando o run_notebook.yml
Agora é só atualizar seu workflow principal para usar a imagem que você acabou de publicar. O ponto chave é a diretiva container: — ela diz ao GitHub Actions para executar todos os steps dentro do seu Docker:
name: motor on: workflow_dispatch: schedule: - cron: '0 10 * * *' # ajuste conforme sua necessidade jobs: motor: runs-on: ubuntu-latest permissions: contents: read packages: read # lê a imagem do ghcr.io # ↓ TODA A MÁGICA ESTÁ AQUI — executa dentro do seu Docker container: image: ghcr.io/rodrigoqbqb/render-engine:latest credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout do repositório privado uses: actions/checkout@v4 # Sem apt-get. Sem pip install. Apenas roda o script. - name: Executar render.py run: python render.py env: OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} OAUTH_REFRESH_TOKEN: ${{ secrets.OAUTH_REFRESH_TOKEN }} ID_PLANILHA_GOOGLE: ${{ secrets.ID_PLANILHA_GOOGLE }} URL_DO_BLOG_BLOGGER: ${{ secrets.URL_DO_BLOG_BLOGGER }} GEMINI_API_KEYS: ${{ secrets.GEMINI_API_KEYS }}
Passo 6 — SSH para debug em tempo real (opcional)
Quando um job falha e você precisa entrar no container para investigar, adicione este step temporário no run_notebook.yml. Ele usa o Ngrok para criar um túnel SSH:
# Adicione este step ANTES do render.py quando precisar debugar # Lembre de remover depois — não deixe em produção - name: Debug SSH (temporário) if: failure() # só ativa se o step anterior falhar run: | /usr/sbin/sshd & curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | tee /etc/apt/trusted.gpg.d/ngrok.asc curl -Lo /usr/local/bin/ngrok https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz tar xf /usr/local/bin/ngrok -C /usr/local/bin/ ngrok config add-authtoken ${{ secrets.NGROK_TOKEN }} ngrok tcp 22 & sleep 3 curl -s http://localhost:4040/api/tunnels | python3 -c "import sys,json; t=json.load(sys.stdin)['tunnels'][0]['public_url']; print(f'SSH: {t.replace(\"tcp://\",\"ssh root@\").replace(\":\",\" -p \",1)}')" sleep 1800 # mantém aberto por 30 minutos
canalqb_debug definida no Dockerfile — troque para algo único antes de usar. Nunca deixe o step de debug ativo em produção. Use sempre com if: failure() para que só abra quando necessário. Adicione NGROK_TOKEN como secret no GitHub.
Passo 7 — Primeiro deploy: ordem de execução
Na primeira vez, siga esta ordem exata — o workflow principal precisa que a imagem já exista antes de rodar:
| Ordem | O que fazer | Como |
|---|---|---|
| 1º | Crie o Dockerfile e requirements.txt | Commit + push no repositório |
| 2º | Rode o build-docker.yml | Actions → build-docker.yml → Run workflow |
| 3º | Verifique a imagem publicada | github.com → Packages → render-engine |
| 4º | Torne a imagem pública OU mantenha privada | Package Settings → Change visibility |
| 5º | Atualize o run_notebook.yml | Commit + push com a diretiva container: |
| 6º | Rode o motor | Actions → motor → Run workflow |
GITHUB_TOKEN com permissão packages: read já dá acesso automaticamente entre workflows do mesmo repositório. Não precisa de nenhum secret adicional para isso.
📚 Recursos e Documentação Oficial
Como publicar, gerenciar visibilidade e autenticar imagens no ghcr.io
Documentação oficial da diretiva container: no workflow YML
Action oficial do Docker para build e push de imagens no CI/CD
Todas as variantes disponíveis: slim, alpine, bookworm — e quando usar cada uma
Como gerar o authtoken e criar túneis TCP para SSH em containers
Gostou do tutorial?
Se isso te poupou horas de reinstalação, compartilha com quem também usa GitHub Actions!
🎬 Ver mais no @CanalQb 📝 Ver todos os tutoriaisℹ️ Nota Técnica: Os scripts e configurações fornecidos são para fins educacionais e de automação pessoal. Teste sempre em um repositório de teste antes de aplicar em produção. O autor não se responsabiliza por uso de minutos além da cota gratuita do GitHub Actions ou por configurações de segurança inadequadas.
@CanalQb — canalqb.com.br

Comentários
Comente só assim vamos crescer juntos!