Introdução: Por Que Go é Ideal para APIs em Produção
Go é uma linguagem compilada, estaticamente tipada e com foco em concorrência. Quando falamos de deploy em produção, você precisa de uma linguagem que seja rápida, confiável e fácil de operar. Go atende todos esses requisitos. Um único binário, sem dependências externas de runtime, torna a implantação em VPS extremamente simples. Além disso, a comunidade Go tem um ecossistema maduro com frameworks como Gin, Echo e Chi que facilitam a construção de APIs robustas.
O objetivo deste artigo é guiá-lo através de três cenários reais de deploy: em uma VPS tradicional, em um cluster Kubernetes e automatizando tudo com GitHub Actions. Você aprenderá não apenas como fazer, mas por que cada abordagem faz sentido em diferentes contextos.
Entendendo a API Go que Vamos Deployar
Estrutura Básica de uma API Go
Antes de falar de deploy, você precisa entender o que estamos implantando. Vou usar o framework Echo, que é lightweight e production-ready. Uma API simples com autenticação básica, logging e tratamento de erros será nosso exemplo.
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
// Inicializar Echo
e := echo.New()
// Middleware de logging
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "[${time_rfc3339}] ${method} ${uri} ${status} ${latency_human}\n",
}))
// Middleware de recuperação de panics
e.Use(middleware.Recover())
// Rota de saúde (importante para healthchecks)
e.GET("/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "ok",
"time": time.Now().UTC().String(),
})
})
// Rota de produtos
e.GET("/api/products", getProducts)
e.POST("/api/products", createProduct)
// Obter porta da variável de ambiente ou usar padrão
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Server starting on port %s\n", port)
if err := e.Start(":" + port); err != nil {
log.Fatalf("Server error: %v\n", err)
}
}
func getProducts(c echo.Context) error {
products := []Product{
{ID: 1, Name: "Notebook", Price: 2500.00},
{ID: 2, Name: "Mouse", Price: 50.00},
}
return c.JSON(http.StatusOK, products)
}
func createProduct(c echo.Context) error {
p := new(Product)
if err := c.BindJSON(&p); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid JSON",
})
}
return c.JSON(http.StatusCreated, p)
}
Este código é minimalista, mas representa uma API real: tem rota de health check (essencial para monitoramento), logging estruturado, middleware de recuperação, e usa variáveis de ambiente. Essas práticas são fundamentais quando você vai para produção.
Preparando o Projeto para Produção
Você precisa de um arquivo go.mod e go.sum versionados. Crie um Dockerfile para containerizar a aplicação, pois isso facilita deploy tanto em VPS quanto em Kubernetes:
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o api-go .
# Runtime stage
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/api-go .
EXPOSE 8080
ENV PORT=8080
CMD ["./api-go"]
Essa abordagem multi-stage reduz o tamanho da imagem final. O primeiro stage compila o Go (que tira o binário), o segundo stage usa apenas Alpine Linux com o binário pronto. Isso resulta em imagens de ~20MB em vez de 800MB.
Deploy em VPS: Simplicidade e Controle Total
Configuração do Servidor
Um deploy em VPS é a abordagem mais simples. Você terá controle total, menos abstrações, e custos previsíveis. A maioria das VPS oferece Ubuntu 22.04 LTS, que é estável e bem suportada. O fluxo é: build local → upload binário → execute com um gerenciador de processo.
Primeiro, provisionando a VPS, você precisa de SSH configurado, firewall adequado e um usuário não-root. Instale Go ou Docker (se optar por container):
#!/bin/bash
# Script de setup inicial da VPS
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git ca-certificates
# Instalar Docker (opcional mas recomendado)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Criar diretório para a aplicação
sudo mkdir -p /opt/api-go
sudo chown $USER:$USER /opt/api-go
Build e Deploy Manual
Localmente, compile seu binário Go para Linux:
# No seu computador ou no runner
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o api-go-linux .
# Upload para VPS via SCP
scp api-go-linux user@seu-vps.com:/opt/api-go/
# Conectar via SSH e executar
ssh user@seu-vps.com
cd /opt/api-go
chmod +x api-go-linux
./api-go-linux
Mas executar manualmente não é prático. Use Systemd para gerenciar o processo:
# /etc/systemd/system/api-go.service
[Unit]
Description=Go API Service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/api-go
ExecStart=/opt/api-go/api-go-linux
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Limites de recursos
LimitNOFILE=65536
LimitNPROC=65536
# Variáveis de ambiente
Environment="PORT=8080"
Environment="ENV=production"
[Install]
WantedBy=multi-user.target
Ativar e iniciar:
sudo systemctl daemon-reload
sudo systemctl enable api-go
sudo systemctl start api-go
sudo systemctl status api-go
Reverse Proxy com Nginx
Nunca exponha sua aplicação Go diretamente na porta 80/443. Use um reverse proxy. Nginx é leve e eficiente:
# /etc/nginx/sites-available/api-go
server {
listen 80;
server_name seu-dominio.com;
# Redirecionar HTTP para HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name seu-dominio.com;
ssl_certificate /etc/letsencrypt/live/seu-dominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seu-dominio.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Logs
access_log /var/log/nginx/api-go-access.log;
error_log /var/log/nginx/api-go-error.log;
# Proxy para sua API Go
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Health check endpoint
location /health {
proxy_pass http://127.0.0.1:8080/health;
access_log off;
}
}
Use Certbot para SSL gratuito com Let's Encrypt:
sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d seu-dominio.com
Por que essa abordagem? VPS é ideal para aplicações pequenas e médias. Você tem controle total, debugar é simples (logs em /var/log ou via journalctl), e não há overhead de orquestração. O custo é entre $5-20/mês.
Deploy em Kubernetes: Escalabilidade e Automação
Quando Usar Kubernetes
Kubernetes é complexo, mas resolve problemas reais em larga escala: orquestração automática de containers, escalabilidade horizontal, service discovery, rolling updates sem downtime. Se sua API precisa servir milhões de requisições e lidar com falhas automaticamente, Kubernetes é o caminho.
Vou assumir um cluster Kubernetes gerenciado (como GKE, EKS ou DigitalOcean Kubernetes). Você precisará de kubectl instalado e acesso ao cluster.
Containerizar a Aplicação
Você já tem o Dockerfile. Agora build e push para um registry:
# Build da imagem
docker build -t seu-usuario/api-go:1.0.0 .
# Login no Docker Hub (ou seu registry)
docker login
# Push
docker push seu-usuario/api-go:1.0.0
Manifest Kubernetes
Crie arquivos de configuração. Primeiro, um Deployment:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-go
labels:
app: api-go
spec:
replicas: 3 # 3 instâncias sempre rodando
selector:
matchLabels:
app: api-go
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # Zero downtime
template:
metadata:
labels:
app: api-go
spec:
containers:
- name: api-go
image: seu-usuario/api-go:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
env:
- name: PORT
value: "8080"
- name: ENV
value: "production"
# Health checks
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# Limites de recursos
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# Graceful shutdown
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
Depois, um Service para expor a aplicação:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: api-go
labels:
app: api-go
spec:
type: ClusterIP # Interno, exposto via Ingress
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: api-go
E um Ingress para roteamento HTTP/HTTPS:
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-go
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
ingressClassName: nginx
tls:
- hosts:
- seu-dominio.com
secretName: api-go-tls
rules:
- host: seu-dominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-go
port:
number: 80
Aplicando no Cluster
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
# Verificar status
kubectl get pods
kubectl get svc
kubectl get ingress
# Ver logs de um pod
kubectl logs -f deployment/api-go
# Acessar shell de um pod (para debug)
kubectl exec -it <pod-name> -- sh
Atualizando a Aplicação
Quando você tem uma nova versão, atualize a tag da imagem no Deployment:
kubectl set image deployment/api-go api-go=seu-usuario/api-go:1.0.1 --record
# Ou edite manualmente
kubectl edit deployment api-go
Kubernetes fará um rolling update: novos pods com a nova versão são criados enquanto os antigos são gracefully desligados. Zero downtime.
Por que essa abordagem? Kubernetes é ideal quando você tem equipes maiores, múltiplas aplicações, e precisa de escalabilidade automática. Mas tem custo de complexidade: você precisa entender networking, storage, RBAC, etc.
CI/CD com GitHub Actions: Automação Completa
Por Que Automatizar?
Deploy manual é propenso a erros. GitHub Actions permite que você defina um pipeline: quando faz push para main, o código é testado, builtado, deployado automaticamente. Você dorme tranquilo.
Estrutura do Workflow
Crie um arquivo .github/workflows/deploy.yaml:
name: Build and Deploy
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
REGISTRY: docker.io
IMAGE_NAME: seu-usuario/api-go
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run Tests
run: go test -v ./...
- name: Run Linter
uses: golangci/golangci-lint-action@v3
with:
version: latest
- name: Build Binary
run: |
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -o api-go-linux .
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-vps:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to VPS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/api-go
docker pull seu-usuario/api-go:main
docker stop api-go || true
docker rm api-go || true
docker run -d \
--name api-go \
--restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-e PORT=8080 \
-e ENV=production \
seu-usuario/api-go:main
echo "Waiting for health check..."
sleep 5
curl -f http://localhost:8080/health || exit 1
echo "Deployment successful!"
deploy-kubernetes:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Update image in Deployment
run: |
kubectl set image deployment/api-go \
api-go=seu-usuario/api-go:main \
--record
- name: Wait for rollout
run: kubectl rollout status deployment/api-go --timeout=5m
- name: Health check
run: |
POD=$(kubectl get pods -l app=api-go -o jsonpath='{.items[0].metadata.name}')
kubectl exec $POD -- wget -qO- http://localhost:8080/health
Configurar Secrets
No repositório GitHub, vá em Settings → Secrets and Variables → Actions e adicione:
DOCKER_USERNAME: seu usuário Docker HubDOCKER_PASSWORD: seu token Docker HubVPS_HOST: IP ou domínio da VPSVPS_USER: usuário SSHVPS_SSH_KEY: chave privada SSH (sem passphrase)KUBE_CONFIG: arquivo kubeconfig em base64 (cat ~/.kube/config | base64)
Análise do Pipeline
O workflow acima faz:
- test: A cada push, testa o código com
go test, roda linter (golangci-lint detecta bugs e estilo ruim), builda o binário. - build: Se tudo passou e é push em
main, constrói a imagem Docker e faz push ao registry. - deploy-vps: Conecta via SSH, faz pull da nova imagem, reinicia o container com
docker run, valida com health check. - deploy-kubernetes: Atualiza o Deployment no cluster, aguarda o rollout ser concluído.
Ambos os deploys rodam em paralelo (needs: build), economizando tempo.
Tagging Automático
Para versionamento semântico, crie tags Git e o GitHub Actions pega automaticamente:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
O workflow de build está configurado para criar tags Docker automáticas baseadas em tags Git.
Por que essa abordagem? GitHub Actions reduz erros humanos, garante que apenas código testado é deployado, e permite que você desligue e o sistema continue funcionando. É essencial em qualquer operação seria.
Boas Práticas e Considerações Operacionais
Monitoramento e Logging
Sua API precisa ser observável. Implemente logging estruturado:
import "github.com/sirupsen/logrus"
var log = logrus.New()
func main() {
log.SetFormatter(&logrus.JSONFormatter{})
log.SetLevel(logrus.InfoLevel)
log.Info("Application started")
}
Em produção, agregue logs em um serviço como ELK Stack, DataDog ou CloudWatch. Seu /health endpoint é fundamental para alertas.
Versionamento e Rollback
Sempre tagueie releases. Se algo quebrar:
# VPS
docker run -d seu-usuario/api-go:v1.0.0
# Kubernetes
kubectl rollout undo deployment/api-go
Secrets e Configuração
Nunca committe secrets. Use variáveis de ambiente ou gerenciadores como:
- VPS:
/etc/api-go/.env(arquivo local, não versionado) - Kubernetes:
Secretresources - GitHub Actions: Secrets
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: api-go-secrets
type: Opaque
stringData:
DATABASE_URL: postgresql://user:pass@db:5432/api
API_KEY: seu-chave-secreta
# Usar no Deployment
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-go-secrets
key: DATABASE_URL
Testes em Produção
Sempre teste localmente antes de fazer push:
# Build e test local
go build
go test -v ./...
# Ou com Docker
docker build -t api-go:test .
docker run -p 8080:8080 api-go:test
curl http://localhost:8080/health
Conclusão
Você aprendeu três paradigmas complementares: VPS é simplicidade e controle, ideal para projetos menores onde você gerencia manualmente. Kubernetes é complexidade e escalabilidade, necessário quando você tem múltiplas instâncias e precisa de automação sofisticada. GitHub Actions é o glue que une tudo, garantindo que código testado é deployado automaticamente.
A realidade profissional é que você frequentemente usa os três: develop e test localmente, push para GitHub Actions que testa e builda, depois choose entre deployar em VPS (para staging) ou Kubernetes (para produção em escala). O importante é automatizar o máximo possível e ter um rollback plan.
Pratique: crie uma API simples, faça deploy em uma VPS trial (DigitalOcean oferece $200 de crédito), configure GitHub Actions, e integre com Kubernetes se seu cluster oferece trial (GKE, EKS têm tiers free). Erros em teste são ouro; erros em produção são disaster.