Artigo 50 — Go na nuvem: deploy no Railway, Fly.io e Google Cloud Run
Curso: Dominando Go em 1 Ano Prof. Ricardo Matos Módulo 9 — Deploy, Cloud e Carreira
Três plataformas, três filosofias
O artigo anterior produziu um binário estático e uma imagem Docker otimizada. Agora é hora de colocá-los em produção. Três plataformas dominam o mercado de deploy de aplicações Go containerizadas, cada uma com filosofia diferente:
Railway — zero configuração, deploy por push, ideal para projetos pessoais e startups que querem ir ao ar rapidamente sem pensar em infraestrutura.
Fly.io — infraestrutura global com controle fino, deploys na borda, banco de dados gerenciado, excelente para APIs que precisam de baixa latência em múltiplas regiões.
Google Cloud Run — serverless containerizado, escala para zero quando sem tráfego, integração nativa com o ecossistema GCP, ideal para cargas variáveis e aplicações corporativas.
Railway: do repositório ao ar em minutos
Railway detecta automaticamente projetos Go e os compila sem configuração adicional.
Deploy via CLI
# Instalar CLI
curl -fsSL https://railway.app/install.sh | sh
# Login
railway login
# Criar projeto
railway new taskapi
# Provisionar banco PostgreSQL
railway add --plugin postgresql
# Configurar variáveis de ambiente
railway variables set JWT_SECRET=$(openssl rand -hex 32)
railway variables set APP_ENV=producao
railway variables set LOG_LEVEL=info
# Ver variáveis (DATABASE_URL é criada automaticamente pelo plugin)
railway variables
# Deploy
railway up
# Verificar logs
railway logs --tail
# Status do deploy
railway status
Arquivo de configuração railway.toml
# railway.toml
[build]
builder = "dockerfile"
dockerfilePath = "Dockerfile"
[deploy]
healthcheckPath = "/saude"
healthcheckTimeout = 30
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 3
[[deploy.environmentVariables]]
name = "PORT"
value = "$PORT" # Railway injeta PORT automaticamente
Executando migrações no Railway
// cmd/api/main.go — executa migrações na inicialização
func executarMigracoes(databaseURL string) error {
m, err := migrate.New(
"file://migrations",
databaseURL,
)
if err != nil {
return fmt.Errorf("criar migrator: %w", err)
}
defer m.Close()
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
return fmt.Errorf("executar migrações: %w", err)
}
version, dirty, _ := m.Version()
slog.Info("migrações executadas", "versão", version, "dirty", dirty)
return nil
}
GitHub Actions para Railway
# .github/workflows/deploy-railway.yml
name: Deploy Railway
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy no Railway
uses: bervProject/railway-deploy@main
with:
railway_token: ${{ secrets.RAILWAY_TOKEN }}
service: taskapi
Fly.io: controle e distribuição global
Fly.io oferece VMs reais (Firecracker microVMs), não containers abstratos. Isso significa acesso a recursos como volumes persistentes, redes privadas e deploy em múltiplas regiões simultaneamente.
Configuração inicial
# Instalar flyctl
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Criar e configurar a aplicação
fly launch \
--name taskapi \
--region gru \
--no-deploy
# Banco PostgreSQL gerenciado pelo Fly
fly postgres create \
--name taskapi-db \
--region gru \
--initial-cluster-size 1 \
--vm-size shared-cpu-1x \
--volume-size 10
# Associa o banco à aplicação
# Isso cria automaticamente DATABASE_URL nos secrets
fly postgres attach taskapi-db --app taskapi
# Segredos da aplicação
fly secrets set \
JWT_SECRET="$(openssl rand -hex 32)" \
APP_ENV="producao" \
LOG_LEVEL="info" \
--app taskapi
fly.toml completo
# fly.toml
app = "taskapi"
primary_region = "gru"
[build]
dockerfile = "Dockerfile"
[build.args]
VERSAO = "1.0.0"
[env]
PORT = "8080"
APP_ENV = "producao"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true # para máquinas sem tráfego
auto_start_machines = true # reinicia quando tráfego chega
min_machines_running = 1 # sempre 1 instância mínima
[http_service.concurrency]
type = "requests"
hard_limit = 1000
soft_limit = 800
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
method = "GET"
path = "/saude"
timeout = "5s"
tls_skip_verify = false
[[vm]]
memory = "256mb"
cpu_kind = "shared"
cpus = 1
# Deploy em múltiplas regiões
[[regions]]
name = "gru" # São Paulo
count = 2
# Volume para dados persistentes (se necessário)
[[mounts]]
source = "taskapi_data"
destination = "/data"
Deploy e operações
# Primeiro deploy
fly deploy --app taskapi
# Deploy com estratégia rolling (sem downtime)
fly deploy --strategy rolling --app taskapi
# Escalar horizontalmente
fly scale count 3 --app taskapi
# Escalar verticalmente
fly scale vm shared-cpu-2x --memory 512 --app taskapi
# SSH na instância
fly ssh console --app taskapi
# Logs em tempo real
fly logs --app taskapi
# Monitoramento
fly status --app taskapi
fly checks list --app taskapi
# Executar comando ad-hoc (ex: migração manual)
fly ssh console --app taskapi -C "/taskapi migrate"
Deploy multi-região
# Adicionar região secundária
fly regions add iad # Northern Virginia
fly regions add lhr # London
fly scale count 1 --region iad
fly scale count 1 --region lhr
O Fly.io roteia automaticamente usuários para a região mais próxima, reduzindo latência globalmente.
Google Cloud Run: serverless com escala automática
Cloud Run executa containers de forma serverless — você paga apenas pelo tempo de CPU consumido durante requisições. Escala para zero automaticamente quando sem tráfego, e de zero para centenas de instâncias em segundos.
Configuração do GCP
# Instalar gcloud CLI
# https://cloud.google.com/sdk/docs/install
# Autenticar
gcloud auth login
gcloud auth configure-docker us-central1-docker.pkg.dev
# Criar projeto
gcloud projects create taskapi-prod --name="Task API Produção"
gcloud config set project taskapi-prod
# Habilitar APIs necessárias
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
sqladmin.googleapis.com \
secretmanager.googleapis.com \
artifactregistry.googleapis.com
# Criar repositório de imagens
gcloud artifacts repositories create taskapi \
--repository-format=docker \
--location=us-central1 \
--description="Task API Docker images"
Cloud SQL: PostgreSQL gerenciado
# Criar instância Cloud SQL
gcloud sql instances create taskapi-db \
--database-version=POSTGRES_16 \
--tier=db-f1-micro \
--region=us-central1 \
--storage-size=10GB \
--storage-auto-increase \
--backup-start-time=03:00 \
--availability-type=zonal
# Criar banco e usuário
gcloud sql databases create taskapi --instance=taskapi-db
gcloud sql users create taskapi_user \
--instance=taskapi-db \
--password="$(openssl rand -hex 16)"
Secret Manager para variáveis sensíveis
# Armazenar segredos no Secret Manager
echo -n "sua-jwt-secret-aqui" | \
gcloud secrets create jwt-secret --data-file=-
echo -n "postgres://taskapi_user:senha@/taskapi?host=/cloudsql/project:region:instance" | \
gcloud secrets create database-url --data-file=-
# Criar service account para o Cloud Run
gcloud iam service-accounts create taskapi-sa \
--display-name="Task API Service Account"
# Permissões para acessar os segredos
gcloud secrets add-iam-policy-binding jwt-secret \
--member="serviceAccount:taskapi-sa@taskapi-prod.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
gcloud secrets add-iam-policy-binding database-url \
--member="serviceAccount:taskapi-sa@taskapi-prod.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
# Permissão para conectar ao Cloud SQL
gcloud projects add-iam-policy-binding taskapi-prod \
--member="serviceAccount:taskapi-sa@taskapi-prod.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
Build e deploy no Cloud Run
# Build via Cloud Build (sem Docker local)
gcloud builds submit \
--tag us-central1-docker.pkg.dev/taskapi-prod/taskapi/api:latest \
.
# Deploy no Cloud Run
gcloud run deploy taskapi \
--image us-central1-docker.pkg.dev/taskapi-prod/taskapi/api:latest \
--region us-central1 \
--platform managed \
--allow-unauthenticated \
--service-account taskapi-sa@taskapi-prod.iam.gserviceaccount.com \
--set-secrets="JWT_SECRET=jwt-secret:latest,DATABASE_URL=database-url:latest" \
--add-cloudsql-instances taskapi-prod:us-central1:taskapi-db \
--min-instances 0 \
--max-instances 10 \
--concurrency 1000 \
--cpu 1 \
--memory 256Mi \
--timeout 30s \
--port 8080
service.yaml: configuração declarativa
# cloudrun/service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: taskapi
labels:
cloud.googleapis.com/location: us-central1
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
run.googleapis.com/cloudsql-instances: "taskapi-prod:us-central1:taskapi-db"
run.googleapis.com/startup-cpu-boost: "true"
spec:
serviceAccountName: taskapi-sa@taskapi-prod.iam.gserviceaccount.com
containerConcurrency: 1000
timeoutSeconds: 30
containers:
- image: us-central1-docker.pkg.dev/taskapi-prod/taskapi/api:latest
ports:
- containerPort: 8080
env:
- name: APP_ENV
value: producao
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: jwt-secret
key: latest
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-url
key: latest
resources:
limits:
cpu: "1"
memory: 256Mi
livenessProbe:
httpGet:
path: /saude
initialDelaySeconds: 10
periodSeconds: 30
startupProbe:
httpGet:
path: /saude
failureThreshold: 3
periodSeconds: 10
# Aplicar configuração declarativa
gcloud run services replace cloudrun/service.yaml --region us-central1
# Listar revisões
gcloud run revisions list --service taskapi --region us-central1
# Traffic splitting — deploy gradual
gcloud run services update-traffic taskapi \
--region us-central1 \
--to-revisions taskapi-00002-abc=10,taskapi-00001-xyz=90
# Logs
gcloud logging read \
"resource.type=cloud_run_revision AND resource.labels.service_name=taskapi" \
--limit 50 \
--format json
GitHub Actions para Cloud Run
# .github/workflows/deploy-cloudrun.yml
name: Deploy Cloud Run
on:
push:
branches: [main]
env:
PROJECT_ID: taskapi-prod
REGION: us-central1
SERVICE: taskapi
IMAGE: us-central1-docker.pkg.dev/taskapi-prod/taskapi/api
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Autenticar no GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
- name: Configurar gcloud
uses: google-github-actions/setup-gcloud@v2
- name: Configurar Docker
run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev
- name: Build e push
run: |
docker build \
--build-arg VERSAO=${{ github.sha }} \
--build-arg COMMIT=${{ github.sha }} \
-t ${{ env.IMAGE }}:${{ github.sha }} \
-t ${{ env.IMAGE }}:latest \
.
docker push ${{ env.IMAGE }}:${{ github.sha }}
docker push ${{ env.IMAGE }}:latest
- name: Deploy
run: |
gcloud run deploy ${{ env.SERVICE }} \
--image ${{ env.IMAGE }}:${{ github.sha }} \
--region ${{ env.REGION }} \
--quiet
- name: URL do serviço
run: |
gcloud run services describe ${{ env.SERVICE }} \
--region ${{ env.REGION }} \
--format "value(status.url)"
Comparativo das três plataformas
| Aspecto | Railway | Fly.io | Cloud Run |
|---|---|---|---|
| Configuração inicial | Mínima | Média | Alta |
| Custo (baixo tráfego) | ~$5/mês | ~$5/mês | ~$0 (escala p/ zero) |
| Controle de infraestrutura | Baixo | Alto | Médio |
| Banco de dados gerenciado | Sim | Sim | Cloud SQL |
| Multi-região | Não | Sim | Sim |
| Escala para zero | Não | Opcional | Sim |
| Cold start | N/A | Rápido (<100ms) | Médio (<500ms) |
| SLA produção | 99.5% | 99.9% | 99.95% |
| Melhor para | Prototipagem | APIs globais | Escala variável |
Saúde e observabilidade em produção
Independentemente da plataforma, um endpoint de saúde robusto é essencial:
type StatusSaude struct {
Status string `json:"status"`
Versao string `json:"versao"`
Uptime string `json:"uptime"`
Banco string `json:"banco"`
Checks map[string]string `json:"checks"`
}
var inicioApp = time.Now()
func handlerSaude(db *pgxpool.Pool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancelar := context.WithTimeout(r.Context(), 3*time.Second)
defer cancelar()
checks := make(map[string]string)
status := "ok"
// Verifica banco de dados
if err := db.Ping(ctx); err != nil {
checks["banco"] = "falha: " + err.Error()
status = "degradado"
} else {
checks["banco"] = "ok"
}
httpStatus := http.StatusOK
if status != "ok" {
httpStatus = http.StatusServiceUnavailable
}
resposta := StatusSaude{
Status: status,
Versao: Versao,
Uptime: time.Since(inicioApp).Round(time.Second).String(),
Checks: checks,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatus)
json.NewEncoder(w).Encode(resposta)
}
}
Resumo do que foi coberto
Este artigo apresentou deploy em nuvem com três plataformas: Railway com zero configuração via CLI e GitHub Actions, Fly.io com fly.toml detalhado, multi-região e operações avançadas, e Google Cloud Run com Secret Manager, Cloud SQL, service.yaml declarativo e pipeline CI/CD com Workload Identity Federation. O comparativo entre plataformas ajuda a escolher a mais adequada para cada cenário. O próximo artigo explora gRPC e Protocol Buffers.
Referências e leituras complementares
-
Railway Docs — Documentação oficial do Railway. https://docs.railway.app
-
Fly.io Docs — Documentação completa do Fly.io. https://fly.io/docs/
-
Cloud Run Docs — Documentação oficial do Google Cloud Run. https://cloud.google.com/run/docs
-
Cloud Run com Go — Guia específico para Go no Cloud Run. https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-go-service
-
Workload Identity Federation — Autenticação segura para GitHub Actions. https://cloud.google.com/blog/products/identity-security/enabling-keyless-authentication-from-github-actions
-
Twelve-Factor App — Metodologia de aplicações cloud-native. https://12factor.net
Próximo artigo: Artigo 51 — gRPC e Protocol Buffers: comunicação eficiente entre serviços**
you asked
Sim