CI/CD with GitHub Actions
Complete guide for setting up auto-deployment using GitHub Actions for Laju applications.
Overview
Laju provides a GitHub Actions workflow template in the github-workflow-sample/ folder to simplify auto-deployment setup. With this workflow, every time you push to the main branch, the application will automatically deploy to your production server.
Deployment Flow:
Push to GitHub → GitHub Actions triggered → SSH to server → Pull code → Install deps → Build on server → Migrate → Reload PM2Benefits of Building on Server:
- No need to build locally before pushing
- Just push source code, server handles the build
- Consistent environment between build and runtime
- Faster development workflow
Prerequisites
On Production Server
- Node.js installed (via NVM recommended)
- PM2 installed globally
- Git installed
- Repository already cloned to server
- SSH access configured
- Sufficient RAM for build (minimum 1GB, recommended 2GB+)
On GitHub
- Repository already pushed to GitHub
- Access to repository Settings (for Secrets setup)
On Local (Development)
- No need to build before pushing
- Just push source code to GitHub
- Server will handle the build
Quick Start
1. Move Workflow File
Move the workflows folder from github-workflow-sample to your project root and rename it to .github:
# From project root
mv github-workflow-sample/workflows .github/2. Edit Workflow Configuration
Edit .github/workflows/deploy.yml and customize these lines:
| Line | Setting | Default | Change To |
|---|---|---|---|
| 68 | Branch name | main | Your branch name |
| 97 | Project path | /root/laju | Your server path |
| 100 | Git pull branch | main | Your branch name |
| 124 | PM2 process name | laju | Your PM2 name |
Example:
# Line 68 - Change branch
push:
branches:
- production # Your branch
# Line 97 - Change project path
cd /var/www/myapp # Your path
# Line 100 - Change branch
git pull origin production # Your branch
# Line 124 - Change PM2 name
pm2 reload myapp # Your PM2 name3. Setup GitHub Secrets
Open your GitHub repository:
- Go to Settings → Secrets and variables → Actions
- Click New repository secret
- Add the following 3 secrets:
| Secret Name | Value |
|---|---|
SSH_HOST | Server IP address or domain |
SSH_USER | SSH username (e.g., root, ubuntu) |
SSH_PRIVATE_KEY | Complete SSH private key content |
4. Push and Deploy
git add .
git commit -m "Add GitHub Actions workflow"
git push origin mainThe workflow will run automatically. Check progress in the Actions tab in your GitHub repository.
Note: You don't need to run
npm run buildlocally. Just push source code, the server will handle the build.
Generate SSH Key
On Local Machine
# Generate a new key specifically for GitHub Actions
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions
# This will generate 2 files:
# ~/.ssh/github_actions (private key - for GitHub Secret)
# ~/.ssh/github_actions.pub (public key - for server)Copy Public Key to Server
# Method 1: Using ssh-copy-id
ssh-copy-id -i ~/.ssh/github_actions.pub user@server-ip
# Method 2: Manual
cat ~/.ssh/github_actions.pub
# Copy output, then on server:
# nano ~/.ssh/authorized_keys
# Paste on a new lineCopy Private Key for GitHub Secret
cat ~/.ssh/github_actions
# Copy the ENTIRE output (including -----BEGIN and -----END)
# Paste into GitHub Secret SSH_PRIVATE_KEYVerify Connection
# Test SSH with the new key
ssh -i ~/.ssh/github_actions user@server-ip
# If successful, you will be logged into the serverServer Configuration
1. Initial Server Setup
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
# Install Node.js
nvm install 22
nvm use 22
# Install PM2
npm install -g pm2
# Clone repository
cd /root # or your preferred directory
git clone https://github.com/username/repository.git laju
cd laju
# Install dependencies & first build
npm install
npm run build
# Setup PM2
cd build
pm2 start server.js --name laju
pm2 save
pm2 startup2. Git Configuration on Server
# Set git config (optional but recommended)
git config --global user.email "deploy@server"
git config --global user.name "Deploy Bot"
# Make sure you can pull without password
# (use SSH key or token)3. Directory Structure on Server
/root/laju/ # Source code (git repository)
├── app/
├── resources/
├── build/ # Production build
│ ├── server.js
│ ├── .env
│ └── data/
└── ...Customization
Change Target Branch
on:
push:
branches:
- production # Change from 'main' to another branchMultiple Environments
name: Deploy to Staging
on:
push:
branches:
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to Staging
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_SSH_HOST }}
username: ${{ secrets.STAGING_SSH_USER }}
key: ${{ secrets.STAGING_SSH_PRIVATE_KEY }}
script: |
cd /root/laju-staging
git pull origin develop
# ... rest of commandsAdd Slack Notification
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}Skip Deployment
Add [skip ci] to your commit message to skip deployment:
git commit -m "Update README [skip ci]"Troubleshooting
Error: "Permission denied (publickey)"
Cause: SSH key is invalid or not registered on the server.
Solution:
# Verify public key exists on server
cat ~/.ssh/authorized_keys
# Make sure permissions are correct
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keysError: "npm: command not found"
Cause: NVM is not loaded in non-interactive shell.
Solution: Make sure the script loads NVM:
script: |
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
npm installError: "pm2: command not found"
Cause: PM2 is not in PATH.
Solution:
script: |
export PATH="$PATH:$(npm bin -g)"
pm2 reload lajuError: "Host key verification failed"
Cause: Server is not in known_hosts.
Solution: Add parameter to workflow:
- name: SSH and deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
# commandsDeployment Succeeded but Application Error
Debug Steps:
# On server, check PM2 logs
pm2 logs laju --lines 100
# Check status
pm2 status
# Manual restart if needed
pm2 restart lajuAdvanced Configuration
Caching Dependencies
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-Health Check After Deploy
- name: Health Check
run: |
sleep 10
curl -f https://yourdomain.com/health || exit 1Automatic Rollback
- name: Deploy with Rollback
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /root/laju
# Backup current version
cp -r build build_backup_$(date +%Y%m%d_%H%M%S)
# Deploy
git pull origin main
npm install
npm run build
cd build
DB_CONNECTION=production npm run migrate
pm2 reload laju
# Health check
sleep 5
if ! curl -f http://localhost:5555/health; then
echo "Health check failed, rolling back..."
pm2 stop laju
rm -rf build
mv build_backup_* build
pm2 start laju
exit 1
fi
# Cleanup old backups (keep last 3)
ls -dt build_backup_* | tail -n +4 | xargs rm -rfMatrix Deployment (Multiple Servers)
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
server: [server1, server2, server3]
steps:
- name: Deploy to ${{ matrix.server }}
uses: appleboy/ssh-action@master
with:
host: ${{ secrets[format('{0}_SSH_HOST', matrix.server)] }}
username: ${{ secrets[format('{0}_SSH_USER', matrix.server)] }}
key: ${{ secrets[format('{0}_SSH_KEY', matrix.server)] }}
script: |
# deployment commandsSecurity Best Practices
Use Dedicated SSH Key
- Create a dedicated SSH key for GitHub Actions
- Don't use personal keys
Limit SSH Key Access
- On server, limit commands that can be executed
Rotate Secrets Regularly
- Change SSH key every 6-12 months
Use Environment Protection
- On GitHub: Settings → Environments → Add protection rules
Audit Logs
- Check GitHub Actions logs regularly
- Monitor SSH access on server (
/var/log/auth.log)
Next Steps
- Production - Manual deployment guide
- Testing - Test before deploying
