1. Introdução
Este é o workshop Rails 8 no Google Cloud. Neste codelab prático, você vai migrar um aplicativo Rails 8 moderno de um simples plano de referência local do SQLite para uma arquitetura de referência de nível de produção e pronta para empresas no Google Cloud.
Neste workshop, não vamos apenas implantar um app, mas executar uma modernização da nuvem opinativa e de nível de produção (migração lift-and-shift feita da maneira certa!).
Comece com a configuração local típica de "funciona na minha máquina" (disco temporário, SQLite incorporado, jobs no processo) e evolua progressivamente para uma arquitetura de nuvem empresarial e à prova de falhas no Google Cloud:
- 🐘 Do SQLite local → Cloud SQL (PostgreSQL): não há mais limpeza do banco de dados em reinicializações. Migramos para o PostgreSQL gerenciado conectado com segurança pelo proxy de autenticação do Cloud SQL (mTLS, sem IPs públicos).
- 🪣 Do armazenamento local temporário → GCS particular:o disco sem servidor é temporário. Conectamos o ActiveStorage ao Google Cloud Storage particular usando URLs assinados do IAM (
iam: true). Assim, não há buckets públicos nem chaves vazadas. - 🚀 De jobs no processo → Cloud Run de vários contêineres:fazemos a ponte entre o monólito do Rails e a nuvem sem servidor. Usando o Docker Compose padrão, executamos o Solid Queue como um contêiner de worker dedicado ao lado do Puma em um pod de vários contêineres do Cloud Run. Sem proliferação de microsserviços, sem falta de linhas de execução. O DHH ficaria orgulhoso!
🍌 E como estamos em 2026, turbinamos toda a pilha com o Google Antigravity. Não é algo que pensamos depois. Temos comandos e habilidades para orientar você a aproveitar ao máximo este workshop.
🏛️ Arquitetura de referência de destino (variante 1: empresa de vetor plano limpo)
Esta é a arquitetura de referência canônica do Google Cloud que você vai criar e implantar (Variante 1: Clean Flat Vector Enterprise), mostrando nosso serviço de produção do Cloud Run com vários contêineres conectado a todos os serviços gerenciados de persistência e IA do Google Cloud:

🎞️ A evolução da arquitetura: do zero para a arquitetura nativa da nuvem
Assista nossa pilha se modernizar progressivamente de um plano de ação efêmero de máquina única para um blueprint totalmente gerenciado do Google Cloud em cada marco do workshop:

O que você vai aprender
- Como fazer programação em dupla com o Google Antigravity para desmistificar o Rails 8 e o Google Cloud.
- Como executar diagnósticos automáticos de pré-voo (
just workshop-test). - Como provisionar a infraestrutura em nuvem do Google Cloud de forma assíncrona usando o Terraform e continuar o desenvolvimento local sem bloqueio usando o provedor
google. - Como eliminar antipadrões de segurança: buckets privados do GCS (
iam: true) e túneis mTLS do proxy de autenticação do Cloud SQL em vez de abrir0.0.0.0/0. - Como injetar secrets diretamente do Google Cloud Secret Manager.
- Como orquestrar jobs em segundo plano assíncronos de IA generativa (gerador de capas do NanoBanana, síntese de podcasts bilíngues) usando a Solid Queue.
Vamos começar!
2. Etapa 0: pré-requisitos, configuração do Antigravity e verificação de faturamento
💡 O cenário:você e sua equipe estão criando um aplicativo Rails 8 essencial para a missão. Antes de mexer no código ou iniciar recursos da nuvem, precisamos estabelecer nosso conjunto de ferramentas, conectar o Google Antigravity e verificar nossas credenciais do Google Cloud e a base de faturamento.
1. Lista de verificação de pré-requisitos
Antes de começar, verifique se você tem as seguintes ferramentas disponíveis no seu ambiente:
just(1.21 ou mais): (just --version) o executor de tarefas. Cada comando neste workshop é umajustreceita. Peça para sua plataforma de IA instalar ou consulteskills/rails8app-workshop.- Git (2.30 ou mais recente): (
git --version) para controle de versão, ramificação e clonagem do repositório. - SDK do Google Cloud (CLI
gcloud): instalado e atualizado. - CLI do Terraform (1.5 ou mais recente): para provisionamento declarativo de infraestrutura.
dockeredocker-compose: instalados e em execução localmente.- Ruby
3.4.5: (ruby -v— fixado porblog/.ruby-version; peça ao seu arnês de IA para instalar3.4.5se estiver faltando). O Rails 8 é declarado noGemfilee instalado automaticamente pelobundle install. - Google Antigravity 2.0:seu assistente autônomo de programação em dupla com IA. Faça o download do Google Antigravity 2.0. Faça login com seu Gmail pessoal. Não é necessário um token de API.
2. Autenticação, configuração dedicada e ADC do Google Cloud
Para evitar conflitos com configurações corporativas, pessoais ou de várias contas do gcloud, recomendamos criar uma configuração nomeada dedicada para este workshop:
# 1. Create and activate dedicated workshop configuration (Fixes Issue #38)
gcloud config configurations create rails8-on-gcp-workshop --activate 2>/dev/null || \
gcloud config configurations activate rails8-on-gcp-workshop
# 2. Authenticate your account and Application Default Credentials (ADC)
gcloud auth login $GOOGLE_CLOUD_ACCOUNT
gcloud auth application-default login
# 3. Configure active project and region
gcloud config set project $GOOGLE_CLOUD_PROJECT
gcloud config set compute/region europe-west1
💡 Por que uma configuração dedicada? O uso do gcloud config configurations create rails8-on-gcp-workshop isola todas as configurações da CLI (conta, projeto de cota, região padrão) especificamente para este workshop. Quando terminar, você poderá voltar à configuração normal a qualquer momento com gcloud config configurations activate default.

3. 🚨 Verificação do faturamento do GCP
⚠️ Observação: as implantações do Google Cloud SQL e do Cloud Run exigem uma conta de faturamento vinculada ativa ou créditos educacionais válidos do workshop. Vamos verificar se a configuração está correta. Para isso, execute a verificação de verificação de faturamento:
gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT
Verifique se billingEnabled: true foi retornado. Você também pode listar todas as suas contas de faturamento:
gcloud billing accounts list
Verifique se pelo menos um deles mostra OPEN: True. Se nenhuma estiver ativa, vincule uma conta de faturamento ou resgate seu cupom de crédito do workshop na página de faturamento do Console do Google Cloud.

4. Clonar o repositório e parear com o Antigravity
Clone o repositório e entre no diretório. Observe que ficamos totalmente em main:
git clone https://github.com/palladius/rails8-app-on-gcp.git
cd rails8-app-on-gcp
Abra esse diretório no Google Antigravity. O Antigravity inspeciona automaticamente o repositório, lê AGENTS.md e fica disponível como seu programador em dupla.
5. 🪫 Se o Antigravity ficar sem créditos sem custo financeiro
⚠️ Os créditos do Cloud que você acabou de resgatar NÃO recarregam o Antigravity. São duas plataformas de faturamento separadas: a cota do Antigravity fica nos planos com IA do Google (Google One), enquanto os vales e créditos do Cloud ficam em uma conta do Cloud Billing. Vincular uma conta de faturamento ao seu projeto do GCP não vai dar a você um único comando extra do Antigravity.
Se o agente parar com uma mensagem de cota, siga esta ordem. As duas primeiras opções são sem custo financeiro e instantâneas:
- Mudar o pool de modelos. A cota é rastreada por família de modelos, e o Antigravity mostra dois pools independentes (modelos do Gemini x modelos do Claude/GPT). Os modelos Claude e GPT-OSS estão disponíveis no nível sem custo financeiro. Por isso, um pool do Gemini esgotado geralmente deixa o outro intacto. Use o seletor de modelos abaixo da caixa de comando.

- Escolha um modelo Flash em vez do Pro. Os limites de taxa estão relacionados à quantidade de trabalho que o agente faz por solicitação. Portanto, um modelo mais leve aumenta o que resta.
- Use a CLI do Antigravity com sua própria chave da API Gemini. O recurso BYOK não é compatível com o ambiente de desenvolvimento integrado do Antigravity. A CLI é a alternativa documentada. Crie uma chave sem custo financeiro no Google AI Studio (é necessário um projeto, mas não uma conta de faturamento). Depois, faça o download e configure a CLI do Antigravity.
- Aguarde ou faça upgrade. A cota do nível sem custo financeiro é atualizada semanalmente. A mensagem de erro informa a data de redefinição. O Google AI Pro/Ultra é a única maneira documentada de aumentar o valor de referência no app para computador. Esses planos são atualizados a cada cinco horas e podem usar os créditos de IA comprados no excedente.
- Embora este seja um workshop do Antigravity, a maioria das ferramentas de programação de IA também funciona. Basta apontar a sua para o diretório
AGENTS.mdeskills/no repositório.
6. Validação automática da etapa 0
Verifique se o ambiente da etapa 0 está 100% em conformidade com o pacote de avaliação:
just workshop-eval 0
✨ O momento incrível:o Antigravity e o mecanismo de avaliação do workshop verificam suas versões da CLI, o ambiente Ruby e a autenticação do ADC em menos de 2 segundos!
3. Etapa 1: início da infraestrutura do Terraform e diagnósticos de pré-teste
💡 A estratégia:bancos de dados gerenciados, como o Google Cloud SQL PostgreSQL, levam aproximadamente de 10 a 12 minutos para serem provisionados. Em vez de esperar depois, vamos iniciar a infraestrutura imutável com o Terraform agora mesmo em segundo plano enquanto desenvolvemos localmente.
1. Executar o pacote de diagnóstico automático de pré-simulação
Antes de lançar a infraestrutura em nuvem, execute o pacote de testes abrangente de pré-simulação:
just workshop-test
Este script (bin/workshop_diagnostics.rb):
- Verifica sua configuração de identidade do
GOOGLE_CLOUD_ACCOUNT. - Verifica o faturamento ativo e a vinculação do projeto.
- Valida as Application Default Credentials (ADC) para a Vertex AI.
- Confirma a imagem de canary seed do ActiveStorage (
blog/app/assets/images/gcs_dev_image.jpg).

Se .env estiver faltando, copie do modelo documentado:
cp .env.dist .env
# Edit .env and configure GOOGLE_CLOUD_ACCOUNT with your Google/Gmail account
💡 Dica:se just workshop-test informar que a API Billing está desativada no seu projeto, ative-a rapidamente usando:
gcloud services enable cloudbilling.googleapis.com
2. ⏱️ Iniciar a infraestrutura do Terraform de forma assíncrona
Execute a receita do Terraform de uma etapa, que cria o bucket de estado do back-end remoto e aciona a implantação da infraestrutura:
just terraform-apply

Alternativa manual (ou execute `iac/bin/terraform-apply.sh` diretamente)
# Fall back to active gcloud config if environment variables are unset
GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project)}"
GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Create remote state bucket idempotently
gcloud storage buckets create "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" --location="$GOOGLE_CLOUD_REGION" 2>/dev/null || \
gcloud storage buckets describe "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" >/dev/null
cd iac
terraform init -backend-config="bucket=${GOOGLE_CLOUD_PROJECT}-tfstate"
terraform apply -auto-approve -var="project_id=${GOOGLE_CLOUD_PROJECT}" -var="region=${GOOGLE_CLOUD_REGION}"
cd ..
O que o Terraform provisiona:
- Bucket do Google Cloud Storage:criado com acesso privado e assinatura de credenciais do IAM (
iam: true). - Imagem de teste canário:faz upload de
gcs_dev_image.jpgpara o bucket e ativa a verificação completa de blobs. - Instância do Google Cloud SQL PostgreSQL:inicia o provisionamento em segundo plano (~10 a 12 minutos).

💡 Observação:você pode ignorar com segurança o aviso sobre backups automáticos ausentes. Não vamos precisar deles para este banco de dados do workshop, embora os backups diários automáticos sejam uma prática recomendada essencial para qualquer banco de dados de produção real.

📝 Anote o URL do Cloud Run:a aplicação do Terraform vai ser concluída e imprimir as saídas da infraestrutura. Anote ou copie para a área de transferência seu cloud_run_url. Você vai precisar dele nas etapas posteriores para verificar seu serviço ativo.
3. Validação automática da etapa 1 (opcional)
Verifique se os pré-requisitos e as configurações da etapa 1 foram aprovados:
just workshop-eval 1
⏱️ Observação:essa avaliação verifica recursos de nuvem ativos e pode levar alguns minutos para ser concluída. É totalmente opcional. Enquanto ele é executado no terminal, você pode prosseguir imediatamente para a próxima etapa sem esperar.
4. Etapa 2: a linha de base local, o Mailpit e a integração do administrador
Nosso ponto de partida é um aplicativo de blog Rails 8 limpo e moderno em execução no localhost com SQLite, interceptação de e-mail do Mailpit e ActiveStorage baseado em disco.
1. Inicializar o app localmente
📁 Cuidado com o diretório! Esse repositório contém o app Rails em blog/ e o Terraform em iac/. A etapa 1 deixou você na raiz do repositório. Portanto, todos os comandos da etapa 2 são executados de
blog/: Gemfile, bin/rails e compose.yaml moram lá. A execução deles na raiz falha com no configuration file provided: not found ou um Gemfile ausente.
Acesse o app Rails, confirme se você está no Ruby fixado, instale as gems e configure o banco de dados:
cd blog
ruby -v # must print 3.4.5 — see Step 0 if it does not
bundle install
bin/rails db:setup
🧯 Erro de permissão ao gravar em
/var/lib/gems/
ou
ruby -v
mostra o Ruby do sistema (por exemplo, 3.3.x)? O Ruby do sistema Linux padrão ignora completamente os arquivos .ruby-version e tenta instalar gems em todo o sistema. Verifique se o rbenv está carregado no shell do terminal ativo:
eval "$(rbenv init - bash)" # or: eval "$(rbenv init - zsh)"
ruby -v # confirm it now outputs 3.4.5!
Observação:se você pulou a instalação da versão 3.4.5 na etapa 0, execute rbenv install 3.4.5. Ou simplesmente use o Modo A (Docker Compose) abaixo (just compose-up), que executa a pilha Ruby fixada completamente dentro de contêineres sem tocar em gems do host.
💡 Prefere não pensar nisso? just install e just compose-up da raiz do repositório fazem o cd blog para você.
2. 🌱 Descoberta automática de sementes inteligentes e bootstrap de administrador (problemas nº 21 e 25)
O seed de banco de dados (db/seeds.rb) tem o descobrimento automático de ambiente inteligente:
- Ele inspeciona seu adaptador de banco de dados ativo (SQLite x Postgres) e a configuração de armazenamento.
- Ele detecta o Estágio 0 (localhost) e cria automaticamente o usuário administrador inicial e a postagem pedagógica:
[LOCAL BASELINE] Welcome to Rails 8 on Localhost!- Anexo de imagem triste local pronta para uso (
local_sad_image.png) com marca-d'água informando que o armazenamento em disco local é temporário.
- Ele aciona automaticamente um e-mail de redefinição de senha pelo ActionMailer.
Verifique ou execute novamente a semente (usa automaticamente seu GOOGLE_CLOUD_ACCOUNT de .env):
bin/rails db:seed # or from repo root: just seed
Inicialize o stack de desenvolvimento local usando o Docker Compose:
docker compose up -d # from blog/ — or `just compose-up` from the repo root

O aplicativo, o servidor SMTP local do Mailpit e o visualizador de banco de dados do Adminer agora estão sendo executados juntos em contêineres isolados:
- Rails App: http://localhost:3000
- Interface da Web do Mailpit: http://localhost:8025
- Interface do Adminer DB: http://localhost:8081
🧯 Conflito na porta 3000 ou
server.pid
erro? Se você já tiver iniciado um servidor de desenvolvimento nativo (bin/dev), pare-o com Ctrl+C. Se o Docker reclamar que a porta 3000 já está alocada ou encontrar um server.pid restante, limpe com:
just compose-down
rm -f blog/tmp/pids/server.pid
just compose-up
3. A experiência do Mailpit e o login de administrador
- Capturar e-mails enviados com o Mailpit: abra
http://localhost:8025no navegador. Confira a caixa de entrada local. Durantedb:seed, o ActionMailer enviou um e-mail de integração de administrador que o Mailpit capturou com segurança localmente sem tocar em servidores ou credenciais de e-mail externos:
2. Faça login no blog: abra http://localhost:3000 no navegador.
- Clique no link de redefinição de senha diretamente no e-mail do Mailpit para definir sua senha.
- Como alternativa, faça login usando o e-mail da sua conta do Google Cloud (de
.env) e a senha padrão:Ch4ng3m3!!1.
- Observe os selos de telemetria visual:
- Observe o banner e os selos amarelos do ambiente na interface:
Notice: Ephemeral Database Active (POSTGRESQL)e[EPHEMERAL DB / STORAGE] 💾 Local. - Observe a marca d'água da postagem: o selo de casa (
nanobanana_stamp_local.pngno canto inferior direito da imagem da capa). Isso fornece uma confirmação visual imediata de que seus recursos e banco de dados estão vinculados ao armazenamento local efêmero.
- Observe o banner e os selos amarelos do ambiente na interface:

💡 Dica profissional opcional: treino do console do Rails. Quer saber como o Rails interage com os dados de seed da CLI? Acesse o console interativo:
bin/rails console
Inspecione o usuário administrador propagado de forma dinâmica usando sua variável de ambiente:
user = User.find_by(email_address: ENV.fetch("GOOGLE_CLOUD_ACCOUNT"))
# Or simply grab the first user:
user = User.first
puts "Admin email: #{user.email_address} (created via: #{user.created_via})"
exit
4. Validação automática da etapa 2
Verifique seu valor de referência local e a configuração de administrador:
just workshop-eval 2

5. A questão: contêineres sem estado
Os contêineres do Cloud Run não têm estado e são temporários. Se implantarmos nosso banco de dados SQLite e o diretório storage/ local diretamente no Cloud Run, todas as postagens e imagens enviadas serão apagadas permanentemente sempre que um contêiner for escalonado para zero ou reiniciado.
Na próxima etapa, vamos implantar intencionalmente essa configuração efêmera no Cloud Run para testemunhar o choque sem estado em primeira mão.
5. Etapa 3: implantação 1: o choque sem estado (WOW inicial em 3 minutos!)
Em vez de esperar de 15 a 20 minutos pelos bancos de dados na nuvem antes de ver algo na Web, o desenvolvimento moderno sem servidor começa com uma vitória inicial: a implantação do nosso aplicativo Rails de contêiner único diretamente no Google Cloud Run em menos de 3 minutos.
1. A retrospectiva da Máquina do tempo
Para garantir que nossa configuração inicial seja 100% temporária (banco de dados SQLite local e armazenamento de arquivos em disco local), use o mecanismo Zero-Branch Time-Machine:
just workshop-rewind 1
💡 O que aconteceu?workshop-rewind 1 aplicou a sobreposição de configuração stage-1-stateless a blog/config/ sem sair da ramificação main. Seu app está configurado com o SQLite no disco do contêiner e o ActiveStorage no sistema de arquivos local.
2. Como implantar o Puma de contêiner único no Cloud Run
Implante diretamente do código-fonte para o Cloud Run. O Rails 8 moderno gera automaticamente um Dockerfile oficial e pronto para produção (com builds de vários estágios, jemalloc e segurança não raiz). Ao fazer a implantação da pasta blog/, o Google Cloud Build detecta esse Dockerfile nativo diretamente e provisiona um serviço gerenciado sem servidor:
# Ensure you are inside the Rails application directory
cd blog
# Ensure default region is set
export GOOGLE_CLOUD_REGION="europe-west1"
# Deploy single-container service from source (with dummy key fallback if starting without credentials)
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--allow-unauthenticated \
--set-env-vars GOOGLE_CLOUD_ACCOUNT=$GOOGLE_CLOUD_ACCOUNT,SECRET_KEY_BASE_DUMMY=1
Você vai ver uma saída de progresso semelhante a esta:

⏳ Isso leva alguns minutos enquanto o Cloud Build empacota o contêiner e provisiona a revisão do Cloud Run.
🧯 Solução de problemas e ressalvas comuns
PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions
? Duas causas diferentes geram o mesmo erro:
- A gcloud acabou de ativar uma API para você. Se a implantação tiver impresso
The following APIs are not enabled ... cloudbuild.googleapis.come você tiver respondidoY, talvez o build tenha começado antes da propagação das novas permissões. O próprio comando gcloud avisa que "isso vai levar alguns minutos". Aguarde um minuto e execute o mesmo comando novamente. Geralmente, isso funciona. A etapa 1terraform applynormalmente ativa essa API para você, então o aviso não deve aparecer. - A conta de serviço padrão do Compute realmente não tem papéis. Os projetos modernos do GCP aplicam o princípio de privilégio mínimo, o que interrompe as implantações de origem. Corrija com: O
just project-status # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builderjust workshop-testtambém informa isso agora. Portanto, vale a pena executar novamente se a implantação continuar falhando.
Durante a implantação:
- O Cloud Run cria a imagem do contêiner do Rails.
- Ele atribui um domínio TLS público e seguro:
https://blog-[hash]-[region].a.run.app. - O tráfego começa a ser encaminhado para o Puma na porta 8080.
3. ✨ O momento WOW inicial
Abra o URL gerado do Cloud Run no seu navegador.
- Seu aplicativo moderno do Rails 8 está ativo no Google Cloud.
- Faça login com suas credenciais de administrador (
GOOGLE_CLOUD_ACCOUNTeAPP_ADMIN_PASSWORD). Se não encontrar um botão de login, clique em "Nova postagem". Isso vai pedir o usuário e a senha primeiro:
- Clique em Nova postagem, escreva um artigo intitulado Minha primeira postagem do Cloud Run, anexe uma imagem e clique em Criar postagem.
- Sua postagem é publicada com formatação completa, e a imagem é renderizada.
- Observe a telemetria visual:
- Selo do cabeçalho:
[EPHEMERAL DB / STORAGE] 💾 Local - Marca-d'água de imagem: o selo 🏠 (
127.0.0.1selo de disco temporário no canto inferior direito).
- Selo do cabeçalho:
🐝 Participe do ranking do Live Workshop Hive! Se você estiver on-line e o fiscal estiver mostrando o ranking, e quiser participar dele, adicione o URL do Cloud Run aqui: 👉 Registre seu Cloud Run no ranking

4. 💥 The Catch: The Stateless Shock & The "Puma Workaround" Trap
O Cloud Run é uma plataforma sem estado e sem servidor. Quando o tráfego da Web cai para zero, o Cloud Run reduz a escala para zero instâncias de contêiner e economiza dinheiro. Quando uma nova solicitação HTTP chega ou uma nova revisão de contêiner é implantada, o Cloud Run inicia uma imagem de contêiner limpa e totalmente nova.
Banner de jobs travados
Quando você cria uma postagem ou anexa uma imagem em uma implantação de contêiner único, o Rails enfileira tarefas do ActiveJob (como análise de dimensão de imagem ou indexação de metadados). Mas como ninguém está executando um worker em segundo plano, o banner de aviso vai aparecer:

A correção tentadora: executar a fila sólida no Puma
Um desenvolvedor inteligente pode dizer: "Espere! O Rails 8 permite executar o Solid Queue diretamente no processo do servidor da Web Puma ao ativar o
SOLID_QUEUE_IN_PUMA=true
variável de ambiente!"
Vamos testar essa solução alternativa no Cloud Run:
# Force a revision update to enable Solid Queue inside Puma
gcloud run services update blog \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars SOLID_QUEUE_IN_PUMA=true
Agora, volte para o navegador e atualize a página:
💥 The Stateless Shock:
- A boa notícia:o worker do Solid Queue agora está ativo no Puma. Todos os novos jobs são esgotados imediatamente.
- O banho de água fria (o problema!):
- Como o Cloud Run implantou uma nova revisão, a instância de contêiner anterior foi substituída.
- O artigo que você escreveu e o arquivo do banco de dados SQLite no disco foram completamente apagados!
- A lição de arquitetura:executar workers em segundo plano no Puma consome CPU/RAM valiosas da linha de execução da Web e ainda não resolve a persistência.
5. Validação automatizada da etapa 3
Verifique a implantação do Cloud Run e o processamento de alertas:
just workshop-eval 3
✨ A lição:soluções alternativas de contêiner único, como SOLID_QUEUE_IN_PUMA, são úteis para o desenvolvimento local, mas na arquitetura moderna da nuvem:
- Os arquivos de mídia exigem o Google Cloud Storage (GCS) (etapa 4).
- Dados relacionais e filas de jobs exigem o Google Cloud SQL gerenciado (etapa 5).
- Workers de segundo plano pesados pertencem a um contêiner secundário dedicado (etapa 6).
6. Etapa 4: implantação 3: armazenamento permanente do GCS e aviso de POLA
Nesta etapa, separamos o armazenamento de mídia e arquivos do disco do contêiner ao mudar o ActiveStorage para o Google Cloud Storage (GCS), usando URLs assinados de curta duração pela API IAM Credentials (iam: true).
Antes de mexer em qualquer configuração, entenda as três coisas que o Rails precisa para se comunicar com o GCS:
① ID do projeto + nome do bucket: o Rails precisa saber onde armazenar blobs. O app lê GOOGLE_CLOUD_PROJECT do ambiente e deriva o nome do bucket como ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. O Terraform da etapa 1 já criou esse bucket. Não é necessário fazer mais nada.
② Uma conta de serviço com as permissões certas: o Cloud Run é executado como a conta de serviço padrão do Compute Engine (PROJECT_NUMBER-compute@developer.gserviceaccount.com). Essa conta de serviço precisa de dois papéis do IAM no bucket:
roles/storage.objectAdmin: para fazer upload e veicular blobsroles/iam.serviceAccountTokenCreatorpor si só: para chamar a API IAM CredentialssignBlobe gerar URLs assinados de curta duração.
⚠️ O atalho tentador —
public: true A maneira mais rápida de fazer o GCS funcionar é definir public: true em storage.yml e conceder allUsers:objectViewer no bucket. As imagens carregam instantaneamente, sem necessidade de assinatura. Não faça isso. Um bucket público vaza todas as mídias do usuário enviadas para a Internet aberta, para sempre. Nosso blueprint usa o iam: true para manter o bucket 100% particular. Cada URL de imagem é um token assinado e expirado gerado na hora pela API IAM Credentials.
③
ACTIVE_STORAGE_SERVICE=google: a única variável de ambiente que muda o Rails do disco local para o GCS durante a execução. Sem mudança de código, sem nova implantação da lógica do aplicativo: apenas uma alternância de variável de ambiente.
1. A retrospectiva da Máquina do tempo
Avance a Máquina do tempo para a Etapa 2:
just workshop-rewind 2
Inspecione blog/config/storage.yml. Você vai ver que o app define três ambientes nomeados do GCS (dev / test / prod), todos usando o mesmo padrão seguro:
<% gcp_project = ENV.fetch("GOOGLE_CLOUD_PROJECT") { Rails.application.credentials.dig(:gcs, :project) } %>
<% gcs_signer_sa = ENV.fetch("GCS_SIGNER_SA_EMAIL") { "rails-cloudrun-sa@#{gcp_project}.iam.gserviceaccount.com" } %>
google_prod: &google_prod
service: GCS
project: <%= gcp_project %>
bucket: <%= gcp_project %>-activestorage-prod # ← derived from project ID, no extra env var!
iam: true # Sign URLs via IAM Credentials signBlob API (no private key JSON required!)
gsa_email: <%= gcs_signer_sa %>
# Convenience alias — ACTIVE_STORAGE_SERVICE=google points here
google:
<<: *google_prod

2. Como conceder permissões de armazenamento e assinatura do IAM
Verifique se a conta de serviço do ambiente de execução do Cloud Run tem permissões para assinar URLs e fazer upload de objetos:
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
# Grant Storage Object Admin
gcloud storage buckets add-iam-policy-binding gs://$GCS_BUCKET \
--member="serviceAccount:$RUN_SA" \
--role="roles/storage.objectAdmin"
# Grant Service Account Token Creator (required for IAM signed URLs)
gcloud iam service-accounts add-iam-policy-binding $RUN_SA \
--member="serviceAccount:$RUN_SA" \
--role="roles/iam.serviceAccountTokenCreator"
3. Terceira implantação no Cloud Run com o GCS anexado
Faça uma nova implantação com o GCS ativado. Verifique se você está na pasta blog/. Caso contrário, o Cloud Run vai tentar criar usando Buildpacks em vez do Dockerfile do Rails 8, o que vai causar falhas na criação.
Use --update-env-vars (não --set-env-vars) para preservar as variáveis de ambiente atuais (chave principal, configuração do IAP etc.):
cd blog
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars ACTIVE_STORAGE_SERVICE=google
💡 Dica:se a implantação falhar, clique no link dos registros do Cloud Build fornecido na saída do terminal, conforme mostrado na figura abaixo. Você também pode pedir ajuda ao Antigravity para decifrar e corrigir o erro.

4. ✨ A imagem sobrevivente e o carimbo da nuvem
- Atualize o URL do blog.
- Crie uma postagem de blog chamada "Sobrevivendo à nuvem" e faça upload de uma foto.
- Observe o canto inferior direito da imagem:
- O selo 🏠 sumiu!
- Ele foi substituído pelo carimbo colorido do GCS na nuvem (
nanobanana_stamp_cloud.png).
- Force outra reinicialização do contêiner, que vai acionar uma quarta revisão:
gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s) - Atualize a página: enquanto o banco de dados SQLite é redefinido, o binário da imagem fica seguro e protegido no Google Cloud Storage. Verificar via CLI:
gcloud storage ls -l -r gs://$GCS_BUCKET/
Confira uma lista de blobs e objetos de mídia enviados que podem aparecer no console do navegador do Google Cloud Storage no bucket de produção (gs://$GCS_BUCKET):

5. ⚠️ O problema: banner de aviso de jobs travados
Quando você enviou a imagem, o ActiveStorage enfileirou um job de análise (ActiveStorage::AnalyzeJob) para extrair dimensões e metadados.
No entanto, o Cloud Run está executando apenas um único contêiner da Web (puma). No Rails 8, a Solid Queue armazena jobs em segundo plano no banco de dados, mas ninguém está executando bundle exec rails solid_queue:start.
Na parte de cima da página do blog, você vai encontrar um banner de aviso brilhante renderizado por blog/app/views/layouts/_check_stuck_jobs.html.erb.
Vamos resolver isso corretamente na Etapa 6, quando passarmos para uma arquitetura sidecar de vários contêineres de nível de produção com um contêiner worker dedicado executando o Solid Queue de forma independente do tráfego da Web.
6. Validação automática da etapa 4
Verifique a configuração do GCS e a telemetria de jobs bloqueados:
just workshop-eval 4
7. Etapa 5: lista de verificação de pré-voo: conectividade do Cloud SQL, secrets e banco de dados
Enquanto você aprendia sobre contêineres sem estado e o Google Cloud Storage nas etapas 3 e 4, o Terraform terminou de preparar seu back-end de produção em segundo plano.
Antes de implantar nossa arquitetura sidecar de produção completa com três contêineres (Implantação 4 na etapa 6), esta etapa funciona como uma inspeção prévia rápida para verificar a integridade, a conectividade e o acesso secreto do banco de dados.
0. Variáveis de ambiente de inicialização
Exporte todas as variáveis de shell necessárias diretamente das saídas do Terraform:
# Core project variables (should already be set from Step 0)
export GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Compute runtime service account
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
# Cloud SQL instance name (from Terraform output, NOT hardcoded)
export SQL_INSTANCE_NAME=$(cd iac && terraform output -raw sql_instance_name 2>/dev/null || gcloud sql instances list --format='value(name)' --limit=1)
# Database password (from Terraform output)
export DB_PASSWORD=$(cd iac && terraform output -raw db_password 2>/dev/null || echo "CHANGE_ME")
# GCS bucket names
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
echo "✅ Pre-flight environment variables ready:"
echo " RUN_SA=$RUN_SA"
echo " SQL_INSTANCE_NAME=$SQL_INSTANCE_NAME"
echo " GCS_BUCKET=$GCS_BUCKET"
1. Verificação de integridade do Cloud SQL
Verifique se a instância gerenciada do PostgreSQL concluiu o provisionamento inicial e está íntegra:
echo "Checking instance: $SQL_INSTANCE_NAME"
gcloud sql instances describe $SQL_INSTANCE_NAME --format="value(state)"
A saída precisa ser RUNNABLE.
2. (Opcional) Testar a conectividade do banco de dados no seu notebook
Em produção, os contêineres do Cloud Run se conectam com segurança ao Cloud SQL por um proxy sidecar criptografado (127.0.0.1:5432) sem expor o banco de dados à Internet pública (0.0.0.0/0).
ℹ️ Observação:esta etapa é totalmente opcional. No Cloud Run, o contêiner secundário oficial do proxy de autenticação do Cloud SQL processa a conectividade mTLS automaticamente. Você não precisa instalar nada localmente para concluir o workshop.
Se você tiver o cloud-sql-proxy e o psql instalados no seu computador e quiser verificar a conectividade direta do seu laptop:
# Optional: test connectivity via Google-managed mTLS proxy
gcloud sql connect $SQL_INSTANCE_NAME --user=rails_user --database=rails_production
Se uma senha for solicitada, insira
$DB_PASSWORD
. Caso contrário, pule direto para a etapa 3 abaixo.
3. Como inspecionar secrets provisionados pelo Terraform
Nunca armazene senhas de banco de dados, chaves de API ou chaves principais do Rails em texto simples no git ou em variáveis de ambiente de contêiner. Na arquitetura de nuvem moderna, usamos o Google Cloud Secret Manager para injeção de tempo de execução de confiança zero.
ℹ️ O Terraform fez isso por você na etapa 1:
rails-master-keypara descriptografar credenciais do Rails no ambiente de execução.rails-db-password: uma senha aleatória criptograficamente segura que corresponde ao usuário do Cloud SQL.rails-admin-password: uma senha segura para seu usuário administrador inicial.
Verifique se os secrets estão presentes e ativos no Secret Manager:
gcloud secrets list --filter="name:rails-"

🔑 Sincronizar o master.key com seu laptop (sincronização local)
Como o blog/config/master.key é ignorado pelo Git por motivos de segurança, seu clone local ainda não tem esse arquivo. Execute este snippet de auto-recuperação amigável para extrair do Secret Manager e permitir que suas ferramentas locais do Rails descriptografem as credenciais:
if [ ! -f blog/config/master.key ]; then
echo "📥 Pulling rails-master-key from Secret Manager to local blog/config/master.key..."
gcloud secrets versions access latest --secret=rails-master-key > blog/config/master.key
echo "🔑 Created local blog/config/master.key! 🎉"
else
echo "✅ Local blog/config/master.key already exists! 🚀"
fi
🧪 Playground: quer criar um secret personalizado só para ver como é fácil? O Secret Manager não é apenas para o Rails. É possível criar qualquer secret com um simples comando da CLI:
echo -n "MySuperSecretValue123!" | gcloud secrets create workshop-fun-secret --data-file=-
echo "✨ Created workshop-fun-secret in Secret Manager! 🪄"
4. Como verificar as permissões do SecretAccessor
Para que os contêineres do Cloud Run montem esses secrets como variáveis de ambiente ou montagens de volume na inicialização, a conta de serviço do ambiente de execução ($RUN_SA) precisa da função roles/secretmanager.secretAccessor em cada secret.
O Terraform já concedeu isso na etapa 1. Verifique a vinculação da política do IAM em rails-master-key:
# Verify that Cloud Run runtime SA has Secret Accessor role
gcloud secrets get-iam-policy rails-master-key \
--flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.members:$RUN_SA"
Você verá:
ROLE MEMBERS
roles/secretmanager.secretAccessor serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com

5. Validação automática de pré-voo
Execute o conjunto de testes automatizado da etapa 5 para confirmar se o banco de dados pode ser executado, se os secrets não estão vazios e se as permissões do IAM estão verdes:
just workshop-eval 5
8. Etapa 6: implantação 4: sidecars de vários contêineres empresariais (o padrão ouro)
Agora vamos montar a arquitetura de referência definitiva: Cloud Run com vários contêineres.
1. Como restaurar o padrão ouro
Restaure a configuração do repositório para o estado canônico main:
just workshop-restore-gold
2. Como inspecionar o blueprint de produção de três contêineres
Abra blog/compose.prod.yaml e inspecione a arquitetura:

graph LR
Client[Internet / HTTPS Client] -->|Port 8080| Web[Web Container: Puma]
Web -->|Localhost:5432| Proxy[Sidecar: Cloud SQL Auth Proxy]
Worker[Worker Container: Solid Queue] -->|Localhost:5432| Proxy
Proxy -->|IAM mTLS Tunnel| CloudSQL[(Google Cloud SQL PostgreSQL)]
A implantação de produção executa três contêineres coordenados que compartilham a mesma rede local:
web: atende ao tráfego HTTP na porta 8080 via Puma.worker: contêiner dedicado de processamento em segundo plano que executabundle exec rails solid_queue:start.cloudsql-proxy: sidecar oficial do proxy de autenticação do Google Cloud SQL (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) vinculado a127.0.0.1:5432.
3. Executar migrações de banco de dados com um job do Cloud Run
Antes de rotear o tráfego da Web para nossa nova implantação, precisamos executar migrações de banco de dados e seed inicial no Cloud SQL.
⚖️ Como executar migrações em contêineres: duas estratégias
Em ambientes de nuvem em contêineres, há duas abordagens comuns para executar migrações de banco de dados:
- Opção A: automática dentro do ponto de entrada do Docker (
entrypoint.shexecutadb:migratena inicialização do contêiner)- Prós:totalmente automatizado. Os desenvolvedores não precisam se lembrar de executar um comando.
- Desvantagem:perigoso no escalonamento automático sem servidor. Quando o Cloud Run aumenta a escala para 10 instâncias simultaneamente em um tráfego alto, 10 contêineres competem para executar migrações simultaneamente, causando bloqueio de banco de dados, condições de corrida de esquema ou tempos limite de inicialização.
- Opção B: explícita via job do Cloud Run (nossa escolha arquitetônica)
- Prós:controle completo, segurança transacional e zero condições de corrida. Executa como uma tarefa de contêiner única e dedicada, completamente separada do tráfego da Web. Se uma migração falhar, a exibição na Web não será afetada.
- Desvantagens:exige o acionamento de um job da CLI ou uma etapa de CI/CD antes da implantação.
Escolhemos a opção B para ter máxima confiabilidade na produção.
⚠️ Crítico: DATABASE_URL Formato e RFC 3986 Strictness do Ruby 3.4.5
Os jobs do Cloud Run se conectam ao Cloud SQL usando o proxy de soquete Unix nativo (/cloudsql/PROJECT:REGION:INSTANCE). O DATABASE_URL precisa usar o formato de barra tripla:
postgresql:///rails_production?user=rails_user&password=PASS&host=/cloudsql/PROJECT:REGION:INSTANCE
# Core environment variables for database migration & initial admin seed
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
echo "Admin user will be seeded for: $GOOGLE_CLOUD_ACCOUNT"
# Get the latest blog image from Cloud Run (jobs don't support --source)
export BLOG_IMAGE=$(gcloud run services describe blog --region=$GOOGLE_CLOUD_REGION --format='value(spec.template.spec.containers[0].image)')
# Build the DATABASE_URL in triple-slash format (required for Unix socket proxy)
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_CONNECTION="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
export DB_URL="postgresql:///rails_production?user=rails_user&password=${DB_PASSWORD}&host=/cloudsql/${CLOUDSQL_CONNECTION}"
# Create migration job — note ALL 4 database URL env vars and GOOGLE_CLOUD_ACCOUNT for seed user creation
gcloud run jobs create rails-migrate \
--image=$BLOG_IMAGE \
--command "bin/rails" \
--args "db:prepare" \
--set-env-vars="DATABASE_URL=${DB_URL},DATABASE_QUEUE_URL=${DB_URL},DATABASE_CACHE_URL=${DB_URL},DATABASE_CABLE_URL=${DB_URL},GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},GOOGLE_CLOUD_ACCOUNT=${GOOGLE_CLOUD_ACCOUNT}" \
--set-secrets="RAILS_MASTER_KEY=rails-master-key:latest" \
--set-cloudsql-instances="${CLOUDSQL_CONNECTION}" \
--service-account=$RUN_SA \
--region $GOOGLE_CLOUD_REGION 2>/dev/null || true
# Execute migration job
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
💡 Rails 8 Multi-Database:o app usa quatro bancos de dados (primário, fila, cache e cabo) por database.yml. Todas as quatro variáveis de ambiente DATABASE_*_URL precisam apontar para a mesma instância do Cloud SQL. Caso contrário, as tabelas do Solid Queue/Cache/Cable não serão criadas.
Depois de db:prepare, carregue os esquemas de fila/cache/cabo do Solid:
# Schema load for queue, cache, and cable databases (creates solid_queue_jobs, etc.)
gcloud run jobs update rails-migrate \
--command "bash" \
--args "-c,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bin/rails db:schema:load:queue db:schema:load:cache db:schema:load:cable" \
--region $GOOGLE_CLOUD_REGION
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
Na figura acima, você pode conferir uma possível saída de
gcloud run jobs executions describe $JOBNAME
Console de jobs do Google Cloud Run mostrando
rails-migrate
execution Succeeded
4. Quarta implantação: implantação do Cloud Run de vários contêineres usando o Docker Compose
Agora implante a pilha de produção genuína de três contêineres (web, worker, cloudsql-proxy) diretamente do compose.prod.yaml:
# 1. Self-healing environment exports (guarantees no missing master key or DB password)
export RAILS_MASTER_KEY="${RAILS_MASTER_KEY:-$(cat config/master.key 2>/dev/null || gcloud secrets versions access latest --secret=rails-master-key)}"
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_INSTANCE_CONNECTION_NAME="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
# 2. Database credentials & GCS attachments
export DB_USER="rails_user"
export DB_NAME="rails_production"
export GCS_BUCKET="${GCS_BUCKET}"
export ACTIVE_STORAGE_SERVICE="google"
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
export GEMINI_API_KEY="${GEMINI_API_KEY:-dummy-key}"
# 3. Deploy the full multi-container sidecar architecture to Cloud Run
gcloud run compose up compose.prod.yaml \
--region=$GOOGLE_CLOUD_REGION \
--allow-unauthenticated
Console do Google Cloud Run: guia "Contêineres" mostrando os três contêineres secundários (
cloudsql-proxy
,
web
,
worker
)
💡 Curiosidade da interface / falha do console:no menu suspenso do console, você pode notar que o Cloud Run rotula a entrada superior como Ingress container: cloudsql-proxy. Não se preocupe! Esse é apenas um problema visual na interface do usuário do console do Google Cloud (rastreado em #149) causado pela ordenação de dependências de contêineres. Na realidade, web é o único contêiner que detecta na porta HTTP 8080 e processa 100% do tráfego da Web de entrada, enquanto cloudsql-proxy atende estritamente ao tráfego privado do PostgreSQL na porta 5432.
5. ✨ O momento incrível: zero avisos e persistência total na nuvem!
Respire fundo e admire a tela: todos os banners de alerta desapareceram! 🪄
O blog de produção do Rails 8: interface impecável, zero banners de aviso e estado persistente do Cloud SQL!
Agora o aplicativo é oficialmente de nível de produção e nativo da nuvem:
- Com tecnologia do Google Cloud SQL (PostgreSQL) gerenciado por tunelamento mTLS seguro.
- Mídia armazenada com segurança no Google Cloud Storage particular com assinatura de blob do IAM.
- Zero alertas de jobs presos e avisos de armazenamento temporário.
Abra o URL do Cloud Run:
- Confira os selos de telemetria:
[CLOUD PERSISTENT 🐘 ☁️]foi verificado na sua seed de banco de dados.- Os selos do Google Cloud Run e do GCS no rodapé ficam verdes.
- O banner de aviso de jobs presos desapareceu completamente.
- Criar postagens e comentários em blogs.
- Reinicie ou reimplante quantas vezes quiser: seus dados, postagens, comentários e recursos vão durar para sempre no Cloud SQL e no GCS.
6. Validação automática da etapa 6
Verifique a configuração de vários contêineres:
just workshop-eval 6
9. Etapa 7: pipelines de IA generativa, Podcastifier e a caça ao tesouro do GCS 🏴☠️
Com a Solid Queue em execução em um contêiner dedicado e o Google Cloud Storage ativo, podemos liberar a IA generativa assíncrona.
1. O gerador de capas vintage do NanoBanana (pipeline de IA generativa pronto para uso)

Boa notícia: essa funcionalidade já está totalmente implementada na base de código. Não é necessário escrever código. Sua tarefa aqui é acionar e observar um pipeline assíncrono de IA generativa na nuvem em ação.
🧠 Como funciona nos bastidores
- Autenticação:não é necessário usar chaves de API. Ele usa Application Default Credentials (
roles/aiplatform.userna Vertex AI) automaticamente pela conta de serviço de ambiente de execução do Cloud Run. Como alternativa, seGEMINI_API_KEYfor fornecido, ele vai chamar a API Gemini diretamente. - O pipeline:quando um artigo é criado sem uma imagem de capa, um callback
after_commitenfileiraGenerateCoverImageJobna Solid Queue. - Modelo:ele solicita que o modelo multimodal do Google (Gemini 2.5 Flash Image / Imagen na Vertex AI) gere um pôster de filme italiano autêntico da década de 1960 ("Locandina di un film 1960") com uma banana e um rubi brilhante "8".
- Marca d'água de origem dinâmica:usa
libvipspara marcar dinamicamente um selo visual no canto (selo de nuvem colorida no GCS). - Troca dinâmica do Turbo Stream:quando a imagem é anexada, o
Turbo::StreamsChannel.broadcast_refresh_to(post)atualiza o navegador do leitor em tempo real sem recarregar a página manualmente.

📐 Ver fonte do diagrama de sequência do Mermaid
sequenceDiagram
autonumber
actor User as 👤 Reader / Author
participant Web as 🌐 Web (Puma)
participant DB as 🐘 Cloud SQL (Solid Queue)
participant Worker as ⚙️ Worker (Solid Queue)
participant Vertex as 🧠 Vertex AI (Gemini Flash / Imagen)
participant GCS as ☁️ Google Cloud Storage
User->>Web: POST /posts (Title & Body, No Cover)
Web->>DB: 1. Save Post & Enqueue GenerateCoverImageJob
Web-->>User: Redirect to Post (turbo_stream subscribed)
Worker->>DB: 2. Dequeue GenerateCoverImageJob
Worker->>Vertex: 3. Prompt for 1960s Italian Poster (ADC auth)
Vertex-->>Worker: 4. Raw Image Buffer
Worker->>Worker: 5. Stamp Provenance Watermark (libvips)
Worker->>GCS: 6. Attach Blob via ActiveStorage (IAM signBlob)
Worker->>Web: 7. Turbo::StreamsChannel.broadcast_refresh_to(post)
Web-->>User: ⚡ Live UI Hot-Swap: Poster magically appears!
🧪 Teste agora!
É possível acionar de duas maneiras:
- Opção A (interface da Web): clique em Nova postagem, insira um título como "Arquitetura sem servidor com Ruby on Rails 8", escreva algum conteúdo e deixe a entrada de arquivo de imagem da capa vazia. Clique em Criar postagem.
- Opção B (executador da CLI): execute no terminal:
bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
Aguarde de 15 a 30 segundos e confira os registros do worker da fila sólida no Cloud Run. O pôster vintage vai aparecer diretamente na sua postagem.
📸 TODO(riccardo): add screenshot of a blog post with an AI-generated vintage 1960s Italian movie poster featuring a cameo banana and ruby 8
2. A Quest do Podcastifier bilíngue (exercício prático de programação antigravidade)
⚠️ Aviso importante:ao contrário do Gerador de capas acima, este recurso AINDA NÃO foi implementado.
Se você abrir blog/app/jobs/podcastifier_job.rb, vai encontrar um stub de scaffold vazio:
class PodcastifierJob < ApplicationJob
queue_as :default
def perform(post_id)
# TODO(student): COMPLETE_ME — Pair program with Google Antigravity to implement
# Italian TTS podcast generation using voice 'it-IT-Wavenet-A' via Cloud TTS / ADC!
end
end
Nesta atividade prática, você vai programar em dupla com o Google Antigravity para concluir este recurso:
- Comando Antigravity:"Me ajude a implementar PodcastifierJob em
app/jobs/podcastifier_job.rb. Ele precisa buscar a postagem, traduzir ou sintetizar uma visão geral em áudio em italiano usando o Google Cloud Text-to-Speech (voz "it-IT-Wavenet-A" com código de idioma "it-IT" via credenciais do ADC) e anexar o arquivo de áudio apost.podcast_audio_itusando o ActiveStorage!" - Adicionar player à visualização:em
app/views/posts/show.html.erb, adicione um player HTML5quando@post.podcast_audio_it.attached?. - Acionar síntese:quando salvo, o Solid Queue executa o job de conversão de texto em voz de forma assíncrona em segundo plano.

🎧 Exemplos de saídas do Podcastifier:
💡 Precisa de ajuda ou não sabe o que fazer? Uma solução de referência completa e testada está disponível na ramificação solutions/podcastifier. Você pode inspecionar a qualquer momento com git diff solutions/podcastifier.
3. [OPCIONAL] 🏴☠️ A caça ao tesouro do GCS (recuperação de blob do console)
ℹ️ Verificação de pré-requisitos: local
cloud-sql-proxy
CLI obrigatória: este exercício opcional conecta seu laptop local diretamente ao Google Cloud SQL por um túnel mTLS criptografado. Verifique se você instalou o proxy da CLI:
which cloud-sql-proxy
- Se estiver instalado:siga as instruções abaixo.
- Se NÃO estiver instalado e você quiser fazer esta missão:instale com:
# Linux (x86_64) curl -o /tmp/cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.0/cloud-sql-proxy.linux.amd64 chmod +x /tmp/cloud-sql-proxy && sudo mv /tmp/cloud-sql-proxy /usr/local/bin/cloud-sql-proxy # macOS (Homebrew) brew install cloud-sql-proxy - Caso contrário:pule para a validação automatizada da etapa 7.
Lembra da foto que você enviou na etapa 4 antes de o reinício do contêiner apagar o banco de dados SQLite efêmero? Esse arquivo de imagem ainda está armazenado com segurança no seu bucket particular do GCS como um "blob órfão".
Vamos usar o console do Rails para resgatá-lo e anexá-lo a uma postagem do Cloud SQL:
# Connect to your production database via Rails console and Cloud SQL Proxy
cloud-sql-proxy --port 5432 ${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME} &
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production bin/rails c
No console do Rails:
# 1. Search for orphaned GCS blobs in ActiveStorage
orphan_blobs = ActiveStorage::Blob.where.missing(:attachments)
puts "🏴☠️ Found #{orphan_blobs.count} orphaned GCS blobs!"
# 2. Attach the surviving blob to your latest post
if orphan_blobs.any?
post = Post.last
post.cover_image.attach(orphan_blobs.first)
post.save!
puts "🎉 Rescued blob #{orphan_blobs.first.filename} attached to '#{post.title}'!"
end
exit
📸 TODO(riccardo): add screenshot of the interactive Rails console executing the GCS Treasure Hunt snippet and rescuing orphaned ActiveStorage blobs
Atualize seu blog: a foto enviada na etapa 4 do Stateless Shock é ressuscitada e anexada permanentemente à sua postagem do Cloud SQL.
4. Validação automatizada da etapa 7
Verificar jobs e recursos de IA generativa:
just workshop-eval 7
10. Etapa 8: Escolha sua aventura / Missões avançadas 🏆
Agora que você aprendeu a arquitetura de referência canônica, escolha sua missão de conclusão!
🛡️ Quest 1: Zero Trust Google IAP (Identity-Aware Proxy)
- Dificuldade:média (segurança empresarial)
- O objetivo:bloquear seu aplicativo do Cloud Run para que apenas suas contas verificadas do Google / Gmail possam acessá-lo, eliminando completamente o login com senha.
- Como funciona:
- Um balanceador de carga de aplicativo HTTPS externo encerra o Google OAuth e verifica a identidade antes que o tráfego chegue ao Cloud Run.
- O Google encaminha cabeçalhos de identidade verificada (
X-Goog-Authenticated-User-Email). - O Rails faz login automaticamente na identidade verificada do Google usando
blog/app/controllers/concerns/iap_authenticatable.rb:module IapAuthenticatable extend ActiveSupport::Concern included { before_action :authenticate_via_iap } private def authenticate_via_iap return unless (raw = request.headers["X-Goog-Authenticated-User-Email"]).present? email = raw.sub(/^accounts\.google\.com:/, "") user = User.find_or_create_by!(email_address: email) { |u| u.password = SecureRandom.hex(16) } start_new_session_for(user) unless authenticated? end end - Ative o módulo do Terraform em
iac/iap.tfcomenable_iap = truee especifique suas Contas do Google permitidas:iap_allowed_users = [var.google_cloud_account]

📊 Quest 2: Production SRE Telemetry & Cloud Logging
- Dificuldade:média (observabilidade)
- A meta:fazer streaming de registros de aplicativos JSON estruturados com IDs de correlação de rastreamento diretamente para o Cloud Logging e detectar exceções de produção em tempo real com o Cloud Error Reporting.
- Como funciona:
- Configure
blog/config/environments/production.rbpara emitir registros JSON estruturados com rótulos de rastreamento do GCP. - Acione uma exceção de teste intencional e veja o Error Reporting do Google Cloud agrupar e notificar você instantaneamente.
- Configure
📸 TODO(riccardo): add screenshot of Google Cloud Error Reporting showing grouped production exceptions and Cloud Logging structured JSON payload with trace labels
🧠 Quest 3: pgvector pesquisa semântica e RAG do Gemini
- Dificuldade:difícil (projeto final de IA generativa)
- Objetivo:pesquisar artigos conceitualmente usando embeddings de vetor armazenados no PostgreSQL no Cloud SQL.
- Como funciona:
- Ative a extensão
vectorna sua instância do Cloud SQL PostgreSQL:CREATE EXTENSION IF NOT EXISTS vector; - Adicione a gema
neighborablog/Gemfilee gere embeddings de texto com o Gemini (text-embedding-004) emPost#after_save. - Realize consultas de distância de cosseno (
<=>) para alimentar uma barra de pesquisa semântica com Turbo Streams.
- Ative a extensão
📸 TODO(riccardo): add screenshot of the live blog semantic search bar returning conceptual matches with cosine similarity scores
⚡ Quest 4: Assistente de auditoria de performance e SEO
- Dificuldade:fácil / divertido (experiência do desenvolvedor)
- Objetivo:auditar as Core Web Vitals, a Largest Contentful Paint (LCP) e os índices de legibilidade de Flesch-Kincaid / Fog usando as ferramentas Antigravity e Speedgrapher.
📸 TODO(riccardo): add screenshot of Speedgrapher / Lighthouse SEO and Core Web Vitals audit summary
🤔 Caixa de curiosidades: como ter um console Rails ativo no Cloud Run
💡 A pergunta:em VMs tradicionais ou plataformas PaaS no estilo Heroku, os desenvolvedores geralmente usam SSH em uma instância em execução ou usam heroku run rails console para inspecionar ou corrigir dados de produção em tempo real. Qual é o equivalente nativo do Cloud Run e como você o protege?
Por que
gcloud run exec
não existe para os serviços do Cloud Run
O Cloud Run é uma plataforma sem servidor: seu contêiner pode ser executado como zero, uma ou centenas de instâncias idênticas, sem uma identidade estável ou um shell endereçável. Isso é fundamentalmente diferente do Kubernetes (kubectl exec), em que um pod específico tem um soquete nomeado ao qual você pode se conectar.
# ❌ This does NOT exist for Cloud Run Services:
gcloud run services exec blog --region europe-west1 -- rails console
# ✅ This exists for Cloud Run JOBS (non-interactive, one-shot tasks):
gcloud run jobs execute my-job --region europe-west1 --wait
Os quatro padrões nativos do GCP
Padrão | Comando | Interativo? | Segurança |
1. Proxy local + banco de dados remoto |
| ✅ Completo | 🔒 mTLS do proxy de autenticação do Cloud SQL |
2. Job do Cloud Run (único) |
| ❌ Somente script | 🔒 Papel do IAM obrigatório |
3. Gema do console da Web + IAP | Navegador → | ✅ Completo | 🛡️ Confiança zero do IAP |
4. Cloud Shell + proxy | Cloud Shell → | ✅ Completo | 🔒 Identidade do Google |
Padrão 1 (recomendado para inspeção de dados): execute um console Rails local que se comunica diretamente com o Cloud SQL PostgreSQL pelo proxy de autenticação do Cloud SQL:
# In one terminal: start the auth proxy
cloud-sql-proxy rails8-fl-20260907b-xm8ko4:europe-west1:your-instance
# In another terminal: open a read-write or sandbox console
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production \
bin/rails console --sandbox
Você tem total interatividade do IRB sem exposição à rede. O proxy do Cloud SQL usa mTLS em um soquete de domínio Unix ou loopback.
Padrão 3 (console da Web + zero trust do IAP, o caminho do navegador reforçado):
A gem web-console oferece uma sessão do IRB baseada em navegador. Sem proteção, ela é uma vulnerabilidade de segurança crítica. Nunca a exponha publicamente. Mas, combinado com o IAP do Google (Quest 1), a superfície de ataque diminui: apenas identidades verificadas do Google na sua lista de permissões podem acessar o endpoint /rails/console.
# Gemfile — only load in development/staging, never production without IAP!
gem "web-console", group: :development
# config/environments/production.rb — if IAP is enabled:
config.web_console.allowed_ips = ["0.0.0.0/0"] # IAP terminates auth before us
⚠️ O padrão 3 exige que a missão 1 (IAP) seja ativada primeiro. Sem ele, /rails/console em um URL público é equivalente a deixar a sala do servidor destrancada. Com o IAP, ele se torna uma ferramenta de operador poderosa reforçada pela confiança zero de nível Google.
Pergunte ao Antigravity: "Mostre como executar
rails console --sandbox
com segurança no meu banco de dados de produção do Cloud SQL usando o proxy de autenticação do Cloud SQL"
🏆 Graduação no The Hive: prova de trabalho validada por fiscal
Depois de concluir a missão escolhida, reivindique o troféu de graduação da Etapa 8 🏆 no ranking da Hive:
- Envie sua missão no GitHub:
- Abra um novo problema no repositório oficial: github.com/palladius/rails8-app-on-gcp/issues/new
- Formato do título:
🎓 [Step 8 Completed]: - No corpo, inclua uma breve descrição do que você realizou e um link para sua implantação ativa do Cloud Run.
- Conecte seu app do Cloud Run ao problema:
- Anote o número do problema (por exemplo,
88ou o URL completohttps://github.com/palladius/rails8-app-on-gcp/issues/88). - Injete a variável de ambiente
STEP_8_GHIno serviço do Cloud Run:gcloud run services update blog \ --region europe-west1 \ --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER> - Verifique se o endpoint
/status.jsonagora informa sua missão: Você vai ver:curl -s https://<YOUR_APP>.run.app/status.json | jq .quest{ "step_8_completed": true, "ghi_issue": 88, "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88" }
- Anote o número do problema (por exemplo,
- Receba sua revisão do proctor e desbloqueie o troféu de ouro:
- Confira o ranking da Hive: sua linha agora vai mostrar um selo âmbar:
⏳ GHI #XX review pending. - Peça aos monitores do workshop (@palladius, @emilianodellacasa ou @ricc) para inspecionar sua implantação.
- Assim que um fiscal comentar
LGTMno seu problema, o Hive vai elevar automaticamente seu progresso para 8/8 🏆 com um selo dourado/roxo brilhante e um troféu permanente que leva diretamente ao seu problema principal.
- Confira o ranking da Hive: sua linha agora vai mostrar um selo âmbar:
11. Conclusão e limpeza 🎓
Parabéns! 🎉
Você criou e implantou um aplicativo Rails 8 pronto para produção e empresas no Google Cloud Platform:
- Progressão de ramificação zero:domínio de fluxos de trabalho modernos com sobreposições de Time-Machine em
main. - Cloud Storage:armazenamento de objetos escalonável e particular com assinatura de blob do IAM (
iam: true). - Cloud SQL:PostgreSQL gerenciado e protegido com túneis mTLS do proxy de autenticação do Cloud SQL.
- Secret Manager:zero credenciais de texto simples ou vazamentos de arquivos
.env. - Multicontêiner do Cloud Run:arquivos secundários isolados da Web Puma, do worker Solid Queue e de proxy.
- IA generativa:geração assíncrona de pôsteres vintage e síntese de podcasts de TTS com a Vertex AI.
- Quests:IAP zero trust, observabilidade de SRE e embeddings do pgvector.
🧹 Limpeza de recursos
Para evitar cobranças contínuas após a conclusão do workshop, limpe seus recursos do Google Cloud. Escolha a abordagem mais adequada ao seu ambiente:
Opção 1: desativação do Terraform (recomendada / padrão 🟢)
Use essa opção se você estiver usando um projeto na nuvem do Google Cloud compartilhado ou atual em que outros recursos ou dados estão armazenados. Ele destrói apenas os recursos provisionados durante este workshop (Cloud Run, Cloud SQL, Secret Manager, buckets do GCS), deixando o restante do projeto intacto.
cd iac
terraform destroy -auto-approve
Opção 2: exclusão total do projeto (não deixar rastros 🌪️)
Use essa opção se você criou um projeto de workshop dedicado (por exemplo, usando créditos de workshop ou uma sandbox) e quer garantir que nenhum rastreamento permaneça, incluindo registros, metadados, contas de serviço e links de faturamento.
gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet