name: CI/CD Pipeline
on:
push:
branches: [ main ]
jobs:
test-and-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run Lint
run: npm run lint
deploy:
needs: test-and-lint
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.VPS_PORT || 22 }}
script: |
cd /var/www/app
git pull origin main
npm ci --omit=dev
npm run build
pm2 reload my-appMi s-a luat de facturile de pe Vercel când un proiect personal a trecut de 15k vizitatori unici pe lună și am început să fiu taxat aiurea pentru bandwidth. Am mutat totul pe un VPS de 6 euro de la Hetzner. Singura chestie care îmi lipsea era deploy-ul automat la fiecare push în main.
Am rezolvat problema cu un workflow simplu în GitHub Actions. Am economisit cam 35% la build time după ce am reglat cache-ul și acum am control total pe server. Mai jos îți arăt cum am structurat pipeline-ul și de ce probleme m-am lovit pe parcurs.
Strategia de build: Pe GitHub sau pe VPS?
Aici e primul trade-off major pe care trebuie să-l înțelegi. Ai două opțiuni:
- Faci build-ul direct pe GitHub Actions, creezi o arhivă cu folderul
.nextși o trimiți prin SCP pe server. - Tragi codul pe VPS via Git și rulezi
npm run builddirect pe server.
Am început cu a doua variantă pentru că era mai simplu de scris în YAML. Dar am dat rapid de o problemă: dacă ai un VPS ieftin cu doar 1GB sau 2GB RAM, procesul de build de la Next.js o să consume toate resursele. OOM (Out Of Memory) killer-ul de la Linux o să-ți închidă procesul exact când ți-e lumea mai dragă.
Pentru proiecte mici, poți rula build-ul direct pe server doar dacă adaugi un fișier swap de măcar 2GB. În exemplul de mai jos, mergem pe varianta rulării pe server, dar cu mențiunea că swap-ul e obligatoriu dacă ai resurse limitate.
Structura pipeline-ului
Workflow-ul nostru face patru lucruri: verifică codul, rulează linter-ul, rulează testele (dacă ai) și apoi se conectează prin SSH pe server pentru a trage ultimele modificări și a restarta aplicația.
Folosesc appleboy/ssh-action pentru partea de SSH. E o acțiune stabilă, o folosesc de ani de zile și nu mi-a făcut niciodată figuri. Pe server, folosesc PM2 ca să țin aplicația Next.js pornită în fundal.
Setup-ul pe server
Înainte să dai drumul la pipeline, trebuie să pregătești serverul. Te conectezi prin SSH și clonezi repo-ul în directorul dorit (de exemplu, /var/www/app).
Tot acum trebuie să configurezi PM2. Rulează o singură dată manual:
pm2 start npm --name "my-app" -- start
După asta, pipeline-ul se va ocupa doar de pm2 reload my-app. Avantajul la reload față de restart este că PM2 va încerca să mențină aplicația online în timp ce încarcă noul cod, reducând downtime-ul la zero dacă ai configurat cluster mode.
Un mic pont pentru performanță
Implicit, Next.js are nevoie de cache-ul din .next/cache ca să facă build-uri rapide. Dacă rulezi build-ul pe GitHub Actions, merită să folosești acțiunea de cache dedicată pentru Next.js. Dacă rulezi build-ul direct pe VPS (ca în exemplul de mai jos), cache-ul se păstrează de la un build la altul în folderul local, ceea ce scurtează considerabil timpul de așteptare.
Tu cum abordezi deploy-ul de Next.js? Rămâi pe serverless sau preferi controlul și costurile fixe ale unui VPS?