Como Automatizei o Pipeline de uma Aplicação Next.js com GitHub Actions, Act e Docker
Sobre mim e este projeto
Meu nome é Claudio Silwa. Sou desenvolvedor fullstack com foco em arquitetura limpa, performance e impacto real no negócio. Atualmente atuo como Fullstack Pleno na Gt Gestão Contábil e também sou fundador da Front-End Fusion, uma comunidade de mentoria para desenvolvedores.
Recentemente, colaborei com a squad da comunidade, que está construindo uma aplicação para a Associação Cultural e Educacional Violeta Eliz, uma instituição que atua desde 2014 na Favela Morro da Mandioca em São Paulo, promovendo inclusão social, educação, cultura e geração de renda. O projeto era o site institucional da associação, uma aplicação Next.js que precisava sair do ambiente de desenvolvimento para produção de forma estável e automatizada.
link do github : https://github.com/Projeto-FrontEnd-Fusion/Aceve-website-development
O desafio não era apenas "subir o site". Era construir um pipeline que permitisse à equipe de desenvolvimento atualizar o conteúdo sem depender de processos manuais, que garantisse performance mesmo com recursos limitados de servidor, e que fosse seguro e rastreável.
Este artigo documenta exatamente como fiz isso: desde a decisão de tirar o build do servidor até a configuração do pipeline com GitHub Actions, testes locais com Act e o deploy final. O foco é prático, com as decisões técnicas que tomei e os problemas que precisei resolver ao longo do caminho.
A Arquitetura do Pipeline
Antes de escrever qualquer código, precisei definir como os componentes se conectariam:
| Componente | Função no Pipeline |
|---|---|
| GitHub Actions | Build da imagem Docker e push para o registry |
| Docker Hub | Armazenamento da imagem |
| Servidor (Dokploy) | Pull da imagem e execução do container |
| Act | Teste local dos workflows antes do commit |
A decisão mais importante foi tirar a responsabilidade do build do servidor de produção. Em vez de construir a imagem diretamente no servidor (o que consumia recursos e causava timeouts), transferi essa tarefa para o GitHub Actions. O servidor passou a fazer apenas o pull da imagem pronta e o deploy.
Essa abordagem é conhecida como "build once, deploy anywhere" e faz toda a diferença quando se trabalha com múltiplos ambientes ou servidores com recursos limitados.
1. O Dockerfile: Construindo uma Imagem Segura e Enxuta
O Dockerfile foi o primeiro arquivo que precisei acertar. Foram várias tentativas até chegar a uma versão estável.
A Estrutura Multi-Stage
Optei por uma estrutura multi-stage para separar o ambiente de build do ambiente de produção. O estágio de build contém todas as dependências de desenvolvimento (TypeScript, ESLint, ferramentas de build). O estágio final de produção copia apenas o que é realmente necessário para rodar a aplicação.
# Estágio base - dependências comuns
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
# Estágio de build
FROM base AS builder
RUN npm ci
COPY . .
RUN npm run build
# Estágio de produção - imagem final enxuta
FROM node:20-alpine AS runner
WORKDIR /app
# Usuário não-root por segurança
RUN addgroup -g 1001 -S nodejs && \
adduser -u 1001 -S nodejs -G nodejs
# Copia apenas o necessário
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
RUN chown -R nodejs:nodejs /app
USER nodejs
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "server.js"]
Por que Alpine?
A escolha da imagem base node:20-alpine não foi aleatória. A versão Alpine do Node.js é significativamente menor que a versão padrão. A diferença final foi de aproximadamente 400MB a menos na imagem final. Para um servidor com recursos limitados, isso é crucial.
A Saída Standalone do Next.js
No arquivo next.config.mjs, precisei ativar o modo standalone:
export default {
output: 'standalone',
}
Esta configuração é específica do Next.js e gera uma versão da aplicação que inclui apenas os arquivos necessários para produção, com as dependências já resolvidas. Sem ela, o estágio COPY --from=builder /app/.next/standalone simplesmente não funcionaria.
O Problema do Sharp
Durante os builds, o Next.js exibia um aviso sobre a falta da biblioteca sharp para otimização de imagens. Instalar o sharp no Alpine não era trivial, pois a biblioteca precisa ser compilada nativamente e o Alpine não tem as ferramentas de build por padrão.
Minha decisão foi desabilitar a otimização de imagens no servidor:
export default {
output: 'standalone',
images: {
unoptimized: true,
},
}
Isso eliminou a dependência do sharp e simplificou o Dockerfile drasticamente. O tradeoff é que as imagens não são otimizadas automaticamente, mas para o volume do projeto, foi uma decisão aceitável.
A Criação do Usuário Não-Root
Executar containers como root é uma prática insegura. Se um invasor conseguir acessar o container, terá privilégios totais no host. Por isso, criei um usuário dedicado no Dockerfile:
RUN addgroup -g 1001 -S nodejs && \
adduser -u 1001 -S nodejs -G nodejs
A flag -S cria um usuário de sistema sem login interativo. O -u 1001 define um UID específico. O -G adiciona o usuário ao grupo recém-criado.
2. Act: Testando Workflows Localmente
Antes de fazer qualquer commit para o GitHub, precisei de uma forma de testar o workflow do GitHub Actions localmente. Foi quando descobri o Act.
O que é o Act?
Act é uma ferramenta de linha de comando que simula o ambiente do GitHub Actions no computador local. Ele lê os arquivos .github/workflows/*.yml, baixa as imagens Docker necessárias e executa cada passo do workflow, exibindo os logs no terminal.
Por que usar o Act?
O problema que o Act resolve é simples: testar workflows sem poluir o repositório com commits de teste. Sem o Act, o ciclo seria:
- Commit
- Push
- Esperar de 2 a 5 minutos
- Ver erro no GitHub
- Corrigir
- Repetir
Com o Act, o ciclo reduz para segundos, localmente, sem commits intermediários.
Instalação
No meu ambiente Windows com WSL, usei:
winget install nektos.act
Após a instalação, precisei reiniciar o terminal para que o comando act fosse reconhecido no PATH.
Comandos que utilizei
# Listar workflows disponíveis
act -l
# Executar o workflow de build
act push -j build-and-push --secret-file .secrets
Aprendizado Importante
O Act não é uma réplica 100% fiel do GitHub Actions. O sistema de cache (type=gha) não funciona corretamente localmente. Além disso, algumas actions específicas do GitHub podem ter comportamentos diferentes. Por isso, depois de validar o workflow com o Act, fiz questão de testar também no GitHub propriamente dito.
3. O Workflow do GitHub Actions
Com o Dockerfile validado e o Act funcionando, criei o arquivo de workflow. A estrutura final ficou assim:
name: Build and Push Docker Image
on:
push:
branches: [main]
workflow_dispatch:
env:
IMAGE_NAME: comunidadeff/violetaeliz
DOCKER_BUILDKIT: 1
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
target: runner
tags: |
${{ env.IMAGE_NAME }}:latest
${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Decisões que tomei
Por que usar workflow_dispatch? Adicionei esta opção para poder executar o workflow manualmente pela interface do GitHub. Isso é útil para testes ou para deploys específicos sem necessidade de um novo commit.
Por que duas tags? A tag latest sempre aponta para a versão mais recente. A tag com o SHA do commit permite rastrear exatamente qual versão está rodando. Se algo der errado, posso voltar para uma versão anterior conhecida.
Por que DOCKER_BUILDKIT=1? O BuildKit é um sistema de build mais moderno do Docker. Ele permite builds paralelos, melhor cache e imagens menores.
Por que cache-from: type=gha? Esta configuração utiliza o cache do GitHub Actions para acelerar builds subsequentes. A primeira build pode demorar, mas as seguintes serão muito mais rápidas.
O Problema do Extract Metadata
No início, incluí a action docker/metadata-action para gerar tags automaticamente. Ela falhava com erro Bad credentials mesmo com o login funcionando. A action tentava acessar o repositório no Docker Hub para extrair metadados.
Minha solução foi remover esta action e definir as tags manualmente no step de build. Perdi alguns metadados, mas ganhei em simplicidade e confiabilidade.
Secrets no GitHub
Configurei os seguintes secrets no repositório (Settings → Secrets and variables → Actions):
| Secret | Para que serve |
|---|---|
DOCKER_USERNAME | Login no Docker Hub |
DOCKER_TOKEN | Token de acesso (não a senha) |
NEXT_PUBLIC_APPURL | URL da aplicação (usada no build) |
NEXT_PUBLIC_PAYPAL_CLIENTID | Chave do PayPal (usada no build) |
O token do Docker Hub foi gerado na conta do Docker Hub, em Account Settings → Security → New Access Token, com permissões de leitura e escrita.
4. O Deploy no Servidor
Com a imagem no Docker Hub, o próximo passo foi configurar o servidor para puxar esta imagem e executar o container.
O Provider Docker no Painel
A plataforma de deploy utilizada permite configurar uma aplicação com Source Type "Docker". Isso significa que ela não constrói nada localmente; apenas puxa a imagem pronta do registry.
A configuração final foi:
| Campo | Valor |
|---|---|
| Source Type | Docker |
| Docker Image | comunidadeff/violetaeliz:latest |
| Username | comunidadeff |
| Password | Token do Docker Hub |
| Porta | 3000 |
Por que esta abordagem funciona?
O servidor nunca vê o código fonte. Ele só baixa a imagem que o GitHub Actions construiu. Isto significa que o servidor pode ser muito mais simples (menos CPU, menos RAM) porque não precisa de ferramentas de build.
As Variáveis de Ambiente
Aprendi uma distinção importante: variáveis NEXT_PUBLIC_* são embutidas no bundle no momento do build. Elas precisam estar disponíveis no GitHub Actions, não no servidor.
Já variáveis de runtime (como senhas de banco de dados, chaves de API) são configuradas diretamente no painel da plataforma de deploy.
5. Problemas que Enfrentei e Como Resolvi
Erro: Permission Denied no Container
Sintoma: chown: unknown user comunidadeff
Causa: O nome do usuário no chown não correspondia ao nome criado no adduser.
Solução: Padronizei os nomes, garantindo consistência entre criação e uso.
Erro: Build do Next.js Falhando por Falta de Variáveis
Sintoma: Module not found: Can't resolve '@/assets/Imagem.png'
Causa: O sistema de arquivos do Linux é case-sensitive. No Windows, nomes com diferenças de maiúscula/minúscula são tratados como o mesmo arquivo. No Linux, são arquivos diferentes.
Solução: Padronizei todos os nomes de arquivos e imports para minúsculo.
Erro: Workflow não Aparecia no GitHub
Sintoma: Aba Actions vazia, mesmo com o arquivo .github/workflows/docker-build.yml no repositório.
Causa: O GitHub Actions estava desabilitado nas configurações do repositório.
Solução: Acessei Settings → Actions → Allow all actions and reusable workflows.
Erro: Bad Credentials no Extract Metadata
Sintoma: Login no Docker Hub funcionava, mas a action docker/metadata-action falhava.
Causa: Permissões insuficientes do token ou repositório não existente.
Solução: Removi a action e defini as tags manualmente.
6. Resultados Alcançados
| Métrica | Antes | Depois |
|---|---|---|
| Tamanho da imagem | ~500MB | ~110MB |
| Deploy | Manual | Automático |
| Build | No servidor | GitHub Actions |
| Segurança | Root | Usuário não-root |
| Teste de workflow | Apenas no GitHub | Local com Act |
7. Para Aprofundar
Fontes que utilizei ao longo do processo:
- Documentação oficial do GitHub Actions: https://docs.github.com/en/actions
- Documentação do Act: https://nektosact.com
- Dockerfile reference: https://docs.docker.com/engine/reference/builder
- Next.js deployment documentation: https://nextjs.org/docs/deployment
- Docker Hub token creation: https://docs.docker.com/security/for-developers/access-tokens
Conclusão
O pipeline que construí transforma o deploy de um processo manual e propenso a erros em uma operação automatizada, rápida e confiável. O código vai do repositório para produção em minutos, com zero intervenção manual no servidor.
Para aplicações Next.js, esta abordagem é particularmente relevante porque o framework já oferece suporte nativo ao modo standalone, que simplifica drasticamente o Dockerfile.
Se você está enfrentando problemas com build no servidor, containers com falhas ou deploys manuais demorados, considere adotar este fluxo. O investimento inicial em configuração se paga rapidamente em horas de operação economizadas.
Quer falar comigo?
Email: claudiosilva.one@gmail.com
LinkedIn: https://www.linkedin.com/in/claudiosilva-dev
Portfolio: https://claudiosilva.vercel.app/