Durante muito tempo, "colocar no ar" era problema de outra equipe. Hoje, quem desenvolve frontend também precisa saber empacotar a aplicação, automatizar testes e publicar com segurança. A boa notícia: o essencial de DevOps para frontend cabe em poucas ferramentas bem entendidas.

Neste guia você vai ver Docker (empacotar), CI/CD com GitHub Actions (automatizar) e as práticas que fazem um deploy ser previsível: variáveis de ambiente, versões, monitoramento e rollback.

O que é DevOps, na prática

DevOps é a combinação de cultura e automação para que o caminho entre um commit e a produção seja curto, repetível e seguro. Para quem faz frontend, isso significa:

  • Build reprodutível: o mesmo código gera o mesmo artefato em qualquer máquina.
  • Integração contínua (CI): cada push roda lint, testes e build automaticamente.
  • Entrega contínua (CD): o que passou pela CI pode ir para produção sem passos manuais.
  • Observabilidade: saber quando algo quebrou antes de o usuário reclamar.
Texto
commit → CI (lint, testes, build) → artefato (imagem Docker) → deploy → monitoramento
   ▲                                                                        │
   └──────────────────────── feedback rápido ◀───────────────────────────────┘

Docker do jeito certo

Docker empacota a aplicação com tudo que ela precisa (runtime, dependências, arquivos) numa imagem. Uma imagem rodando é um container. Resolve o clássico "na minha máquina funciona".

Conceitos em uma tabela

ConceitoO que é
ImagemPacote imutável, construído a partir de um Dockerfile
ContainerUma instância em execução de uma imagem
CamadaCada instrução do Dockerfile gera uma camada cacheada
RegistroOnde as imagens ficam guardadas (Docker Hub, GitHub Container Registry)
VolumeDados que sobrevivem ao container
RedeComo containers conversam entre si

Dockerfile multi-stage para Next.js

O segredo de imagens pequenas é o multi-stage build: um estágio instala e compila, outro só roda o resultado. Com output: "standalone" no next.config, o Next.js gera uma pasta com apenas o necessário para executar.

next.config.ts
TypeScript
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  output: "standalone",
};
 
export default nextConfig;
Dockerfile
Dockerfile
# 1. Dependências (cacheadas enquanto o lockfile não mudar)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
# 2. Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
 
# 3. Execução: só o necessário
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
 
RUN addgroup -S app && adduser -S app -G app
 
COPY --from=builder /app/public ./public
COPY --from=builder --chown=app:app /app/.next/standalone ./
COPY --from=builder --chown=app:app /app/.next/static ./.next/static
 
USER app
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]

Por que cada detalhe importa:

  • Copiar package.json e o lockfile antes do código faz o npm ci ser reaproveitado do cache enquanto as dependências não mudam.
  • npm ci instala exatamente o que está no lockfile, ideal para builds reprodutíveis.
  • O estágio final não tem código-fonte, devDependencies nem ferramentas de build: a imagem fica bem menor e com menos superfície de ataque.
  • USER app evita rodar a aplicação como root dentro do container.

.dockerignore

Tão importante quanto o Dockerfile: evita mandar lixo (e segredos) para o build.

.dockerignore
Texto
node_modules
.next
.git
*.log
.env*
!.env.example
Dockerfile
docker-compose*.yml

Build e execução

Bash
docker build -t meu-app:1.0.0 .
docker run -d --name meu-app -p 3000:3000 --env-file .env.production meu-app:1.0.0
docker logs -f meu-app
docker exec -it meu-app sh
docker stop meu-app && docker rm meu-app

SPA estática com Nginx

Uma aplicação React/Vite que gera só arquivos estáticos não precisa de Node em produção:

Dockerfile
Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
 
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
nginx.conf
Nginx
server {
  listen 80;
  root /usr/share/nginx/html;
 
  location /assets/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
 
  location / {
    try_files $uri /index.html;   # rotas do React Router caem no index.html
  }
}

docker compose para o ambiente local

Quando a aplicação depende de banco, cache ou outros serviços, o docker compose sobe tudo com um comando:

compose.yaml
YAML
services:
  web:
    build: .
    ports:
      - "3000:3000"
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
 
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10
 
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
 
volumes:
  pgdata:
Bash
docker compose up -d        # sobe tudo em segundo plano
docker compose logs -f web  # acompanha os logs
docker compose ps           # status
docker compose down         # derruba (mantém os volumes)
docker compose down -v      # derruba e apaga os volumes

Para aprofundar em bancos de dados, veja o guia de SQL e NoSQL.

Variáveis de ambiente e segredos

  • Nunca coloque segredos no código nem no repositório. Versione só um .env.example com as chaves vazias.
  • No Next.js, variáveis com prefixo NEXT_PUBLIC_ vão para o navegador e ficam visíveis para qualquer um. Chaves de API privadas nunca levam esse prefixo.
  • Variáveis NEXT_PUBLIC_ são embutidas no build. Mudar o valor exige um novo build (ou uma estratégia de configuração em tempo de execução).
  • Em CI e hospedagem, use o cofre de segredos da plataforma (GitHub Secrets, variáveis da Vercel, AWS Secrets Manager).
  • Valide as variáveis ao iniciar a aplicação: falhar rápido com uma mensagem clara é melhor que um erro misterioso em produção.

CI com GitHub Actions

A CI roda a cada push e pull request, garantindo que nada quebrado chegue à branch principal.

.github/workflows/ci.yml
YAML
name: CI
 
on:
  push:
    branches: [main]
  pull_request:
 
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  qualidade:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
 
      - run: npm ci
      - run: npm run lint
      - run: npx tsc --noEmit
      - run: npm test -- --run
      - run: npm run build
  • concurrency cancela execuções antigas da mesma branch quando chega um push novo.
  • cache: npm reaproveita os downloads entre execuções.
  • Combine com proteção de branch no GitHub (exigir que o job passe antes do merge).

Testes end-to-end

YAML
  e2e:
    runs-on: ubuntu-latest
    needs: qualidade
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run build
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: relatorio-playwright
          path: playwright-report/

CD: publicando automaticamente

Plataformas gerenciadas

Vercel, Netlify e Cloudflare Pages conectam no repositório e fazem o deploy sozinhas: cada pull request ganha uma URL de preview, e o merge na main publica em produção. Para a maioria dos frontends, é o caminho mais simples.

Publicando uma imagem Docker

Para rodar em servidor próprio, Kubernetes, AWS ECS, Fly.io ou Railway, a pipeline gera e publica a imagem:

.github/workflows/deploy.yml
YAML
name: Deploy
 
on:
  push:
    branches: [main]
 
permissions:
  contents: read
  packages: write
 
jobs:
  imagem:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - uses: docker/setup-buildx-action@v3
 
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Marcar a imagem com o hash do commit permite saber exatamente qual código está em produção e voltar para a versão anterior trocando só a tag.

Estratégias de deploy

EstratégiaComo funcionaVantagem
RecreateDerruba a versão antiga e sobe a novaSimples, mas com indisponibilidade
RollingTroca as instâncias aos poucosSem indisponibilidade
Blue-greenDuas produções; o tráfego muda de uma vezRollback instantâneo
CanaryUma fração dos usuários recebe a versão novaRisco controlado

Monitoramento e observabilidade

Publicar é metade do trabalho; a outra metade é saber como a aplicação está se comportando.

  • Erros: ferramentas como Sentry capturam exceções do navegador e do servidor com stack trace e o contexto do usuário.
  • Desempenho real: acompanhe os Core Web Vitals (LCP, INP e CLS) dos usuários reais, não só do seu computador.
  • Disponibilidade: um monitor externo que acessa a URL a cada minuto e avisa se cair.
  • Logs estruturados: JSON com nível, rota e identificador da requisição, fáceis de filtrar.
  • Health check: uma rota como /api/health que responde rápido e confirma que as dependências estão acessíveis.
app/api/health/route.ts
TypeScript
export const dynamic = "force-dynamic";
 
export async function GET() {
  return Response.json({ status: "ok", time: new Date().toISOString() });
}

Checklist de produção

  • Build reprodutível (npm ci, lockfile versionado, versão do Node fixada).
  • Imagem multi-stage, rodando sem root, com .dockerignore.
  • Segredos fora do repositório e fora de variáveis NEXT_PUBLIC_.
  • CI com lint, checagem de tipos, testes e build a cada pull request.
  • Branch principal protegida.
  • Deploy automático com versão rastreável (hash do commit).
  • Plano de rollback testado.
  • Monitoramento de erros, desempenho e disponibilidade.
  • HTTPS, cabeçalhos de segurança e cache de assets com hash.

Tabela completa de comandos

Cheat sheet

DevOps para frontend: comandos essenciais

Docker, compose e GitHub Actions.

Docker: imagens

Construir e publicar

ComandoDescrição
docker build -t app:1.0 .Constrói a imagem
docker build --target builder .Constrói até um estágio
docker imagesLista imagens
docker tag app:1.0 ghcr.io/u/app:1.0Nova tag
docker push ghcr.io/u/app:1.0Publica no registro
docker pull nginx:alpineBaixa uma imagem
docker rmi app:1.0Remove imagem
docker history app:1.0Camadas e tamanhos

Docker: containers

Executar e inspecionar

ComandoDescrição
docker run -d -p 3000:3000 appRoda em segundo plano
docker run --env-file .env appCom variáveis de ambiente
docker ps / docker ps -aRodando / todos
docker logs -f nomeLogs ao vivo
docker exec -it nome shShell dentro do container
docker stop nomePara
docker rm nomeRemove
docker statsCPU e memória

Limpeza

Recuperando espaço

ComandoDescrição
docker system dfEspaço usado
docker system pruneRemove o que não está em uso
docker image prune -aRemove imagens sem container
docker volume pruneRemove volumes órfãos
docker builder pruneLimpa o cache de build

docker compose

Vários serviços juntos

ComandoDescrição
docker compose up -dSobe tudo
docker compose up -d --buildReconstrói e sobe
docker compose psStatus
docker compose logs -f webLogs de um serviço
docker compose exec web shShell num serviço
docker compose downDerruba
docker compose down -vDerruba e apaga volumes

GitHub Actions

Peças de um workflow

ComandoDescrição
on: [push, pull_request]Quando roda
runs-on: ubuntu-latestMáquina
uses: actions/checkout@v4Baixa o código
uses: actions/setup-node@v4Instala o Node
run: npm ciExecuta um comando
needs: qualidadeDepende de outro job
secrets.NOMEUsa um segredo
if: failure()Só em caso de falha
gh run list / gh run watchAcompanha pelo terminal

Referências

Para os comandos de terminal usados em servidores, veja o guia completo de Linux, e para o fluxo de branches e pull requests, o guia de Git e GitHub.

Teste seus conhecimentos
1 / 5

5 perguntas sobre DevOps para frontend.

Qual a principal vantagem de um build multi-stage?

Não foi dessa vez. Explicação:

O estágio final copia só o necessário para executar a aplicação.