← Guides

Acebook AWS CI/CD Guide

AWSCI/CDGitHub ActionsEC2CodeDeployNode.js August 2026

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 nvm says command not found after install, you need to reload your shell with source ~/.zshrc before 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.0 often fails with Bootstrap failed: 5: Input/output error. Use the mongod --fork command 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

  1. AWS Console → S3 → Create bucket
  2. Name: acebook-<team-name>-deployments (must be globally unique, lowercase)
  3. Region: eu-west-2
  4. Block all public access: leave checked
  5. Create bucket

Step 2: Create IAM Roles

Role 1 — EC2 Instance Profile:

  1. IAM → Roles → Create role
  2. Trusted entity: AWS service → EC2
  3. Attach: AmazonS3ReadOnlyAccess
  4. Name: acebook-ec2-role

Role 2 — CodeDeploy Service Role:

  1. IAM → Roles → Create role
  2. Trusted entity: AWS service → CodeDeploy
  3. AWS auto-attaches AWSCodeDeployRole
  4. Name: acebook-codedeploy-role

GitHub Actions IAM User:

  1. IAM → Users → Create user
  2. Name: acebook-github-actions
  3. No console access
  4. Attach: AmazonS3FullAccess + AWSCodeDeployFullAccess
  5. After creating: Security credentials → Create access key → Application running outside AWS
  6. 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

  1. EC2 → Launch Instance
  2. Name tag: Key=Application, Value=acebook
  3. AMI: Amazon Linux 2023 (Free Tier)
  4. Instance type: t2.micro
  5. Key pair: create new → RSA → .pem → save it safely
  6. Security group: allow SSH (port 22) + Custom TCP port 3000 from anywhere
  7. Advanced Details → IAM instance profile: acebook-ec2-role
  8. 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

  1. AWS Console → CodeDeploy → Applications → Create application

    • Name: acebook-app
    • Platform: EC2/On-premises
  2. 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

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:

SecretValue
AWS_ACCESS_KEY_IDFrom IAM user
AWS_SECRET_ACCESS_KEYFrom IAM user
AWS_REGIONeu-west-2
S3_BUCKETYour bucket name
CODEDEPLOY_APP_NAMEacebook-app
CODEDEPLOY_GROUP_NAMEproduction

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 services block must be inside the test job at the same indentation level as steps.

Gotcha: If the AWS_SECRET_ACCESS_KEY secret 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

ProblemCauseFix
nvm: command not foundShell not reloadedRun source ~/.zshrc
MongoDB won’t start via brewmacOS permission issueUse mongod --dbpath ... --fork instead
Tests timeout in GitHub ActionsNo MongoDB serviceAdd services: mongodb block to workflow
CodeDeploy agent not foundUser data script failedSSH in and install manually
EC2 instance not in deployment groupTag mismatchCheck EC2 tags exactly match CodeDeploy config
AccessDenied in GitHub ActionsWrong IAM credentialsRecreate access key and update GitHub Secrets
App not visible on port 3000Security group missing ruleEC2 → Security groups → add TCP 3000 inbound
appspec.yml not foundFile not committedVerify 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/