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.
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
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.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
};
export default nextConfig;# 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.jsone o lockfile antes do código faz onpm ciser reaproveitado do cache enquanto as dependências não mudam. npm ciinstala 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 appevita rodar a aplicação como root dentro do container.
.dockerignore
Tão importante quanto o Dockerfile: evita mandar lixo (e segredos) para o build.
node_modules
.next
.git
*.log
.env*
!.env.example
Dockerfile
docker-compose*.ymlBuild e execução
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-appSPA estática com Nginx
Uma aplicação React/Vite que gera só arquivos estáticos não precisa de Node em produção:
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 80server {
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:
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: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 volumesPara 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.examplecom 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.
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 buildconcurrencycancela execuções antigas da mesma branch quando chega um push novo.cache: npmreaproveita 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
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:
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=maxMarcar 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
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/healthque responde rápido e confirma que as dependências estão acessíveis.
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
DevOps para frontend: comandos essenciais
Docker, compose e GitHub Actions.
Docker: imagens
Construir e publicar
Docker: containers
Executar e inspecionar
Limpeza
Recuperando espaço
docker compose
Vários serviços juntos
GitHub Actions
Peças de um workflow
Referências
- Documentação do Docker: guias e referência do Dockerfile.
- Boas práticas de Dockerfile: recomendações oficiais.
- Documentação do GitHub Actions: workflows, segredos e ambientes.
- Deploy do Next.js: opções oficiais de deploy.
- Web Vitals: as métricas de experiência do usuário.
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.
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.



