1. Introduzione
Ti diamo il benvenuto al workshop Rails 8 su Google Cloud. In questa codelab pratica, prenderai un'applicazione Rails 8 moderna da una semplice baseline SQLite locale a un'architettura di riferimento di livello di produzione e pronta per l'uso aziendale su Google Cloud.
In questo workshop non eseguiamo solo il deployment di un'app, ma eseguiamo una modernizzazione del cloud basata su opinioni e di livello di produzione (lift-and-shift eseguito correttamente).
Si parte dalla tipica configurazione locale "funziona sulla mia macchina" (disco temporaneo, SQLite incorporato, job in-process) e la si evolve progressivamente in un'architettura cloud aziendale e a prova di errore su Google Cloud:
- 🐘 Da SQLite locale → Cloud SQL (PostgreSQL): il database non viene più cancellato al riavvio. Passiamo a PostgreSQL gestito connesso in modo sicuro tramite proxy di autenticazione Cloud SQL (mTLS, nessun IP pubblico).
- 🪣 Da archiviazione locale temporanea a GCS privato: il disco serverless è temporaneo. Colleghiamo ActiveStorage a Google Cloud Storage privato utilizzando URL firmati (
iam: true): nessun bucket pubblico, nessuna chiave compromessa. - 🚀 Da job in-process → Cloud Run multi-container: colleghiamo il monolite Rails e il cloud serverless. Utilizzando Docker Compose standard, eseguiamo Solid Queue come container di lavoro dedicato insieme a Puma in un pod multiconteiner di Cloud Run: zero proliferazione di microservizi, zero carenza di thread. DHH sarebbe orgoglioso.
🍌 E poiché viviamo nel 2026, potenziamo l'intero stack con Google Antigravity. Non è un'idea improvvisata, abbiamo prompt e competenze per guidare il tuo harness a eseguire (e godersi) questo workshop.
🏛️ Architettura di riferimento target (variante 1: Clean Flat Vector Enterprise)
Di seguito è riportata l'architettura di riferimento canonica di Google Cloud che creerai ed eseguirai il deployment (Variante 1: Clean Flat Vector Enterprise), che mostra il nostro servizio Cloud Run di produzione multiconteiner collegato a tutti i servizi di persistenza e AI gestiti di Google Cloud:

🎞️ L'evoluzione dell'architettura: da zero al cloud-native
Guarda il nostro stack modernizzarsi progressivamente da una baseline effimera a una singola macchina a un progetto Google Cloud completamente gestito in ogni traguardo del workshop:

Obiettivi didattici
- Come programmare in coppia con Google Antigravity per demistificare Rails 8 e Google Cloud.
- Come eseguire la diagnostica pre-volo automatica (
just workshop-test). - Come eseguire il provisioning dell'infrastruttura Google Cloud in modo asincrono utilizzando Terraform continuando lo sviluppo locale senza blocchi utilizzando il provider
google. - Come eliminare gli anti-pattern di sicurezza: tunnel mTLS privati bucket GCS (
iam: true) e proxy di autenticazione Cloud SQL anziché aprire0.0.0.0/0. - Come inserire i secret direttamente da Google Cloud Secret Manager.
- Come orchestrare job in background di AI generativa asincrona (generatore di copertine NanoBanana, sintesi di podcast bilingue) tramite Solid Queue.
Iniziamo.
2. Passaggio 0: prerequisiti, configurazione di Antigravity e verifica della fatturazione
💡 Lo scenario: tu e il tuo team state creando un'applicazione Rails 8 mission-critical. Prima di toccare il codice o avviare le risorse cloud, dobbiamo stabilire la nostra toolchain, connettere Google Antigravity e verificare le nostre credenziali Google Cloud e le basi della fatturazione.
1. Elenco di controllo dei prerequisiti
Prima di iniziare, assicurati di avere a disposizione i seguenti strumenti nel tuo ambiente:
just(1.21+): (just --version) il task runner: ogni singolo comando di questo workshop è unajustricetta (chiedi all'AI di installarlo o consultaskills/rails8app-workshop).- Git (2.30+): (
git --version) per il controllo della versione, la creazione di rami e la clonazione del repository. - Google Cloud SDK (
gcloudCLI): installato e aggiornato. - Interfaccia a riga di comando Terraform (1.5+): per il provisioning dichiarativo dell'infrastruttura.
dockeredocker-compose: installati ed eseguiti localmente.- Ruby
3.4.5: (ruby -v, aggiunto dablog/.ruby-version; chiedi al tuo sistema AI di installare3.4.5se non è presente). Rails 8 è dichiarato inGemfilee installato automaticamente dabundle install. - Google Antigravity 2.0:il tuo assistente di programmazione in coppia con l'AI autonoma (scarica Google Antigravity 2.0). Accederai con il tuo account Gmail personale, senza bisogno di token API.
2. Autenticazione Google Cloud, configurazione dedicata e Credenziali predefinite dell'applicazione
Per evitare conflitti con configurazioni gcloud aziendali, personali o multi-account esistenti, ti consigliamo vivamente di creare una configurazione denominata dedicata per questo 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
💡 Perché una configurazione dedicata? L'utilizzo di gcloud config configurations create rails8-on-gcp-workshop isola tutte le impostazioni della CLI (account, progetto quota, regione predefinita) specificamente per questo workshop. Al termine, puoi tornare alla configurazione normale in qualsiasi momento con gcloud config configurations activate default.

3. 🚨 Verifica della fatturazione Google Cloud
⚠️ Nota: i deployment di Google Cloud SQL e Cloud Run richiedono un account di fatturazione collegato attivo o crediti didattici validi per il workshop. Assicuriamoci che la configurazione sia corretta. Per farlo, esegui il controllo di verifica della fatturazione:
gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT
Assicurati che billingEnabled: true venga restituito. Puoi anche elencare tutti i tuoi account di fatturazione:
gcloud billing accounts list
Assicurati che almeno uno di questi mostri OPEN: True. Se nessuno è attivo, collega un account di fatturazione o riscatta il coupon per il credito del workshop nella pagina Fatturazione della console Google Cloud.

4. Clona il repository e accoppialo ad Antigravity
Clona il repository e accedi alla directory. Tieni presente che rimaniamo interamente su main:
git clone https://github.com/palladius/rails8-app-on-gcp.git
cd rails8-app-on-gcp
Apri questa directory in Google Antigravity. Antigravity ispezionerà automaticamente il repository, leggerà AGENTS.md e rimarrà in attesa come programmatore in coppia.
5. 🪫 Se Antigravity esaurisce i crediti senza costi
⚠️ I crediti Cloud che hai appena riscattato NON ricaricano Antigravity. Si tratta di due superfici di fatturazione separate: la quota di Antigravity si trova nei piani Google AI (Google One), mentre i voucher e i crediti Cloud vengono inseriti in un account di fatturazione Cloud. Il collegamento di un account di fatturazione al tuo progetto Google Cloud non ti darà un singolo prompt Antigravity aggiuntivo.
Se l'agente si interrompe con un messaggio relativo alla quota, segui questa scala: i primi due sono senza costi e immediati:
- Cambia pool di modelli. La quota viene monitorata per famiglia di modelli e Antigravity mostra due pool indipendenti (modelli Gemini e modelli Claude/GPT). I modelli Claude e GPT-OSS sono disponibili nel Livello senza costi, quindi un pool Gemini esaurito spesso lascia intatto l'altro. Utilizza il selettore del modello sotto la casella del prompt.

- Scegli un modello Flash anziché Pro. I limiti di frequenza sono correlati alla quantità di lavoro svolto dall'agente per richiesta, quindi un modello più leggero estende ciò che rimane.
- Utilizza la CLI Antigravity con la tua chiave API Gemini. Bring Your Own Key non è supportato nell'IDE Antigravity. La CLI è l'alternativa documentata. Crea una chiave senza costi in Google AI Studio (è necessario un progetto, ma nessun account di fatturazione), quindi scarica e configura la CLI Antigravity.
- Attendi o esegui l'upgrade. La quota del livello senza costi viene aggiornata settimanalmente (il messaggio di errore indica la data di reimpostazione). Google AI Pro/Ultra è l'unico modo documentato per aumentare la base di riferimento all'interno dell'app per computer; questi piani vengono aggiornati ogni cinque ore e possono utilizzare i crediti AI acquistati per il superamento.
- Sebbene questo sia un workshop su Antigravity, la maggior parte degli strumenti di codifica AI dovrebbe funzionare. Basta puntare il tuo strumento alle directory
AGENTS.mdeskills/nel repository.
6. Convalida automatizzata del passaggio 0
Verifica che l'ambiente del passaggio 0 sia conforme al 100% alla suite di valutazione:
just workshop-eval 0
✨ Il momento wow: Antigravity e il motore di valutazione del workshop verificano le versioni della CLI, l'ambiente Ruby e l'autenticazione ADC in meno di 2 secondi.
3. Passaggio 1: avvio dell'infrastruttura Terraform e diagnostica pre-volo
💡 La strategia: il provisioning dei database gestiti come Google Cloud SQL PostgreSQL richiede circa 10-12 minuti. Anziché aspettare inattivi in un secondo momento, lanciamo l'infrastruttura immutabile tramite Terraform in background mentre sviluppiamo in locale.
1. Esegui la suite di diagnostica pre-volo automatica
Prima di lanciare l'infrastruttura cloud, esegui la suite di test pre-volo completa:
just workshop-test
Questo script (bin/workshop_diagnostics.rb):
- Verifica la configurazione dell'identità
GOOGLE_CLOUD_ACCOUNT. - Verifica il collegamento attivo di fatturazione e progetto.
- Convalida le Credenziali predefinite dell'applicazione (ADC) per Vertex AI.
- Conferma l'immagine canary seed di ActiveStorage (
blog/app/assets/images/gcs_dev_image.jpg).

Se .env non è presente, copialo dal modello documentato:
cp .env.dist .env
# Edit .env and configure GOOGLE_CLOUD_ACCOUNT with your Google/Gmail account
💡 Suggerimento:se just workshop-test segnala che l'API Billing è disabilitata nel tuo progetto, abilitala rapidamente tramite:
gcloud services enable cloudbilling.googleapis.com
2. ⏱️ Avvia Terraform Infrastructure in modo asincrono
Esegui la ricetta Terraform in un solo passaggio, che crea il bucket di stato del backend remoto e attiva il deployment dell'infrastruttura:
just terraform-apply

Alternativa manuale (o esegui direttamente `iac/bin/terraform-apply.sh`)
# 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 ..
Elementi di cui Terraform esegue il provisioning:
- Bucket Google Cloud Storage:creato con accesso privato e firma delle credenziali IAM (
iam: true). - Immagine di test canary:carica
gcs_dev_image.jpgnel bucket per abilitare la verifica end-to-end dei blob. - Istanza Google Cloud SQL PostgreSQL:avvia il provisioning in background (~10-12 minuti).

💡 Nota:puoi ignorare tranquillamente l'avviso relativo ai backup automatici mancanti. Non ci serviranno per questo database del workshop (anche se i backup automatici giornalieri sono una best practice essenziale per qualsiasi database di produzione reale).

📝 Prendi nota dell'URL Cloud Run:l'applicazione Terraform dovrebbe riuscire e stampare gli output dell'infrastruttura. Annota il tuo cloud_run_url (o copialo negli appunti). Ti servirà nei passaggi successivi per verificare il servizio live.
3. (Facoltativo) Convalida automatizzata del passaggio 1
Verifica che i prerequisiti e le configurazioni del passaggio 1 siano soddisfatti:
just workshop-eval 1
⏱️ Nota:questa valutazione controlla le risorse cloud attive e potrebbe richiedere un paio di minuti. È completamente facoltativo: mentre viene eseguito nel terminale, puoi procedere immediatamente al passaggio successivo senza attendere.
4. Passaggio 2: la baseline locale, Mailpit e l'onboarding dell'amministratore
Il nostro punto di partenza è un'applicazione blog Rails 8 pulita e moderna in esecuzione su localhost con SQLite, intercettazione delle email Mailpit e ActiveStorage basato su disco.
1. Avvia l'app in locale
📁 Attenzione alla directory! Questo repository contiene l'app Rails in blog/ e Terraform in iac/. Il passaggio 1 ti ha lasciato nella radice del repository, quindi ogni comando del passaggio 2 viene eseguito da
blog/: Gemfile, bin/rails e compose.yaml vivono lì. L'esecuzione dalla radice non va a buon fine e viene visualizzato l'errore no configuration file provided: not found o manca Gemfile.
Sposta l'app Rails, verifica di utilizzare Ruby bloccato, quindi installa i gem e configura il database:
cd blog
ruby -v # must print 3.4.5 — see Step 0 if it does not
bundle install
bin/rails db:setup
🧯 Errore di autorizzazione durante la scrittura in
/var/lib/gems/
o
ruby -v
mostra Ruby di sistema (ad es. 3.3.x)? Vanilla Linux system Ruby ignora completamente i file .ruby-version e tenta di installare gem a livello di sistema. Assicurati che rbenv sia caricato nella shell del terminale attivo:
eval "$(rbenv init - bash)" # or: eval "$(rbenv init - zsh)"
ruby -v # confirm it now outputs 3.4.5!
Nota:se hai saltato l'installazione della versione 3.4.5 nel passaggio 0, esegui rbenv install 3.4.5. In alternativa, utilizza la modalità A (Docker Compose) di seguito (just compose-up), che esegue lo stack Ruby bloccato completamente all'interno dei container senza toccare i gem dell'host.
💡 Preferisci non pensarci? just install e just compose-up dalla radice del repository eseguono cd blog per te.
2. 🌱 Rilevamento automatico di Smart Seed e bootstrap dell'amministratore (problemi n. 21 e 25)
Il seed del database (db/seeds.rb) include la rilevamento automatico dell'ambiente intelligente:
- Ispeziona l'adattatore di database attivo (SQLite o Postgres) e la configurazione di archiviazione.
- Rileva la fase 0 (localhost) e crea automaticamente l'utente amministratore iniziale e inserisce il post pedagogico:
[LOCAL BASELINE] Welcome to Rails 8 on Localhost!- Allegato predefinito di immagine triste locale (
local_sad_image.png) con filigrana che ti informa che lo spazio di archiviazione sul disco locale è temporaneo.
- Attiva automaticamente un'email di reimpostazione della password tramite ActionMailer.
Verifica o esegui di nuovo il seed (utilizza automaticamente il tuo GOOGLE_CLOUD_ACCOUNT da .env):
bin/rails db:seed # or from repo root: just seed
Avvia lo stack di sviluppo locale tramite Docker Compose:
docker compose up -d # from blog/ — or `just compose-up` from the repo root

L'applicazione, il server SMTP locale Mailpit e il visualizzatore di database Adminer vengono ora eseguiti insieme in container isolati:
- App Rails: http://localhost:3000
- Interfaccia utente web di Mailpit: http://localhost:8025
- Interfaccia utente del database Adminer: http://localhost:8081
🧯 Conflitto con la porta 3000 o
server.pid
errore? Se in precedenza hai avviato un server di sviluppo nativo (bin/dev), arrestalo con Ctrl+C. Se Docker segnala che la porta 3000 è già allocata o trova un server.pid residuo, esegui la pulizia con:
just compose-down
rm -f blog/tmp/pids/server.pid
just compose-up
3. L'esperienza Mailpit e l'accesso amministratore
- Acquisire le email in uscita con Mailpit: apri
http://localhost:8025nel browser. Dai un'occhiata alla posta in arrivo locale. Durantedb:seed, ActionMailer ha inviato un'email di onboarding dell'amministratore che Mailpit ha acquisito in modo sicuro a livello locale senza toccare server o credenziali email esterni:
2. Accedi al blog: apri http://localhost:3000 nel browser.
- Puoi fare clic sul link di reimpostazione password direttamente nell'email di Mailpit per impostare la password.
- In alternativa, accedi utilizzando l'email del tuo account Google Cloud (da
.env) e la password predefinita:Ch4ng3m3!!1.
- Osserva i badge di telemetria visiva:
- Nota il banner e i badge gialli dell'ambiente nell'interfaccia utente:
Notice: Ephemeral Database Active (POSTGRESQL)e[EPHEMERAL DB / STORAGE] 💾 Local. - Nota la filigrana del post: il timbro 🏠 (
nanobanana_stamp_local.pngnell'angolo in basso a destra dell'immagine di copertina). In questo modo, puoi verificare immediatamente che gli asset e il database siano attualmente associati all'archiviazione locale temporanea.
- Nota il banner e i badge gialli dell'ambiente nell'interfaccia utente:

💡 Suggerimento professionale facoltativo: esercizio con la console Rails Ti incuriosisce il modo in cui Rails interagisce con i dati iniziali dalla CLI? Accedi alla console interattiva:
bin/rails console
Ispeziona l'utente amministratore iniziale in modo dinamico utilizzando la variabile di 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. Convalida automatizzata del passaggio 2
Verifica la configurazione di base locale e dell'amministratore:
just workshop-eval 2

5. Il problema: container stateless
I container Cloud Run sono stateless e temporanei. Se eseguiamo il deployment del nostro database SQLite e della directory locale storage/ direttamente su Cloud Run, tutti i post e le immagini caricate verranno eliminati definitivamente ogni volta che un container viene scalato a zero o riavviato.
Nel passaggio successivo, eseguiremo intenzionalmente il deployment di questa configurazione effimera in Cloud Run per assistere in prima persona allo Stateless Shock.
5. Passaggio 3: esegui il deployment 1 - The Stateless Shock (WOW iniziale in 3 minuti)
Invece di aspettare 15-20 minuti per i database cloud prima di vedere qualcosa sul web, lo sviluppo serverless moderno inizia con una vittoria immediata: il deployment della nostra applicazione Rails a singolo container direttamente su Google Cloud Run in meno di 3 minuti.
1. Il Rewind della macchina del tempo
Per garantire che la nostra configurazione iniziale sia effimera al 100% (database SQLite locale e archiviazione di file su disco locale), utilizza il motore Zero-Branch Time-Machine:
just workshop-rewind 1
💡 Che cosa è successo?workshop-rewind 1 ha applicato l'overlay di configurazione stage-1-stateless a blog/config/ senza uscire dal ramo main. La tua app è configurata con SQLite sul disco del container e ActiveStorage sul file system locale.
2. Deployment di Puma a contenitore singolo in Cloud Run
Esegui il deployment direttamente dal codice sorgente a Cloud Run. Modern Rails 8 genera automaticamente un Dockerfile ufficiale pronto per la produzione (con build multifase, jemalloc e sicurezza non root). Quando esegui il deployment dalla cartella blog/, Google Cloud Build rileva direttamente questo Dockerfile nativo e esegue il provisioning di un servizio serverless gestito:
# 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
Dovresti visualizzare un output di avanzamento simile a questo:

⏳ L'operazione dovrebbe richiedere un paio di minuti mentre Cloud Build pacchettizza il container e esegue il provisioning della revisione Cloud Run.
🧯 Risoluzione dei problemi e avvertenze comuni
PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions
? Due cause diverse generano lo stesso errore:
- gcloud ha appena abilitato un'API per te. Se il deployment ha stampato
The following APIs are not enabled ... cloudbuild.googleapis.come hai rispostoY, la build potrebbe essere iniziata prima che la propagazione delle nuove autorizzazioni sia stata completata. Il prompt di gcloud avvisa che "questa operazione richiederà alcuni minuti". Attendi un minuto ed esegui di nuovo lo stesso comando. In genere l'operazione va a buon fine. Il passaggio 1terraform applyin genere abilita questa API, quindi non dovresti visualizzare la richiesta. - Nel service account Compute predefinito mancano davvero dei ruoli. I progetti Google Cloud moderni applicano il principio del privilegio minimo, il che interrompe i deployment delle origini. Correggi con:
Ora anchejust project-status # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builderjust workshop-testsegnala questo problema, quindi vale la pena eseguire di nuovo il comando se il deployment continua a non riuscire.
Durante il deployment:
- Cloud Run crea l'immagine container Rails.
- Assegna un dominio TLS pubblico e sicuro:
https://blog-[hash]-[region].a.run.app. - Il traffico inizia a essere indirizzato a Puma sulla porta 8080.
3. ✨ Il primo momento WOW
Apri l'URL di Cloud Run generato nel browser.
- La tua moderna applicazione Rails 8 è online su Google Cloud.
- Accedi con le tue credenziali di amministratore (
GOOGLE_CLOUD_ACCOUNTeAPP_ADMIN_PASSWORD). Se non trovi il pulsante di accesso, fai clic su "Nuovo post" e ti verrà chiesto prima l'utente e la password:
- Fai clic su "Nuovo post", scrivi un articolo intitolato "Il mio primo post di Cloud Run", allega un'immagine e fai clic su Crea post.
- Il post viene pubblicato con la formattazione completa e l'immagine viene visualizzata.
- Nota la telemetria visiva:
- Badge dell'intestazione:
[EPHEMERAL DB / STORAGE] 💾 Local - Filigrana immagine: il bollino 🏠 (badge del disco temporaneo
127.0.0.1nell'angolo in basso a destra).
- Badge dell'intestazione:
🐝 Partecipa alla classifica dell'alveare del workshop live. Se sei online e il tuo supervisore sta mostrando la classifica e vuoi partecipare, aggiungi qui il tuo URL Cloud Run: 👉 Registra Cloud Run nella classifica

4. 💥 Il problema: lo shock stateless e la trappola della "soluzione alternativa Puma"
Cloud Run è una piattaforma serverless stateless. Quando il traffico web scende a zero, Cloud Run esegue lo scale down fino a zero istanze container per risparmiare denaro. Quando arriva una nuova richiesta HTTP o viene eseguito il deployment di una nuova revisione del container, Cloud Run avvia un'immagine container nuova e pulita.
Banner Lavori bloccati
Quando crei un post o alleghi un'immagine in un deployment a singolo container, Rails mette in coda le attività ActiveJob (come l'analisi delle dimensioni delle immagini o l'indicizzazione dei metadati). Tuttavia, poiché nessuno esegue un worker in background, vedrai il banner di avviso:

La soluzione allettante: esecuzione di Solid Queue all'interno di Puma
Uno sviluppatore intelligente potrebbe dire: "Aspetta! Rails 8 consente di eseguire Solid Queue direttamente all'interno del processo del server web Puma attivando
SOLID_QUEUE_IN_PUMA=true
variabile di ambiente."
Testiamo questa soluzione alternativa su 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
Ora, torna al browser e aggiorna la pagina:
💥 Lo shock senza stato:
- Buone notizie: il worker Solid Queue è ora attivo in Puma. Tutti i nuovi job vengono eseguiti immediatamente.
- Doccia fredda (il trucco):
- Poiché Cloud Run ha eseguito il deployment di una nuova revisione, l'istanza container precedente è stata sostituita.
- L'articolo che hai scritto e il file del database SQLite sul disco sono stati completamente cancellati.
- La lezione di architettura: l'esecuzione di worker in background all'interno di Puma consuma preziosa CPU/RAM dei thread web e non risolve comunque la persistenza.
5. Convalida automatizzata del passaggio 3
Verifica il deployment di Cloud Run e la gestione degli avvisi:
just workshop-eval 3
✨ La lezione: le soluzioni alternative per i singoli container come SOLID_QUEUE_IN_PUMA sono utili per lo sviluppo locale, ma nell'architettura cloud moderna:
- I file multimediali richiedono Google Cloud Storage (GCS) (passaggio 4).
- I dati relazionali e le code di lavoro richiedono Google Cloud SQL gestito (passaggio 5).
- I worker in background pesanti devono trovarsi in un container sidecar dedicato (passaggio 6).
6. Passaggio 4: esegui il deployment 3 - GCS Persistent Storage e avviso POLA
In questo passaggio, separiamo l'archiviazione di contenuti multimediali e file dal disco del container passando ad ActiveStorage su Google Cloud Storage (GCS), utilizzando URL firmati di breve durata tramite l'API IAM Credentials (iam: true).
Prima di modificare qualsiasi configurazione, scopri le tre cose di cui Rails ha bisogno per comunicare con GCS:
① ID progetto + Nome bucket: Rails deve sapere dove archiviare i blob. L'app legge GOOGLE_CLOUD_PROJECT dall'ambiente e deriva il nome del bucket come ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. Terraform del passaggio 1 ha già creato questo bucket, quindi non è necessario fare altro.
② Un service account con le autorizzazioni giuste: Cloud Run viene eseguito come service account Compute Engine predefinito (PROJECT_NUMBER-compute@developer.gserviceaccount.com). Questo service account richiede due ruoli IAM sul bucket:
roles/storage.objectAdmin: per caricare e pubblicare i blobroles/iam.serviceAccountTokenCreatorsu se stesso per chiamare l'API IAM CredentialssignBlobe generare URL firmati di breve durata
⚠️ La scorciatoia allettante :
public: true Il modo più rapido per far funzionare GCS è impostare public: true in storage.yml e concedere allUsers:objectViewer sul bucket. Le immagini vengono caricate immediatamente, senza bisogno di accedere. Non farlo. Un bucket pubblico espone per sempre tutti i contenuti multimediali degli utenti caricati su internet. Il nostro progetto utilizza iam: true per mantenere il bucket privato al 100%. Ogni URL immagine è un token firmato e in scadenza generato al volo dall'API IAM Credentials.
③
ACTIVE_STORAGE_SERVICE=google: la singola variabile di ambiente che sposta Rails dal disco locale a GCS in fase di runtime. Nessuna modifica al codice, nessun nuovo deployment della logica dell'applicazione: basta attivare/disattivare una variabile di ambiente.
1. Il Rewind della macchina del tempo
Avanza nella macchina del tempo fino alla fase 2:
just workshop-rewind 2
Esamina blog/config/storage.yml: vedrai che l'app definisce tre ambienti GCS denominati (dev / test / prod), tutti che utilizzano lo stesso pattern sicuro:
<% 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. Concessione delle autorizzazioni IAM Storage & Signing
Assicurati che il service account di runtime di Cloud Run disponga delle autorizzazioni per firmare gli URL e caricare gli oggetti:
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. Terzo deployment su Cloud Run con GCS collegato
Esegui nuovamente il deployment con GCS abilitato. Assicurati di trovarti all'interno della cartella blog/ (altrimenti Cloud Run tenta di eseguire la build utilizzando Buildpacks anziché Dockerfile di Rails 8, causando errori di build).
Utilizza --update-env-vars (non --set-env-vars) per conservare le variabili di ambiente esistenti (chiave master, configurazione IAP e così via):
cd blog
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars ACTIVE_STORAGE_SERVICE=google
💡 Suggerimento: se il deployment non va a buon fine, fai clic sul link ai log di Cloud Build fornito nell'output del terminale (come mostrato nella figura seguente). Puoi anche chiedere ad Antigravity di aiutarti a decifrare e correggere l'errore.

4. ✨ L'immagine sopravvissuta e il timbro del cloud
- Aggiorna l'URL del blog.
- Crea un nuovo post del blog intitolato "Surviving the Cloud" (Sopravvivere al cloud) e carica una foto.
- Guarda l'angolo in basso a destra dell'immagine:
- Il bollino 🏠 è sparito.
- Viene sostituito dal colorato timbro GCS Cloud (
nanobanana_stamp_cloud.png).
- Forza un altro riavvio del container (che dovrebbe attivare una quarta revisione):
gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s) - Aggiorna la pagina: durante la reimpostazione del database SQLite, il binario dell'immagine è al sicuro in Google Cloud Storage. Verifica tramite CLI:
gcloud storage ls -l -r gs://$GCS_BUCKET/
Ecco un elenco di blob e oggetti multimediali caricati che potresti visualizzare nella console del browser Google Cloud Storage nel bucket produzione (gs://$GCS_BUCKET):

5. ⚠️ Il problema: banner di avviso per i job bloccati
Quando hai caricato l'immagine, ActiveStorage ha messo in coda un job di analisi (ActiveStorage::AnalyzeJob) per estrarre dimensioni e metadati.
Tuttavia, al momento Cloud Run esegue solo un singolo contenitore web (puma). In Rails 8, Solid Queue archivia i job in background nel database, ma nessuno esegue bundle exec rails solid_queue:start.
Guarda la parte superiore della pagina del blog: vedrai un banner di avviso luminoso visualizzato da blog/app/views/layouts/_check_stuck_jobs.html.erb.
Risolviamo questo problema correttamente nel passaggio 6, quando passeremo a un'architettura sidecar multiconteiner di livello di produzione con un container worker dedicato che esegue Solid Queue indipendentemente dal traffico web.
6. Convalida automatica del passaggio 4
Verifica la configurazione di GCS e la telemetria dei job bloccati:
just workshop-eval 4
7. Passaggio 5: elenco di controllo pre-volo: Cloud SQL, secret e connettività al database
Mentre hai acquisito competenze sui container stateless e su Google Cloud Storage nei passaggi 3 e 4, Terraform ha terminato la preparazione del backend di produzione in background.
Prima di eseguire il deployment della nostra architettura sidecar di produzione completa a tre container (Deploy 4 nel passaggio 6), questo passaggio funge da ispezione pre-volo rapida per verificare l'integrità, la connettività e l'accesso ai secret del database.
0. Variabili di ambiente di bootstrap
Esporta tutte le variabili di shell richieste direttamente dagli output di 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. Controllo dell'integrità di Cloud SQL
Verifica che il provisioning iniziale dell'istanza PostgreSQL gestita sia stato completato e che sia in stato integro:
echo "Checking instance: $SQL_INSTANCE_NAME"
gcloud sql instances describe $SQL_INSTANCE_NAME --format="value(state)"
L'output deve essere RUNNABLE.
2. (Facoltativo) Test della connettività del database dal laptop
In produzione, i container Cloud Run si connettono in modo sicuro a Cloud SQL tramite un proxy sidecar criptato (127.0.0.1:5432) senza esporre il database a internet pubblico (0.0.0.0/0).
ℹ️ Nota: questo passaggio è completamente facoltativo. In Cloud Run, il container sidecar ufficiale del Cloud SQL Auth Proxy gestisce automaticamente la connettività mTLS. Non devi installare nulla in locale per completare il workshop.
Se hai installato cloud-sql-proxy e psql sul tuo computer e vuoi verificare la connettività diretta dal tuo laptop:
# Optional: test connectivity via Google-managed mTLS proxy
gcloud sql connect $SQL_INSTANCE_NAME --user=rails_user --database=rails_production
(Se viene richiesta una password, inserisci
$DB_PASSWORD
. In caso contrario, puoi passare direttamente al passaggio 3 di seguito.)
3. Ispezione dei secret di cui è stato eseguito il provisioning con Terraform
Non archiviare mai password di database, chiavi API o chiavi master Rails in testo normale in Git o nelle variabili di ambiente del container. Nell'architettura cloud moderna, utilizziamo Google Cloud Secret Manager per l'inserimento di runtime zero-trust.
ℹ️ Terraform ha gestito questa operazione per te nel passaggio 1:
rails-master-key: per decrittografare le credenziali Rails in fase di runtime.rails-db-password: una password casuale crittograficamente sicura che corrisponde all'utente Cloud SQL.rails-admin-password: una password sicura per l'utente amministratore iniziale.
Verifica che i secret siano presenti e attivi in Secret Manager:
gcloud secrets list --filter="name:rails-"

🔑 Sincronizzazione di master.key con il laptop (sincronizzazione locale)
Poiché blog/config/master.key è ignorato da Git per motivi di sicurezza, la tua clonazione locale potrebbe non averlo ancora. Esegui questo snippet di auto-ripristino per estrarlo da Secret Manager in modo che gli strumenti Rails locali possano decriptare le credenziali:
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: vuoi creare un secret personalizzato solo per vedere quanto è facile? Secret Manager non è solo per Rails. Puoi creare qualsiasi secret con un semplice comando CLI:
echo -n "MySuperSecretValue123!" | gcloud secrets create workshop-fun-secret --data-file=-
echo "✨ Created workshop-fun-secret in Secret Manager! 🪄"
4. Verifica delle autorizzazioni di Secret Accessor
Affinché i container Cloud Run possano montare questi secret come variabili di ambiente o volumi montati all'avvio, il service account di runtime ($RUN_SA) deve disporre del ruolo roles/secretmanager.secretAccessor per ogni secret.
Terraform lo ha già concesso nel passaggio 1. Verifica l'associazione della policy IAM su 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"
Dovresti vedere:
ROLE MEMBERS
roles/secretmanager.secretAccessor serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com

5. Convalida pre-volo automatizzata
Esegui la suite di test automatizzata del passaggio 5 per verificare che il database sia eseguibile, che i secret non siano vuoti e che le autorizzazioni IAM siano verdi:
just workshop-eval 5
8. Passaggio 6: esegui il deployment di 4 - Enterprise Multi-Container Sidecars (lo standard di riferimento)
Ora assembla l'architettura di riferimento definitiva: Cloud Run multiconteiner.
1. Ripristino del Gold Standard
Ripristina la configurazione del repository allo stato canonico main:
just workshop-restore-gold
2. Esame del progetto di produzione con tre container
Apri blog/compose.prod.yaml ed esamina l'architettura:

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)]
Il deployment di produzione esegue tre container coordinati che condividono la stessa rete locale:
web: gestisce il traffico HTTP sulla porta 8080 tramite Puma.worker: container di elaborazione in background dedicato che eseguebundle exec rails solid_queue:start.cloudsql-proxy: Official Google Cloud SQL Auth Proxy sidecar (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) bound to127.0.0.1:5432.
3. Esecuzione delle migrazioni del database tramite il job Cloud Run
Prima di indirizzare il traffico web al nostro nuovo deployment, dobbiamo eseguire le migrazioni del database e il seeding iniziale su Cloud SQL.
⚖️ Come eseguire le migrazioni nei container: due strategie
Negli ambienti cloud containerizzati, esistono due approcci comuni per l'esecuzione delle migrazioni dei database:
- Opzione A: automatica all'interno di Docker Entrypoint (
entrypoint.sheseguedb:migrateall'avvio del container)- Vantaggi:completamente automatizzato; gli sviluppatori non devono ricordarsi di eseguire un comando.
- Svantaggi:pericoloso nella scalabilità automatica serverless. Quando Cloud Run esegue lo scale up fino a 10 istanze contemporaneamente in caso di traffico elevato, 10 container competono per eseguire le migrazioni contemporaneamente, causando il blocco del database, race condition dello schema o timeout di avvio.
- Opzione B: esplicita tramite Cloud Run Job (la nostra scelta architetturale)
- Vantaggi:controllo completo, sicurezza transazionale e nessuna condizione di competizione. Viene eseguito come attività di container dedicata una tantum completamente separata dalla gestione del traffico web. Se una migrazione non riesce, la pubblicazione web non viene interessata.
- Svantaggi:richiede l'attivazione di un job CLI o di un passaggio CI/CD prima del deployment.
Scegliamo l'opzione B per la massima affidabilità della produzione.
⚠️ Critico: DATABASE_URL formato e rigore RFC 3986 di Ruby 3.4.5
Cloud Run Jobs si connette a Cloud SQL utilizzando il proxy socket Unix nativo (/cloudsql/PROJECT:REGION:INSTANCE). DATABASE_URL deve utilizzare il formato con tre barre:
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: l'app utilizza 4 database (principale, coda, cache, cavo) per database.yml. Tutte e quattro le variabili di ambiente DATABASE_*_URL devono puntare alla stessa istanza Cloud SQL, altrimenti le tabelle Solid Queue/Cache/Cable non verranno create.
Dopo db:prepare, carica gli schemi Solid Queue/Cache/Cable:
# 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
Nella figura sopra, puoi vedere un possibile output di
gcloud run jobs executions describe $JOBNAME
Console Google Cloud Run Jobs che mostra
rails-migrate
execution Succeeded
4. Quarto deployment: deployment di Cloud Run multiconteiner tramite Docker Compose
Ora implementa lo stack di produzione a tre container originale (web, worker, cloudsql-proxy) direttamente da 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 Google Cloud Run: scheda "Container" che mostra tutti e tre i container collaterali (
cloudsql-proxy
,
web
,
worker
)
💡 Curiosità dell'interfaccia utente / Bug della console: nel menu a discesa della console, potresti notare che Cloud Run etichetta la prima voce come Ingress container: cloudsql-proxy. Non preoccuparti. Si tratta di un semplice problema visivo dell'interfaccia utente nella console Google Cloud (monitorato in #149) causato dall'ordinamento delle dipendenze dei container. In realtà, web è l'unico container in ascolto sulla porta HTTP 8080 e gestisce il 100% del traffico web in entrata, mentre cloudsql-proxy gestisce rigorosamente il traffico PostgreSQL privato sulla porta 5432.
5. ✨ Il momento wow: zero avvisi e persistenza completa nel cloud.
Fai un respiro profondo e ammira il tuo schermo: tutti i banner di avviso sono scomparsi! 🪄
Il blog su Rails 8 di produzione: UI incontaminata, nessun banner di avviso e stato Cloud SQL persistente.
L'applicazione è ora ufficialmente di livello di produzione e cloud-native:
- Supportato da Google Cloud SQL (PostgreSQL) gestito tramite tunneling mTLS sicuro.
- I contenuti multimediali vengono archiviati in modo sicuro in Google Cloud Storage privato con la firma dei blob IAM.
- Nessun avviso di spazio di archiviazione effimero e nessun avviso di job bloccati.
Apri l'URL di Cloud Run:
- Esamina i badge di telemetria:
[CLOUD PERSISTENT 🐘 ☁️]è verificato nel seed del database.- I badge Google Cloud Run e GCS nel piè di pagina sono verdi.
- Il banner di avviso relativo ai job bloccati non è più disponibile.
- Creare post e commenti del blog.
- Riavvia o esegui nuovamente il deployment tutte le volte che vuoi: i tuoi dati, post, commenti e asset sopravvivono per sempre in Cloud SQL e GCS.
6. Convalida automatizzata del passaggio 6
Verifica la configurazione multi-container:
just workshop-eval 6
9. Passaggio 7: pipeline di AI generativa, Podcastifier e la caccia al tesoro di GCS 🏴☠️
Con Solid Queue in esecuzione in un container dedicato e Google Cloud Storage attivo, possiamo scatenare l'AI generativa asincrona.
1. Il generatore di copertine vintage NanoBanana (pipeline di AI generativa pronta all'uso)

Buone notizie: questa funzionalità è già completamente implementata nel codebase. Non devi scrivere alcun codice: il tuo compito qui è attivare e osservare una pipeline cloud GenAI asincrona in azione.
🧠 Come funziona dietro le quinte
- Autenticazione:non sono necessarie chiavi API. Utilizza automaticamente le credenziali predefinite dell'applicazione (
roles/aiplatform.usersu Vertex AI) tramite il service account di runtime Cloud Run. In alternativa, se viene fornitoGEMINI_API_KEY, chiama direttamente l'API Gemini. - La pipeline:quando viene creato un articolo senza un'immagine di copertina, un callback
after_commitinserisceGenerateCoverImageJobin Solid Queue. - Modello: chiede al modello multimodale di Google (Gemini 2.5 Flash Image / Imagen su Vertex AI) di generare un autentico poster di un film italiano vintage degli anni'60 ("Locandina di un film 1960") con una banana cameo e un rubino lucido a forma di "8".
- Filigrana di provenienza dinamica:utilizza
libvipsper stampare dinamicamente un badge visivo nell'angolo (timbro a forma di nuvola colorata su GCS). - Turbo Stream Hot-Swap: quando l'immagine viene allegata,
Turbo::StreamsChannel.broadcast_refresh_to(post)aggiorna il browser del lettore in tempo reale senza ricaricare manualmente la pagina.

📐 Visualizza l'origine del diagramma di sequenza 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!
🧪 Prova le live!
Puoi attivarlo in due modi:
- Opzione A (interfaccia utente web): fai clic su Nuovo post, inserisci un titolo come "Architettura serverless con Ruby on Rails 8", scrivi dei contenuti e lascia vuoto l'input del file dell'immagine di copertina. Fai clic su Crea post.
- Opzione B (CLI Runner): esegui dal terminale:
bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
Attendi circa 15-30 secondi: guarda i log del worker Solid Queue in Cloud Run. Il poster vintage verrà visualizzato direttamente nel tuo post.
📸 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. La quest di Bilingual Podcastifier (esercizio di programmazione pratico di antigravità)
⚠️ Avviso importante:a differenza del generatore di copertine sopra, questa funzionalità NON è ancora implementata.
Se apri blog/app/jobs/podcastifier_job.rb, troverai uno stub di scaffolding vuoto:
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
La tua missione in questo esercizio pratico è programmare in coppia con Google Antigravity per completare questa funzionalità:
- Prompt Antigravity:"Aiutami a implementare PodcastifierJob in
app/jobs/podcastifier_job.rb. Deve recuperare il post, tradurre o sintetizzare una panoramica audio in italiano utilizzando Google Cloud Text-to-Speech (voce "it-IT-Wavenet-A" con codice lingua "it-IT" tramite credenziali ADC) e allegare il file audio apost.podcast_audio_itutilizzando ActiveStorage." - Aggiungi player alla visualizzazione:in
app/views/posts/show.html.erb, aggiungi un playerHTML5 quando@post.podcast_audio_it.attached?. - Attiva sintesi:una volta salvato, Solid Queue eseguirà il lavoro di sintesi vocale in modo asincrono in background.

🎧 Esempi di output di Podcastifier:
💡 Hai bisogno di aiuto o non riesci ad andare avanti? Una soluzione di riferimento completa e testata è disponibile nel ramo solutions/podcastifier. Puoi esaminarlo in qualsiasi momento con git diff solutions/podcastifier.
3. [FACOLTATIVO] 🏴☠️ La caccia al tesoro di GCS (recupero dei blob della console)
ℹ️ Controllo prerequisiti: locale
cloud-sql-proxy
CLI Required Questo esercizio facoltativo connette il tuo laptop locale direttamente a Google Cloud SQL tramite un tunnel mTLS criptato. Verifica se hai installato il proxy CLI:
which cloud-sql-proxy
- Se è installato, continua a leggere.
- Se NON è installato e vuoi completare questa missione: installalo con:
# 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 - Altrimenti: puoi passare direttamente alla convalida automatica del passaggio 7.
Ricordi la foto che hai caricato nel passaggio 4 prima che il riavvio del container cancellasse il database SQLite temporaneo? Il file immagine è ancora al sicuro nel tuo bucket GCS privato come "blob orfano".
Utilizziamo la console Rails per recuperarlo e allegarlo a un post di 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
All'interno della console 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
Aggiorna il blog: la foto caricata durante lo Stateless Shock del passaggio 4 viene ripristinata e allegata in modo permanente al post di Cloud SQL.
4. Convalida automatizzata del passaggio 7
Verifica i job e gli asset di AI generativa:
just workshop-eval 7
10. Passaggio 8: scegli la tua avventura / missioni avanzate 🏆
Ora che hai acquisito dimestichezza con l'architettura di riferimento canonica, scegli la tua missione finale.
🛡️ Quest 1: Zero-Trust Google IAP (Identity-Aware Proxy)
- Difficoltà: media (sicurezza aziendale)
- L'obiettivo:proteggere l'applicazione Cloud Run in modo che solo i tuoi account Google / Gmail verificati possano accedervi, eliminando completamente l'accesso con password.
- Come funziona:
- Un bilanciatore del carico delle applicazioni HTTPS esterno termina Google OAuth e verifica l'identità prima che il traffico raggiunga Cloud Run.
- Google inoltra le intestazioni dell'identità verificata (
X-Goog-Authenticated-User-Email). - Rails esegue automaticamente l'accesso con l'identità Google verificata tramite
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 - Attiva il modulo Terraform in
iac/iap.tfconenable_iap = truee specifica i tuoi Account Google consentiti:iap_allowed_users = [var.google_cloud_account]

📊 Quest 2: Telemetria SRE di produzione e Cloud Logging
- Difficoltà:media (osservabilità)
- Obiettivo:trasmetti in streaming i log delle applicazioni JSON strutturati con ID di correlazione delle tracce direttamente a Google Cloud Logging e rileva le eccezioni di produzione in tempo reale con Cloud Error Reporting.
- Come funziona:
- Configura
blog/config/environments/production.rbper generare log JSON strutturati con etichette di traccia Google Cloud. - Attiva un'eccezione di test intenzionale e guarda Google Cloud Error Reporting raggruppare e inviarti una notifica istantaneamente.
- Configura
📸 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 Ricerca semantica e RAG di Gemini
- Difficoltà: difficile (progetto finale sull'AI generativa)
- Obiettivo:cercare articoli in modo concettuale utilizzando i vector embedding archiviati in PostgreSQL su Cloud SQL.
- Come funziona:
- Abilita l'estensione
vectorsull'istanza Cloud SQL PostgreSQL:CREATE EXTENSION IF NOT EXISTS vector; - Aggiungi il gem
neighborablog/Gemfilee genera text embedding con Gemini (text-embedding-004) suPost#after_save. - Esegui query di distanza del coseno (
<=>) per potenziare una barra di ricerca semantica con Turbo Streams.
- Abilita l'estensione
📸 TODO(riccardo): add screenshot of the live blog semantic search bar returning conceptual matches with cosine similarity scores
⚡ Missione 4: assistente per l'audit SEO e del rendimento
- Difficoltà:facile / divertente (esperienza di sviluppo)
- L'obiettivo: esamina le metriche Core Web Vitals, Largest Contentful Paint (LCP) e gli indici di leggibilità Flesch-Kincaid / Fog utilizzando gli strumenti Antigravity e Speedgrapher.
📸 TODO(riccardo): add screenshot of Speedgrapher / Lighthouse SEO and Core Web Vitals audit summary
🤔 Curiosity Box: Getting a Live Rails Console on Cloud Run
💡 La domanda:nelle VM tradizionali o nelle piattaforme PaaS in stile Heroku, gli sviluppatori utilizzano comunemente SSH in un'istanza in esecuzione o heroku run rails console per ispezionare o correggere i dati di produzione in tempo reale. Qual è l'equivalente nativo di Cloud Run e come si esegue l'hardening?
Perché
gcloud run exec
non esiste per i servizi Cloud Run
Cloud Run è una piattaforma serverless: il tuo container può essere eseguito come zero, una o centinaia di istanze identiche, senza identità stabile o shell indirizzabile. Questo è fondamentalmente diverso da Kubernetes (kubectl exec), dove un pod specifico ha un socket denominato a cui puoi collegarti.
# ❌ 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
I quattro pattern nativi di GCP
Pattern | Comando | Interattivo? | Sicurezza |
1. Proxy locale + DB remoto |
| ✅ Completo | 🔒 mTLS del proxy di autenticazione Cloud SQL |
2. Job Cloud Run (una tantum) |
| ❌ Solo script | 🔒 Ruolo IAM richiesto |
3. Gemma della console web + IAP | Browser → | ✅ Completo | 🛡️ IAP Zero-Trust |
4. Cloud Shell + Proxy | Cloud Shell → | ✅ Completo | 🔒 Identità Google |
Pattern 1 (consigliato per l'ispezione dei dati): esegui una console Rails locale che comunica direttamente con Cloud SQL PostgreSQL tramite il Cloud SQL Auth Proxy:
# 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
Ottieni la piena interattività IRB con zero esposizione alla rete: il proxy Cloud SQL utilizza mTLS su un socket di dominio Unix o loopback.
Pattern 3 (console web + IAP Zero-Trust, il percorso del browser protetto):
La web-console gem fornisce una sessione IRB basata su browser. Senza protezione, si tratta di una vulnerabilità di sicurezza critica, quindi non esporla mai pubblicamente. Tuttavia, se combinata con Google IAP (Quest 1), la superficie di attacco si riduce: solo le identità Google verificate nella tua lista consentita possono raggiungere l'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
⚠️ Il pattern 3 richiede l'abilitazione di Quest 1 (IAP). Senza, /rails/console su un URL pubblico equivale a lasciare aperta la sala server. Con IAP, diventa un potente strumento per gli operatori rafforzato da Zero Trust di livello Google.
Chiedi ad Antigravity: "Mostrami come correre
rails console --sandbox
in modo sicuro sul mio database di produzione Cloud SQL utilizzando il Cloud SQL Auth Proxy".
🏆 Diploma su The Hive: prova di lavoro con convalida del supervisore
Una volta completata la missione che hai scelto, richiedi il trofeo di laurea del passaggio 8 🏆 nella classifica dell'alveare:
- Invia la tua missione su GitHub:
- Apri un nuovo problema nel repository ufficiale: github.com/palladius/rails8-app-on-gcp/issues/new
- Formato del titolo:
🎓 [Step 8 Completed]: - Nel corpo, includi una breve descrizione di ciò che hai realizzato e un link al deployment live di Cloud Run.
- Collega l'app Cloud Run al problema:
- Prendi nota del numero del problema (ad es.
88o dell'URL completohttps://github.com/palladius/rails8-app-on-gcp/issues/88). - Inserisci la variabile di ambiente
STEP_8_GHInel tuo servizio Cloud Run:gcloud run services update blog \ --region europe-west1 \ --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER> - Verifica che l'endpoint
/status.jsonora riporti la tua missione: Dovresti visualizzare: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" }
- Prendi nota del numero del problema (ad es.
- Ottieni la revisione del supervisore e sblocca il trofeo d'oro:
- Controlla la classifica dell'alveare: la tua riga ora mostrerà un badge ambra:
⏳ GHI #XX review pending. - Chiedi ai responsabili del workshop (@palladius, @emilianodellacasa o @ricc) di ispezionare la tua implementazione.
- Non appena un supervisore commenta
LGTMil tuo problema, The Hive aumenterà automaticamente i tuoi progressi a 8/8 🏆 con un badge dorato/viola brillante e un trofeo permanente che rimanda direttamente al tuo problema di progetto finale.
- Controlla la classifica dell'alveare: la tua riga ora mostrerà un badge ambra:
11. Conclusione e pulizia 🎓
Complimenti! 🎉
Hai creato ed eseguito il deployment di un'applicazione Rails 8 di livello di produzione e pronta per l'uso aziendale su Google Cloud:
- Zero-Branch Progression: ha acquisito la padronanza dei workflow moderni con le sovrapposizioni Time-Machine su
main. - Cloud Storage: spazio di archiviazione di oggetti privato e scalabile con firma dei blob IAM (
iam: true). - Cloud SQL: PostgreSQL gestito protetto con tunnel mTLS del proxy di autenticazione Cloud SQL.
- Secret Manager:nessuna perdita di credenziali in testo normale o di file
.env. - Cloud Run Multi-Container:file collaterali isolati di Puma web, Solid Queue worker e proxy.
- AI generativa: generazione asincrona di poster vintage e sintesi di podcast TTS con Vertex AI.
- Missioni:IAP Zero Trust, osservabilità SRE e incorporamenti pgvector.
🧹 Pulizia delle risorse
Per evitare addebiti continui dopo aver completato il workshop, assicurati di liberare spazio dalle risorse Google Cloud. Scegli l'approccio più adatto al tuo ambiente:
Opzione 1: Terraform Teardown (consigliata / predefinita 🟢)
Utilizza questa opzione se utilizzi un progetto Google Cloud esistente o condiviso in cui si trovano altre risorse o dati. Distrugge chirurgicamente solo le risorse di cui è stato eseguito il provisioning durante questo workshop (bucket Cloud Run, Cloud SQL, Secret Manager, GCS), lasciando intatto il resto del progetto.
cd iac
terraform destroy -auto-approve
Opzione 2: eliminazione totale del progetto (non lasciare tracce 🌪️)
Utilizza questa opzione se hai creato un progetto workshop dedicato (ad es. utilizzando crediti workshop o una sandbox) e vuoi garantire che non rimanga alcuna traccia, inclusi log, metadati, service account e link di fatturazione.
gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet