Pular para o conteúdo
Development

Desembaraçando a dependência do Colima nos deploys do Kamal

Por Victor Da Luz
kamaldockercigithub-actionsdev-logblog-manager

Toda vez que eu precisava fazer um deploy de produção do blog-manager, tinha que lembrar de uma coisa: o Colima está rodando? Se não estivesse, o kamal deploy simplesmente travava. O daemon Docker local do meu Mac era uma peça estrutural do caminho de deploy de produção, e eu vivia esquecendo disso.

A causa raiz: o config/deploy.yml tinha builder.remote: ssh://kamal@blog-manager.internal. O Kamal fazia SSH no LXC de produção, mas precisava de um daemon Docker local para o contexto do buildx. Sem Colima, sem build, sem deploy.

O staging tinha o mesmo problema, só que mais escondido. O workflow de deploy do staging rodava em um runner self-hosted dentro do LXC de staging e chamava bin/kamal deploy -d staging, que usava builder.remote: ssh://kamal@blog-manager-staging.internal para construir de volta nele mesmo. Funcionava, mas cada merge gerava uma imagem recém-construída que nunca era testada em um runner do GitHub Actions, e seria construída de novo na máquina de desenvolvimento ao fazer deploy para produção. “Passou no staging” não significava nada como sinal de confiança, tecnicamente.

A correção é o padrão clássico do Kamal, build-once-deploy-many: o CI constrói e envia a imagem uma vez, e cada ambiente puxa e faz deploy dessa mesma imagem.

O plano

Três partes:

  1. Um novo workflow build.yml que constrói e envia gitea.example.net/vic/blog-manager:<sha> a cada push para main. PRs recebem uma execução só de build (sem push) para pegar Dockerfiles quebrados cedo.
  2. Um deploy-staging.yml reescrito, encadeado a partir do build.yml via workflow_run, que faz o deploy com --skip-push --version=<sha> em vez de construir localmente.
  3. Remover builder.remote das duas configurações de deploy.

Para produção: bin/kamal deploy --skip-push --version=<sha> de qualquer máquina. Sem Colima. Sem daemon Docker local. Só acesso SSH ao host.

Três coisas que a pesquisa errou

A descrição original do issue dizia para enviar para registry.internal/blog-manager; na verdade isso é um cache pull-through do Docker Hub, não um destino de push. O registro de verdade é a instância do Gitea (container 1009 no Proxmox).

A pesquisa também dizia que o Gitea era acessível publicamente via Cloudflare. Não é. dig +short gitea.example.net @1.1.1.1 retorna 192.168.x.x, um endereço RFC1918 interno. O runner ubuntu-latest hospedado pelo GitHub não consegue alcançá-lo. Descobri isso da pior forma, quando a primeira tentativa de build deu timeout tentando fazer login no registro. A correção: rodar o job de build no runner [self-hosted, blog-manager-staging], que fica dentro do homelab.

A terceira coisa: o kamal deploy --skip-push valida se a imagem tem um label service que bate com o nome do serviço definido em config/deploy.yml. O kamal build push adiciona isso automaticamente. O docker/build-push-action não. O deploy puxava a imagem sem problema, e então a rejeitava:

Image gitea.../blog-manager:<sha> is missing the 'service' label

Uma linha no workflow resolveu:

labels: service=blog_manager

A pegadinha do dotenv, de novo

Tem mais uma pegadinha com os secrets do Kamal no CI, a mesma que já tinha pegado a configuração do staging. O Kamal avalia .kamal/secrets-common usando Dotenv.parse, não um subprocesso bash. Isso significa que as variáveis de ambiente do CI não ficam disponíveis dentro da expansão de parâmetro ${VAR:-fallback}, só em subprocessos $(cmd). A solução: escrever config/master.key explicitamente a partir do secret do CI antes de rodar qualquer comando kamal.

A pegadinha do cache em camadas

O plano previa o cache type=registry,mode=max; é a resposta “certa” para cachear camadas Docker em um registro self-hosted. Só que está quebrado contra o Gitea (gitea#28973, o registro do Gitea retorna um erro em PATCH durante o push do cache). O type=gha funciona corretamente e não exige nenhum suporte do registro.

Como o pipeline está agora

push to main
  └─ Build and push image  (self-hosted, blog-manager-staging)
       └─ Deploy staging    (self-hosted, blog-manager-staging, --skip-push)

Deploy de produção: bin/kamal deploy --skip-push --version=<sha>. Staging e produção agora rodam exatamente a mesma imagem.

A parte que falta é o deploy automático para produção com um gate de aprovação do GitHub Environments. Isso exige um runner self-hosted no LXC de produção (1027), a mesma configuração do staging, só que no outro host. Planejado para uma próxima etapa.

Leitura relacionada

Você também pode achar útil

Proton

Proton VPN

VPN comercial com filtragem NetShield e interruptor de desligamento automático.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Airalo

eSIM Airalo

eSIM de dados local para viagens - sem necessidade de trocar um SIM físico.

Este é meu link de indicação da Airalo. Você recebe um desconto no seu primeiro eSIM e eu ganho crédito da Airalo para o meu.

Saiba mais
Proton

Proton Drive

Armazenamento em nuvem criptografado, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais