Membangun CI/CD Pipeline untuk Aplikasi Node.js dengan GitHub Actions: Tutorial Step-by-Step
Pendahuluan
Bayangkan kamu sedang membangun backend API untuk aplikasi e-commerce — setiap kali ada perubahan kode, kamu harus manual menjalankan tes, build, lalu upload ke server. Proses itu bukan hanya lambat, tapi juga rawan kesalahan manusia. Itulah masalah yang diselesaikan oleh CI/CD pipeline.
Continuous Integration (CI) adalah praktik menggabungkan kode secara otomatis dan memverifikasi hasilnya setiap kali ada push. Continuous Deployment (CD) melanjutkannya dengan men-deploy kode yang sudah terverifikasi ke server secara otomatis.
Jika kamu ingin membangun layanan seperti Tokopedia atau Bukalapak, konsep CI/CD adalah fondasi yang membuat ratusan developer bisa bekerja bersama tanpa saling menghalangi. Dalam tutorial ini, kita akan membangun pipeline CI/CD lengkap untuk aplikasi Node.js menggunakan GitHub Actions.
Prasyarat dan Pengenalan Workflow GitHub Actions
Sebelum mulai, pastikan kamu sudah menyiapkan:
- Akun GitHub dengan repository Node.js
- Node.js v18+ terinstall di lokal
- Aplikasi Node.js dengan test suite (kita pakai Jest)
- Server VPS atau akses SSH ke server deployment (untuk bagian CD)
- Pemahaman dasar tentang
package.jsondan npm scripts
Struktur project Node.js yang akan kita gunakan:
my-node-app/
├── src/
│ ├── app.js
│ └── routes/
│ └── users.js
├── tests/
│ └── users.test.js
├── package.json
└── .github/
└── workflows/
└── ci-cd.yml
File package.json dengan scripts yang diperlukan:
{
"name": "my-node-app",
"version": "1.0.0",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"test": "jest --coverage",
"lint": "eslint src/ --ext .js",
"build": "echo 'Build step for Node.js'"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"jest": "^29.0.0",
"eslint": "^8.0.0",
"supertest": "^6.3.0",
"nodemon": "^3.0.0"
}
}
Implementasi Continuous Integration: Build dan Test Otomatis
Buat file .github/workflows/ci-cd.yml. Kita mulai dengan bagian CI dulu — menjalankan lint dan test otomatis setiap ada push atau pull request:
name: CI/CD Pipeline Node.js
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: '20'
jobs:
# =====================
# JOB 1: Continuous Integration
# =====================
ci:
name: Build & Test
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Jalankan ESLint
run: npm run lint
- name: Jalankan unit tests
run: npm test
env:
NODE_ENV: test
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: matrix.node-version == 20
with:
name: coverage-report
path: coverage/
retention-days: 7
Beberapa poin penting di sini:
npm cidigunakan sebagai penggantinpm installkarena lebih deterministik — ia membacapackage-lock.jsondan tidak memodifikasinya- matrix strategy memungkinkan kita menguji di Node.js 18 dan 20 secara paralel
cache: 'npm'menyimpan cachenode_modulesantar run sehingga pipeline lebih cepat
Kalau kamu sudah familiar dengan Mastering ES6 Modules: Cara Mengelola Kode JavaScript Secara Terstruktur, pastikan test suite kamu juga menggunakan module system yang konsisten agar tidak ada error saat CI berjalan.
Implementasi Continuous Deployment: Deploy Otomatis ke Server
Setelah CI berhasil, kita tambahkan job CD yang akan berjalan hanya jika CI sukses dan hanya untuk branch main:
# =====================
# JOB 2: Continuous Deployment
# =====================
cd:
name: Deploy to Production
runs-on: ubuntu-latest
needs: ci # Tunggu job CI selesai
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: production
url: https://myapp.example.com
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Setup SSH key
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Tambahkan server ke known_hosts
run: |
ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Deploy ke server via SSH
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'ENDSSH'
set -e
cd /var/www/my-node-app
git pull origin main
npm ci --production
pm2 reload ecosystem.config.js --env production
echo "Deploy berhasil pada $(date)"
ENDSSH
- name: Verifikasi deployment
run: |
sleep 10
curl -f https://myapp.example.com/health || exit 1
- name: Kirim notifikasi sukses
if: success()
run: |
echo "Deploy sukses ke production!"
# Bisa ditambah webhook Slack/Discord di sini
- name: Kirim notifikasi gagal
if: failure()
run: |
echo "Deploy GAGAL! Periksa logs di GitHub Actions."
Secrets yang perlu kamu tambahkan di Settings > Secrets and variables > Actions repositorimu:
| Secret Name | Isi |
|---|---|
SSH_PRIVATE_KEY | Private key SSH (isi ~/.ssh/id_rsa) |
SERVER_HOST | IP atau domain server, misal 203.0.113.10 |
SERVER_USER | Username SSH, misal ubuntu atau deploy |
File ecosystem.config.js di server untuk PM2:
module.exports = {
apps: [{
name: 'my-node-app',
script: 'src/app.js',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};
Contoh Kasus Nyata: Docker Build dan Push
Berikut workflow lengkap yang juga menangani Docker build — pola yang umum digunakan pada aplikasi backend berskala:
# =====================
# JOB 3: Build & Push Docker Image (Opsional)
# =====================
docker:
name: Build Docker Image
runs-on: ubuntu-latest
needs: ci
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Login ke Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build dan push image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myusername/my-node-app:latest
myusername/my-node-app:${{ github.sha }}
Dockerfile untuk Node.js:
FROM node:20-alpine AS base
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --production
# Copy source
COPY src/ ./src/
# Non-root user untuk keamanan
USER node
EXPOSE 3000
CMD ["node", "src/app.js"]
Pola yang sama bisa kamu terapkan saat membangun backend dengan bahasa lain. Misalnya, jika kamu mengikuti tutorial Membangun RESTful API Sederhana dengan Go: Tutorial Step-by-Step, strukturnya hampir identik — hanya bagian build dan runtime command yang berbeda.
Troubleshooting: Error yang Sering Muncul
npm ci: npm error: Missing package-lock.json
Penyebab: File package-lock.json tidak di-commit ke repository, padahal npm ci membutuhkannya secara wajib.
Solusi:
# Jalankan di lokal, lalu commit hasilnya
npm install
git add package-lock.json
git commit -m "chore: add package-lock.json"
git push
SSH: Host key verification failed saat deploy
Penyebab: Server belum ditambahkan ke known_hosts sebelum koneksi SSH dilakukan, atau step ssh-keyscan dijalankan setelah step ssh command.
Solusi:
# Pastikan urutan steps sudah benar di workflow
- name: Tambahkan server ke known_hosts
run: ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Deploy via SSH
run: ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} "cd /app && git pull"
Jest: Jest encountered an unexpected token saat CI
Penyebab: Jest belum dikonfigurasi untuk menangani ES Modules, sementara source code menggunakan sintaks import/export.
Solusi:
# Install dependensi Babel untuk Jest
npm install --save-dev @babel/core @babel/preset-env babel-jest
// package.json — tambahkan konfigurasi Jest
{
"jest": {
"transform": {
"^.+\\.js$": "babel-jest"
}
}
}
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }]
]
};
PM2: Error: Script not found setelah pm2 reload
Penyebab: Path script di ecosystem.config.js tidak sesuai dengan struktur direktori di server, atau file tidak ter-copy saat git pull.
Solusi:
# Di server, verifikasi path file ada
ls -la /var/www/my-node-app/src/app.js
# Restart PM2 dengan path absolut jika perlu
pm2 start /var/www/my-node-app/src/app.js --name my-node-app
pm2 save
Pertanyaan yang Sering Diajukan
Apa perbedaan npm install dan npm ci di GitHub Actions?
npm ci dirancang khusus untuk lingkungan CI/CD. Ia selalu menghapus node_modules dan menginstall ulang dari nol berdasarkan package-lock.json, sehingga hasilnya 100% konsisten. npm install bisa memodifikasi package-lock.json dan lebih lambat karena tidak menjamin reproduksi yang identik.
Bagaimana cara menyimpan environment variable yang berbeda untuk staging dan production?
Gunakan fitur Environments di GitHub Actions. Buat dua environment (staging dan production) di Settings repositori, lalu tambahkan secrets yang berbeda di masing-masing. Di workflow, gunakan environment: production atau environment: staging di setiap job agar secrets yang tepat digunakan secara otomatis.
Apakah CI/CD pipeline ini aman untuk menyimpan SSH key?
Ya, selama kamu menggunakan GitHub Secrets (bukan hardcode di file workflow). Secrets dienkripsi dan hanya bisa diakses oleh workflow saat runtime — nilainya tidak pernah muncul di logs. Untuk keamanan tambahan, buat SSH key khusus untuk deployment dengan akses terbatas, bukan menggunakan key pribadi kamu.
Bagaimana cara men-trigger pipeline hanya untuk folder tertentu di monorepo?
Gunakan filter paths di bagian on:
on:
push:
branches: [ main ]
paths:
- 'packages/backend/**'
- '.github/workflows/backend-ci.yml'
Dengan ini, pipeline hanya berjalan jika ada perubahan di folder packages/backend/, sehingga tidak membuang waktu build untuk perubahan yang tidak relevan.
Berapa lama waktu ideal untuk satu CI pipeline berjalan?
Idealnya di bawah 5 menit untuk developer experience yang baik. Jika lebih lama, identifikasi bottleneck: biasanya install dependencies (solusi: aktifkan caching dengan cache: 'npm') atau test yang terlalu banyak (solusi: jalankan test secara paralel dengan Jest flag --maxWorkers=4).
Kesimpulan
Kita telah berhasil membangun pipeline CI/CD lengkap untuk aplikasi Node.js: mulai dari menjalankan lint dan test otomatis saat ada push, hingga deployment otomatis ke server via SSH setelah semua checks hijau. Pipeline ini juga bisa diperluas dengan Docker build, notifikasi Slack, atau deployment ke platform cloud seperti Railway atau Render.
Poin-poin kunci yang perlu diingat:
- Gunakan
npm ci(bukannpm install) untuk konsistensi di CI - Simpan semua kredensial di GitHub Secrets, bukan di kode
- Gunakan
needs:untuk memastikan CD hanya berjalan setelah CI sukses - Tambahkan health check setelah deploy untuk verifikasi otomatis
Selamat mencoba membangun pipeline CI/CD pertamamu! Dengan otomatisasi ini, kamu bisa fokus menulis fitur baru tanpa khawatir tentang proses deployment yang membosankan. Jika ada pertanyaan atau menemui kendala, jangan ragu untuk eksplorasi artikel-artikel lainnya di KamusNgoding — komunitas developer Indonesia selalu siap belajar bersama!