Deploy na nuvem é só SSH e Docker (e o Kamal prova isso)
Lembro dos meus primeiros deploys. Abrir o FileZilla, arrastar os arquivos PHP pra pasta /var/www/html, dar F5 no navegador e torcer pra não ter esquecido nada. Era tosco, mas era simples. Uma pessoa, um servidor, um arrasta-e-solta.

Daí a gente cresceu. E junto vieram Kubernetes, Terraform, Helm charts, service meshes, pipelines de CI/CD com 47 steps… De repente, pra colocar um CRUD no ar você precisava de um time de DevOps e uma certificação AWS.
Essa semana eu fiz o deploy de um projeto pessoal pra uma VM no Google Cloud usando o Kamal. E a sensação que eu tive foi estranhamente familiar. Era o FileZilla de novo. Só que com zero-downtime, rollback automático e sem aquele medo de “será que eu esqueci o config.php”.
O “one person framework”
O DHH vem construindo o que ele chama de one person framework há alguns anos: a ideia de que uma pessoa sozinha consegue construir, manter e colocar no ar um produto inteiro com Rails. Sem time de frontend separado. Sem time de backend separado. Sem time de infra.
O Kamal (originalmente MRSK, lançado em 2023) foi uma das primeiras peças desse quebra-cabeça — o deploy sem DevOps. Depois vieram o Solid Queue, Solid Cache e Solid Cable pra eliminar a dependência de Redis. O Rails 8 empacotou tudo junto: SQLite como banco padrão, a Solid Trifecta embutida, Propshaft e Importmaps pra matar o Webpack, e o Kamal 2 como ferramenta de deploy oficial.
Pela primeira vez, uma pessoa tem o stack completo: do rails new até a produção, sem sair do ecossistema.
SSH e Docker. Só isso.
Quando você roda bin/kamal deploy, o que acontece por baixo dos panos é quase decepcionante de tão simples:
- Builda a imagem Docker na sua máquina
- Faz push pra um
container registry - Conecta via SSH no servidor
- Roda
docker pullpra baixar a imagem - Roda
docker runpra subir o container - Troca o tráfego pro novo container
Não tem agente instalado no servidor. Não tem daemon. Não tem nada rodando em background esperando comandos. É literalmente o Kamal sentando na sua máquina, abrindo um SSH e digitando comandos Docker no servidor remoto.
É o que você faria manualmente se soubesse os comandos de cabeça. O Kamal só automatiza a sequência.
Mas o detalhe que me pegou: o Kamal não sabe que eu estou usando GCP. Ele não faz nenhuma chamada de API do Google. Não usa o gcloud CLI. Não usa Terraform. Ele só precisa de uma máquina que aceita SSH e tem Docker instalado. Pode ser GCP, AWS, DigitalOcean, Hetzner, um Raspberry Pi debaixo da sua mesa. Tanto faz.
É tipo o FileZilla — ele também não sabia se o servidor era da Locaweb ou do GoDaddy. Ele só precisava do IP, usuário e senha.
O deploy na prática
Meu projeto, o MapWise (editor de mapas com tracking em tempo real, import de CSV e chat com IA), é um Rails 8 com SQLite, Solid Queue e Action Cable. O pacote completo do one person framework. Pra colocar ele no ar numa VM e2-small do GCP (2 vCPU, 2GB RAM), eu precisei de:
Uma VM com Docker instalado
Qualquer VM Linux serve:
gcloud compute instances create mapwise \
--machine-type=e2-small \
--image-family=ubuntu-2404-lts-amd64 \
--image-project=ubuntu-os-cloud
SSH na VM, instalar Docker, sair. Nunca mais você precisa entrar nela.
Um container registry
Um lugar pra guardar a imagem Docker entre a sua máquina e o servidor. Eu usei o Artifact Registry do GCP, mas Docker Hub serve igual:
gcloud artifacts repositories create mapwise \
--repository-format=docker \
--location=us-central1
Três arquivos de config
O config/deploy.yml — o coração:
service: mapwise
image: edy-ai-playground/mapwise/mapwise
servers:
web:
- 34.135.208.39
job:
hosts:
- 34.135.208.39
cmd: bin/jobs
registry:
server: us-central1-docker.pkg.dev
username: oauth2accesstoken
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
clear:
WEB_CONCURRENCY: 2
volumes:
- "mapwise_storage:/rails/storage"
ssh:
user: edy
keys:
- ~/.ssh/google_compute_engine
O .kamal/secrets:
RAILS_MASTER_KEY=$(cat config/master.key)
KAMAL_REGISTRY_PASSWORD=$(gcloud auth print-access-token)
E uma linha no config/environments/production.rb:
config.hosts.clear # permite acesso por IP
Um comando
bin/kamal setup
Dois minutos depois, app no ar. Próximas atualizações? bin/kamal deploy. Fim.
Zero-downtime (a parte que o FileZilla não fazia)
Até aqui era nostalgia. Agora é engenharia.
Quem fez deploy com FTP conhece o terror: você sobe os arquivos um por um, o site fica quebrado durante 30 segundos enquanto metade dos arquivos é da versão nova e metade da velha. Se dava erro no meio, o servidor ficava num estado Frankenstein.
O Kamal resolve isso com um truque esperto:
- O novo container sobe ao lado do antigo — os dois rodam ao mesmo tempo
- Um proxy reverso minúsculo (
kamal-proxy) espera ohealth checkdo novo container passar - O tráfego é redirecionado pro novo container
- O container antigo drena as conexões restantes e morre
Dois containers coexistem por alguns segundos, o proxy faz o corte, e o usuário nem percebe. Se o novo container falhar no health check, o antigo continua servindo como se nada tivesse acontecido.
O que vive no servidor
Depois do deploy, isso é tudo que existe na VM:
kamal-proxy (porta 80)
├── mapwise-web-<sha> (Puma - requests HTTP)
└── mapwise-job-<sha> (Solid Queue - jobs em background)
└── volume: mapwise_storage:/rails/storage
Três containers. Uma rede Docker. Um volume pro SQLite. Sem Nginx. Sem Passenger. Sem Capistrano. Sem sudo systemctl restart. O kamal-proxy cuida do roteamento e o Thruster (embutido no Rails 8) faz compressão e cache de assets.
Se você já configurou um servidor com Nginx + Passenger + Capistrano + rbenv + systemd, sabe a dor. O Kamal troca tudo isso por Docker e pronto.
Os tombos
Nenhum tutorial mostra os erros que você vai tomar. Então fica documentado:
O campo image não inclui o servidor do registry
O Kamal prefixa automaticamente. Se você colocar o caminho completo us-central1-docker.pkg.dev/projeto/repo/app, ele duplica pra us-central1-docker.pkg.dev/us-central1-docker.pkg.dev/projeto/repo/app. O erro que aparece não ajuda em nada.
Chave SSH com passphrase trava o Kamal
Ele tenta ~/.ssh/id_rsa por padrão. Se essa chave tem passphrase, morre com Errno::ENODEV. Esse erro não te diz nada — você fica achando que é problema de rede, de permissão, de firewall. Na verdade é só o Kamal tentando ler a chave, encontrando a passphrase, e não sabendo o que fazer porque não tem como abrir um prompt interativo pra você digitar.
A solução é apontar pra uma chave sem passphrase. E se você está usando GCP, você provavelmente já tem uma. Quando você roda gcloud compute ssh pela primeira vez, o gcloud gera automaticamente um par de chaves em ~/.ssh/google_compute_engine (sem passphrase) e sobe a chave pública pro metadata da VM. É por isso que gcloud compute ssh funciona sem você configurar nada — ele cuida de tudo silenciosamente.
Essa mesma chave funciona com SSH puro, e portanto com o Kamal:
ssh:
keys:
- ~/.ssh/google_compute_engine
Se você não está no GCP, a ideia é a mesma: gere uma chave sem passphrase (ssh-keygen -t ed25519 -N "") e aponte pra ela.
Docker Desktop precisa estar rodando
O Kamal builda a imagem localmente. Sem Docker Desktop ligado, falha silenciosamente no docker buildx build.
O build vem do último git commit, não do working directory
Alterou o deploy.yml mas não commitou? A config nova não vai pro build. Isso é intencional — o Kamal quer garantir que você está deployando código versionado. Mas te pega de surpresa na primeira vez.
A volta à simplicidade
O que me fascina no Kamal é que ele não inventa nada. SSH existe desde 1995. Docker desde 2013. Proxy reverso é conceito dos anos 90. O Kamal só juntou tudo num workflow que respeita a escala real do seu projeto.
Eu acredito que a gente complica deployment porque a indústria tem incentivo pra vender complexidade. Cada camada nova é um produto novo, uma certificação nova, um time novo. Mas a pergunta honesta é: você precisa disso?
Kubernetes é incrível — pra quem tem 50 microserviços e precisa de auto-scaling horizontal. Pra um app Rails com SQLite num servidor só? É um canhão pra matar formiga.
Pra 90% dos projetos, deploy é SSH + Docker. O Kamal só torna isso ergonômico. E a sensação de rodar bin/kamal deploy e ver seu app no ar em dois minutos me lembrou de algo que a gente perdeu pelo caminho: colocar software no ar deveria ser simples.
Como nos tempos de FTP — só que dessa vez, sem o medo.
Por hoje é só.