Acebook AWS CI/CD Guide
Overview
This guide walks you through deploying the Acebook Node.js app to AWS EC2 using a fully automated CI/CD pipeline via GitHub Actions, S3, and CodeDeploy.
Architecture:
GitHub repo → GitHub Actions (CI: lint + tests) → S3 Bucket (zip revision)
→ CodeDeploy → EC2 Instance (runs the app)
Phase 1: Local Setup
Install NVM and Node.js
brew install nvm
Add NVM to your shell (run after install):
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"
Add permanently to ~/.zshrc:
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc
echo '[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"' >> ~/.zshrc
source ~/.zshrc
Install Node:
nvm install 23
node --version
npm --version
Gotcha: If
nvmsayscommand not foundafter install, you need to reload your shell withsource ~/.zshrcbefore it works.
Install MongoDB
brew tap mongodb/brew
brew trust mongodb/brew
brew install mongodb-community@7.0
Start MongoDB (use this if brew services start fails):
mongod --dbpath /opt/homebrew/var/mongodb --logpath /opt/homebrew/var/log/mongodb/mongo.log --fork
Gotcha:
brew services start mongodb-community@7.0often fails withBootstrap failed: 5: Input/output error. Use themongod --forkcommand instead.
Gotcha: If the config file doesn’t exist, create the directories first:
mkdir -p /opt/homebrew/var/mongodb mkdir -p /opt/homebrew/var/log/mongodb
Clone and Run the App
git clone <your-repo-url>
cd acebook-<team-name>
npm install
npm start
Visit http://localhost:3000 — you should see the Acebook homepage.
Run Tests
npm run test:unit
Gotcha: Tests will fail if MongoDB is not running. Always start MongoDB first.
Phase 2: AWS Infrastructure Setup
Step 1: Create S3 Bucket
- AWS Console → S3 → Create bucket
- Name:
acebook-<team-name>-deployments(must be globally unique, lowercase) - Region:
eu-west-2 - Block all public access: leave checked
- Create bucket
Step 2: Create IAM Roles
Role 1 — EC2 Instance Profile:
- IAM → Roles → Create role
- Trusted entity: AWS service → EC2
- Attach:
AmazonS3ReadOnlyAccess - Name:
acebook-ec2-role
Role 2 — CodeDeploy Service Role:
- IAM → Roles → Create role
- Trusted entity: AWS service → CodeDeploy
- AWS auto-attaches
AWSCodeDeployRole - Name:
acebook-codedeploy-role
GitHub Actions IAM User:
- IAM → Users → Create user
- Name:
acebook-github-actions - No console access
- Attach:
AmazonS3FullAccess+AWSCodeDeployFullAccess - After creating: Security credentials → Create access key → Application running outside AWS
- Save both values immediately — you cannot see the secret again
Gotcha: If you lose the secret key, you must delete the access key and create a new one. Do not try to copy the structured credential string — you only need the two individual values (Access Key ID and Secret Access Key).
Step 3: Launch EC2 Instance
- EC2 → Launch Instance
- Name tag: Key=
Application, Value=acebook - AMI: Amazon Linux 2023 (Free Tier)
- Instance type:
t2.micro - Key pair: create new → RSA →
.pem→ save it safely - Security group: allow SSH (port 22) + Custom TCP port 3000 from anywhere
- Advanced Details → IAM instance profile:
acebook-ec2-role - User data:
#!/bin/bash
yum -y update
yum install -y ruby
yum install -y aws-cli
aws s3 cp s3://aws-codedeploy-eu-west-2/latest/install . --region eu-west-2
chmod +x ./install
./install auto
curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
yum install -y nodejs
Gotcha: The User Data script often fails silently. After launch, SSH in and manually verify the CodeDeploy agent is running.
Move your key and set permissions:
mv ~/Downloads/your-key.pem ~/.ssh/
chmod 400 ~/.ssh/your-key.pem
SSH into EC2:
ssh -i ~/.ssh/your-key.pem ec2-user@<EC2-PUBLIC-IP>
Verify CodeDeploy agent:
sudo service codedeploy-agent status
If not running, install manually:
sudo yum install -y ruby wget
wget https://aws-codedeploy-eu-west-2.s3.eu-west-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent start
Install Node.js on EC2:
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install -y nodejs
node --version
Step 4: Set Up CodeDeploy
-
AWS Console → CodeDeploy → Applications → Create application
- Name:
acebook-app - Platform: EC2/On-premises
- Name:
-
Create deployment group:
- Name:
production - Service role:
acebook-codedeploy-role - Deployment type: In place
- Environment: Amazon EC2 instances → Key=
Application, Value=acebook - Deployment settings:
CodeDeployDefault.AllAtOnce - Uncheck Enable load balancing
- Name:
Gotcha: If you get a load balancer error, scroll up and uncheck “Enable load balancing” — it is checked by default.
Gotcha: The EC2 tag key/value must exactly match what you set when launching the instance. If CodeDeploy can’t find your instance, check the tags.
Phase 3: Codebase Changes
appspec.yml
Create in the project root:
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/acebook
overwrite: true
permissions:
- object: /home/ec2-user/acebook
owner: ec2-user
group: ec2-user
hooks:
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 10
runas: ec2-user
AfterInstall:
- location: scripts/install_dependencies.sh
timeout: 300
runas: ec2-user
ApplicationStart:
- location: scripts/start_server.sh
timeout: 30
runas: ec2-user
Deployment Scripts
Create a scripts/ folder in the project root:
mkdir scripts
touch scripts/stop_server.sh scripts/install_dependencies.sh scripts/start_server.sh
scripts/stop_server.sh:
#!/bin/bash
pkill -f "node.*bin/www" || true
scripts/install_dependencies.sh:
#!/bin/bash
cd /home/ec2-user/acebook
npm ci --omit=dev
scripts/start_server.sh:
#!/bin/bash
cd /home/ec2-user/acebook
export MONGODB_URL="mongodb://localhost:27017/acebook"
nohup node ./bin/www > /tmp/acebook.log 2>&1 &
Make scripts executable:
chmod +x scripts/stop_server.sh scripts/install_dependencies.sh scripts/start_server.sh
Fix MongoDB Test Helper
Update spec/mongodb_helper.js line 4 to use the environment variable:
mongoose.connect(process.env.MONGODB_URL || "mongodb://0.0.0.0/acebook_test", {
This allows GitHub Actions to pass in its own MongoDB URL during CI.
Phase 4: CI/CD Workflow
GitHub Secrets
Go to your repo → Settings → Secrets and variables → Actions and add:
| Secret | Value |
|---|---|
AWS_ACCESS_KEY_ID | From IAM user |
AWS_SECRET_ACCESS_KEY | From IAM user |
AWS_REGION | eu-west-2 |
S3_BUCKET | Your bucket name |
CODEDEPLOY_APP_NAME | acebook-app |
CODEDEPLOY_GROUP_NAME | production |
GitHub Actions Workflow
Create .github/workflows/ci-cd.yml:
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
services:
mongodb:
image: mongo:7
ports:
- 27017:27017
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '23'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Unit tests
run: npm run test:unit
env:
MONGODB_URL: mongodb://localhost:27017/acebook_test
deploy:
name: Deploy to EC2
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Package and upload revision
run: |
zip -r revision.zip . \
--exclude "*.git*" \
--exclude "node_modules/*" \
--exclude "cypress/*"
aws s3 cp revision.zip \
s3://${{ secrets.S3_BUCKET }}/revision-${{ github.sha }}.zip
- name: Trigger CodeDeploy deployment
run: |
aws deploy create-deployment \
--application-name ${{ secrets.CODEDEPLOY_APP_NAME }} \
--deployment-group-name ${{ secrets.CODEDEPLOY_GROUP_NAME }} \
--s3-location bucket=${{ secrets.S3_BUCKET }},key=revision-${{ github.sha }}.zip,bundleType=zip \
--description "Deploy commit ${{ github.sha }}"
Gotcha: YAML indentation must be consistent — use 2 spaces throughout. Never mix tabs and spaces. If pasting into the GitHub editor, change the tab size to 2 first.
Gotcha: The
servicesblock must be inside thetestjob at the same indentation level assteps.
Gotcha: If the
AWS_SECRET_ACCESS_KEYsecret gives a “5 slash-delimited elements” error, you pasted the wrong value. Delete the access key in IAM and create a new one.
Common Issues
| Problem | Cause | Fix |
|---|---|---|
nvm: command not found | Shell not reloaded | Run source ~/.zshrc |
| MongoDB won’t start via brew | macOS permission issue | Use mongod --dbpath ... --fork instead |
| Tests timeout in GitHub Actions | No MongoDB service | Add services: mongodb block to workflow |
| CodeDeploy agent not found | User data script failed | SSH in and install manually |
| EC2 instance not in deployment group | Tag mismatch | Check EC2 tags exactly match CodeDeploy config |
AccessDenied in GitHub Actions | Wrong IAM credentials | Recreate access key and update GitHub Secrets |
| App not visible on port 3000 | Security group missing rule | EC2 → Security groups → add TCP 3000 inbound |
appspec.yml not found | File not committed | Verify appspec.yml is in repo root and committed |
Submission Checklist
- Architecture diagram (
.jpg) showing GitHub → Actions → S3 → CodeDeploy → EC2 - CI job passes on every push/PR
- CD job deploys automatically on merge to
main - App accessible at
http://<EC2-PUBLIC-IP>:3000 - Codebase zipped including
.github/workflows/,appspec.yml,scripts/