Implementar uma pipeline de CI/CD Node.js é um dos passos mais críticos para elevar o nível de maturidade técnica de qualquer projeto de software. Para desenvolvedores e sysadmins, a diferença entre fazer deploys manuais via FTP ou SSH e utilizar automação é abismal: consistência, velocidade e, acima de tudo, redução de erros humanos. Neste tutorial, vamos configurar um ambiente completo onde o código sai do seu repositório local, passa por validações automáticas e chega pronto para produção em uma VPS (Virtual Private Server) com sistema Linux.
O cenário adotado aqui é prático e robusto: utilizaremos o Git como ferramenta de versionamento e gatilho da pipeline. A ideia central é simples, mas poderosa: ao fazer push para a branch principal (main/master), um script no servidor executa automaticamente as tarefas necessárias para colocar a nova versão em produção. Não precisamos de orquestradores complexos como Kubernetes ou ferramentas pesadas de nuvem neste momento; vamos focar na essência da integração contínua e entrega contínua usando scripts shell simples e eficientes.
1. Preparação do Ambiente no Servidor Linux
O primeiro passo é garantir que nossa VPS esteja preparada para receber e executar a aplicação. Vamos assumir um sistema Debian ou Ubuntu, que são os mais comuns em ambientes de desenvolvimento modernos. Conecte-se ao seu servidor via SSH e atualize o sistema operacional para garantir que todas as bibliotecas estejam na versão mais recente e segura.
sudo apt update && sudo apt upgrade -y
Agora, precisamos instalar as dependências essenciais. Para rodar Node.js em produção de forma eficiente, instalaremos o Node Version Manager (nvm). O nvm permite gerenciar múltiplas versões do Node sem precisar de permissões root para a instalação global, além de facilitar a troca de versões conforme a necessidade do projeto.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Após a instalação do script, carregue o nvm no seu ambiente atual e instale a versão LTS (Long Term Support) do Node.js, que é recomendada para ambientes de produção por sua estabilidade.
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install --lts
Com o Node e o npm (Node Package Manager) instalados, vamos criar um usuário dedicado para a aplicação. Nunca rode aplicações em produção como root. Criar um usuário específico isola os riscos de segurança e organiza as permissões de arquivo.
sudo useradd -m -s /bin/bash nodeapp
sudo mkdir -p /var/www/nodeapp
sudo chown -R nodeapp:nodeapp /var/www/nodeapp
Este diretório /var/www/nodeapp será a raiz onde nosso código clonado residirá e onde os processos ficarão ativos. Também vamos instalar o Nginx, que atuará como um proxy reverso, recebendo as requisições externas e encaminhando para a porta em que nossa aplicação Node.js estará ouvindo.
sudo apt install nginx -y
2. Configuração do Proxy Reverso no Nginx
O Nginx não deve ser o responsável por servir os arquivos estáticos ou processar a lógica da aplicação Node.js diretamente em cenários de alta concorrência, mas ele é excelente para gerenciar SSL (HTTPS), compressão e roteamento. Vamos configurar um bloco de servidor básico.
Crie um arquivo de configuração no diretório /etc/nginx/sites-available/. Para fins didáticos, vamos chamar o arquivo de nodeapp.
sudo nano /etc/nginx/sites-available/nodeapp
Dentro do arquivo, insira a seguinte configuração. Note que estamos apontando para localhost na porta 3000, que é onde nosso app Node.js rodará.
server {
listen 80;
server_name seu-dominio.com www.seu-dominio.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Ative essa configuração criando um link simbólico na pasta sites-enabled e teste se a sintaxe está correta antes de reiniciar o serviço.
sudo ln -s /etc/nginx/sites-available/nodeapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Com isso, temos a infraestrutura de rede pronta. Agora, precisamos garantir que nossa aplicação Node.js tenha um processo persistente que reinicie automaticamente caso o servidor seja rebotado ou a aplicação crash.
3. Gerenciamento de Processos com PM2
No ecossistema Node.js, o PM2 é o padrão da indústria para gerenciamento de processos em produção. Ele oferece balanceamento de carga, monitoramento de memória e, crucialmente, recuperação automática (auto-restart) quando algo dá errado.
Instale o PM2 globalmente no servidor para que fique disponível para todos os usuários, ou apenas para o usuário nodeapp. Vamos instalar para o usuário específico da aplicação.
sudo -u nodeapp -i
npm install -g pm2
exit
Agora, precisamos configurar o PM2 para iniciar automaticamente junto com o sistema Linux. Isso cria um serviço systemd que gerencia o ciclo de vida do nosso app.
sudo -u nodeapp -i
pm2 startup systemd
sudo systemctl enable pm2-nodeapp
O comando acima gera um script específico que deve ser copiado e executado com privilégios root para registrar o serviço corretamente. Anote a saída do terminal; ela conterá um comando sudo systemctl enable ... exato que você precisa rodar.
4. Estrutura de Automação no Repositório Local
A magia da automação acontece através de scripts que rodam tanto localmente quanto no servidor. Vamos criar um script shell simples que simula o processo de deploy. Este script fará o seguinte: parar o app antigo, puxar as últimas alterações do Git, instalar dependências e reiniciar o app.
No seu projeto local (ou em uma pasta dedicada no servidor se estiver testando manualmente), crie um arquivo chamado deploy.sh.
touch deploy.sh
chmod +x deploy.sh
O conteúdo deste script é a base da sua lógica de entrega contínua. Ele deve ser robusto o suficiente para lidar com erros comuns.
#!/bin/bash
# Configurações
APP_DIR="/var/www/nodeapp"
USER="nodeapp"
echo "Iniciando deploy..."
# 1. Entrar no diretório da aplicação e puxar alterações
cd $APP_DIR || exit
git pull origin main
# 2. Instalar dependências (se houver mudanças)
npm install --production
# 3. Reiniciar a aplicação com PM2
sudo -u $USER pm2 restart all
echo "Deploy concluído com sucesso!"
Este script é o coração do processo. Ele garante que o estado do servidor seja sempre consistente com o último commit na branch main. Em ambientes mais complexos, você adicionaria etapas de build (como npm run build para projetos React ou Next.js) e testes antes da reinicialização.
5. Automatizando com Git Hooks (Webhooks Simples)
Para transformar esse script manual em uma verdadeira pipeline, precisamos de um gatilho. A maneira mais simples e eficaz sem usar serviços externos como GitHub Actions ou Jenkins é utilizar um Git Hook do lado do servidor.
No seu repositório na VPS (que deve ser um repositório "bare" ou apenas o diretório de trabalho), configuramos um hook de post-receive. Isso significa que, sempre que fizermos push para esse repositório remoto, o script será executado.
cd /var/www/nodeapp
mkdir -p .git/hooks
nano .git/hooks/post-receive
Insira o seguinte conteúdo no hook:
#!/bin/bash
GIT_WORK_TREE=/var/www/nodeapp git checkout -f main
bash /var/www/nodeapp/deploy.sh
No entanto, para projetos simples que já têm o diretório de trabalho configurado como /var/www/nodeapp, podemos simplificar e chamar diretamente o script de deploy criado anteriormente. A abordagem mais limpa é configurar um endpoint HTTP externo ou usar uma ferramenta como Webhook.io ou integrações nativas do GitHub/GitLab, mas para manter a dependência zero, vamos focar na configuração do usuário e permissões SSH.
Para permitir que seu usuário local faça push e trigger do deploy sem pedir senha toda vez (automação real), você precisa configurar chaves SSH. Gere uma chave no seu computador local se ainda não tiver uma:
ssh-keygen -t ed25519
Copie o conteúdo da chave pública para o arquivo ~/.ssh/authorized_keys do usuário nodeapp na VPS. Isso permite que scripts locais executem comandos remotos de forma segura e automática.
6. Criando um Processo de Build e Deploy Automatizado Local
Para finalizar a integração contínua, vamos criar um script local que prepara o ambiente e faz o push para o servidor. Isso garante que o código seja testado antes de sair do seu computador.
Crie um arquivo scripts/deploy-prod.sh no seu projeto local:
#!/bin/bash
echo "Rodando testes locais..."
npm test
if [ $? -ne 0 ]; then
echo "Testes falharam. Abortando deploy."
exit 1
fi
echo "Fazendo commit e push..."
git add .
git commit -m "Auto-deploy: $(date)"
git push origin main
echo "Push enviado para produção. Aguardando finalização no servidor..."
Ao executar este script, você garante que apenas código que passa nos testes locais seja enviado ao servidor. O servidor, por sua vez, já possui o hook ou a lógica configurada para aplicar essas mudanças.
7. Monitoramento e Manutenção Contínua
Após a implementação da pipeline CI/CD Node.js, o trabalho não termina. A visibilidade é tão importante quanto a automação. Utilize o PM2 para monitorar o consumo de CPU e memória em tempo real.
pm2 monit
Além disso, configure logs robustos. O Node.js pode gerar logs verbosos que enchem o disco rapidamente. Configure o PM2 para rotacionar logs ou utilize um sistema como Winstons/DailyRotateFile dentro da aplicação.
pm2 save
Sempre salve o estado do PM2 após fazer mudanças manuais de configuração, garantindo que elas persistam em reinicializações.
Conclusão
Configurar um ambiente de deploy automatizado em uma VPS Linux transforma a relação do desenvolvedor com o servidor. Você deixa de ser um operador de botões manuais e passa a atuar como um engenheiro de infraestrutura, focando na arquitetura e na qualidade do código.
Este tutorial cobriu desde a instalação das dependências básicas até a criação de scripts de automação que garantem integridade e velocidade. Lembre-se: segurança é primordial. Mantenha suas chaves SSH protegidas, atualize o sistema operacional regularmente e monitore os acessos ao seu servidor.
Com essa base sólida, você está pronto para escalar. Futuramente, você pode integrar ferramentas como Docker para containerizar a aplicação ou migrar para orquestradores de nuvem, mas os conceitos de pipeline, testes prévios e automação de deploy permanecerão os mesmos pilares da engenharia de software moderna.