Repository: https://github.com/daretechie/github-actions-ec2-pipeline
This document provides step-by-step instructions, evidence checklist, and troubleshooting guidance to help reviewers verify the CI/CD pipeline, cloud deployment, monitoring, and release automation for grading.
- App: Node.js/Express API with static frontend
- CI: Test + automated version tagging on
main - CD: Tag-triggered multi-stage deploy to AWS EC2 (staging → approval → production)
- Process manager: PM2 with zero-downtime swap via
currentsymlink, rollback, and health checks - Monitoring: Scheduled health-check workflow that creates a GitHub issue on failures
- GitHub repository access and Actions enabled
- Two EC2 instances (recommended): staging and production
- Or a single instance with only the production job enabled
- Repo secrets set:
DEV_EC2_HOST,DEV_EC2_USER,DEV_EC2_KEYPROD_EC2_HOST,PROD_EC2_USER,PROD_EC2_KEYREPO_ACCESS_TOKEN(classic PAT, repo scope) for tag pushes in CI
- GitHub Environments created:
staging,production(with approval rules if desired)
- Workflows
.github/workflows/ci.yml: CI on push branches; runs tests; bumps SemVer patch and pushesv*tag onmain.github/workflows/release.yml: Onv*tag → package → deploy-staging (environment: staging) → deploy-production (environment: production) → create release.github/workflows/health-check.yml: Every 5 minutes and manual; checks/api/healthfor DEV/PROD URLs; opens issue on failures
- Deploy
- Artifact is uploaded once and reused for staging and production
scripts/deploy.sh: creates timestamped release dir, updatescurrentsymlink, starts/refreshes PM2, health-check and rollback, cleans old releases
On each instance (replace USER/IP):
ssh -i <key.pem> ubuntu@<EC2_IP>
sudo apt update && sudo apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo npm install -g pm2
sudo apt install nginx -y # optional reverse proxy
sudo mkdir -p /var/www/app /var/www/backup
sudo chown -R $USER:$USER /var/www/app /var/www/backup- Repository → Settings → Secrets and variables → Actions → New repository secret
- Add
DEV_*andPROD_*secrets (host, user, private key) - Add
REPO_ACCESS_TOKEN(classic PAT, repo scope)
- Add
- Repository → Settings → Actions → General → Workflow permissions → Read and write permissions
- Settings → Environments: add
stagingandproductionand optional required reviewers for approvals
- Push changes to a feature branch → CI runs (tests only)
- Merge to
main→ CI runs, then bumps tagvX.Y.Zusing PAT - Tag push triggers
release.yml:package: build tarball and upload artifactdeploy-staging: download artifact, deploy over SSH to staging EC2, health-check passesdeploy-production: waits for environment approval in Actions UI; on approval deploys to production EC2create-release: creates GitHub Release named after the tag
Capture screenshots and attach to your submission (use a folder like submission_images/):
- CI run on
mainshowing tests and tag creation - Tags view showing
vX.Y.Z - Actions run for
Release and Deployshowing stagespackage,deploy-staging, approval gate,deploy-production, andcreate-release - Release page showing the created release for the tag
- PM2 status on EC2 after deployment
pm2 status
- Health endpoint responses
curl http://<STAGING_IP>:3000/api/healthcurl http://<PROD_IP>:3000/api/health
- Health-check workflow run creating an issue when an environment is down (optional test)
- Direct:
http://<EC2_IP>:3000/andhttp://<EC2_IP>:3000/api/health - Optional Nginx reverse proxy (HTTP only) example is included in root
README.md
- Release workflow didn’t start after CI:
- Ensure CI uses
REPO_ACCESS_TOKENin bump job; confirm PAT hasreposcope
- Ensure CI uses
- Create release failed with 403:
- Ensure
create-releasejob haspermissions: contents: writeand repo workflow permissions are set to Read and write
- Ensure
- Health-check workflow cannot create issues:
- Ensure job
permissions: issues: writeand repo workflow permissions are Read and write
- Ensure job
- PM2 reload points to old path:
- Script uses
current/src/server.js. If you previously used absolute timestamp path, delete and restart PM2 withpm2 start /var/www/app/current/src/server.js --name app
- Script uses
- Health check 404:
- Endpoint is
/api/health(not/health)
- Endpoint is
- Node/npm errors on EC2:
- Confirm Node 20.x installed via Nodesource, rerun
npm ci --omit=dev
- Confirm Node 20.x installed via Nodesource, rerun
- SSH failures:
- Verify user, host, and private key formatting in secrets; ensure port 22 open; set perms
chmod 600on key file when testing locally
- Verify user, host, and private key formatting in secrets; ensure port 22 open; set perms
- Use repository secrets; avoid committing credentials
- Limit inbound rules to required ports (22/80/443/3000 as needed)
- Consider rotating keys and using AWS SSM or Systems Manager for access
- CI fundamentals: tests on push, caching, Node 20
- Automated versioning and tagging: SemVer patch via action
- Tag-based releases: automated GitHub Releases
- Cloud deployment: staged EC2 deploy via GitHub Actions over SSH
- Zero-downtime and rollback: PM2 + symlink strategy + health checks
- Monitoring: scheduled health-check workflow creating issues
- Documentation: README with setup, troubleshooting, and evidence checklist
- Project repo:
https://github.com/daretechie/github-actions-ec2-pipeline
Here are the screenshots as evidence of project completion.
1. CI run on main showing tests and tag creation

3. Actions run for Release and Deploy

4. Release page showing the created release

5. PM2 status on EC2 after deployment

6. Health endpoint responses (Staging)



