<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Shongkor Talukdar</title>
    <description>The latest articles on Forem by Shongkor Talukdar (@shongkor_talukdar).</description>
    <link>https://forem.com/shongkor_talukdar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1652778%2F54ba620c-8220-4d80-ab38-96a5bd0ca273.png</url>
      <title>Forem: Shongkor Talukdar</title>
      <link>https://forem.com/shongkor_talukdar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shongkor_talukdar"/>
    <language>en</language>
    <item>
      <title>Addressing Single Point of Failure Concerns</title>
      <dc:creator>Shongkor Talukdar</dc:creator>
      <pubDate>Sun, 01 Mar 2026 16:51:19 +0000</pubDate>
      <link>https://forem.com/shongkor_talukdar/addressing-single-point-of-failure-concerns-3g0g</link>
      <guid>https://forem.com/shongkor_talukdar/addressing-single-point-of-failure-concerns-3g0g</guid>
      <description>&lt;p&gt;The risk of a Single Point of Failure (SPOF) has become a critical concern in interconnected modern businesses and technologies. The concept represents a part of a system that, if it fails, will stop the entire system from working. It can be software, hardware, human resources, or any aspect critical to operations. For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## FrontEnd ----&amp;gt; Backend(Deployed on single EC2) ----&amp;gt; DataBase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we deploy a backend on one server, when we add a feature, and when we deploy it, that causes an interruption for the active end user. Think about streaming platforms like Netflix, e-commarce system like Amazon. A single moment of failure will cost millions of dollars.&lt;/p&gt;

&lt;p&gt;Avoiding a single point of failure (SPOF) in cloud-based systems is critical for ensuring high availability, fault tolerance, and resilience. To mitigate the risks associated with SPOF, systems should be designed with redundancy and fault tolerance in mind. Here are some best practices to minimize the risk of SPOFs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redundant Components and Geographic Distribution:&lt;/strong&gt; Enhance the resilience of cloud architecture by implementing redundancy for all critical components, such as servers, databases, and load balancers.&lt;/p&gt;

&lt;p&gt;Today, I am going to develop a pipeline that will mitigate SPOF for the Backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5y2337jivk4mz96g6to.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5y2337jivk4mz96g6to.jpeg" alt=" " width="797" height="1143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, NGINX acts as a load balancer, distributing user requests to both servers. This Architecture will solve :&lt;br&gt;
&lt;strong&gt;Redundancy:&lt;/strong&gt; If Instance 1 fails during the update, Instance 2 is still live, serving user requests.&lt;br&gt;
&lt;strong&gt;Automatic Rollback:&lt;/strong&gt; If the deployment fails (e.g., the app fails to start on Instance 1), CodeDeploy can be configured to automatically roll back to the previous version, ensuring no permanent downtime.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2q0g82xa95hd6zvuzinb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2q0g82xa95hd6zvuzinb.png" alt=" " width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deployment flow in this context will work as follows. GitHub Actions builds and pushes your Docker image to Docker Hub as it does today. It then notifies CodeDeploy to begin a deployment. CodeDeploy pulls your repository code (specifically the appspec.yml and deployment scripts) from an S3 bucket, then executes the deployment on each EC2 instance in a rolling fashion — one instance at a time — so your application remains available throughout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer pushes code to GitHub
         ↓
GitHub Actions (Build &amp;amp; Test)
         ↓
Docker Image pushed to Docker Hub
         ↓
Deployment bundle uploaded to S3
         ↓
CodeDeploy triggered
         ↓
Instance A deregistered from ALB
         ↓
Scripts run on Instance A (stop old → pull new → start → validate)
         ↓
Instance A re-registered to ALB
         ↓
Same process repeats for Instance B
         ↓
Both instances running the latest code ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 1 — Project Folder Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trust-estate-server/
├── .github/
│   └── workflows/
│       └── pipeline.yml
├── scripts/
│   ├── before_install.sh
│   ├── after_install.sh
│   ├── start_application.sh
│   └── validate_service.sh
├── src/
│   └── index.js
├── .env.example
├── .gitignore
├── appspec.yml
├── docker-compose.yml
├── Dockerfile
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 2 — Application Files
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.use(express.json())

// Health check endpoint for ALB and CodeDeploy
app.get('/health', (req, res) =&amp;gt; {
  res.status(200).json({
    status: 'healthy',
    app: 'trust-estate-server',
    timestamp: new Date().toISOString()
  })
})

app.get('/', (req, res) =&amp;gt; {
  res.status(200).json({
    message: 'Trust Estate API is running',
    version: '1.0.0'
  })
})

app.listen(port, () =&amp;gt; {
  console.log(`Trust Estate Server running on port ${port}`)
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then &lt;/p&gt;

&lt;p&gt;&lt;code&gt;.env.example&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=3000
NODE_ENV=production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/
.env
.env.*
!.env.example
*.log
coverage/
dist/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 3 — Docker Files
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Dockerfile&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --omit=dev

COPY . .

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "src/index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  node-app:
    image: ${DOCKER_USERNAME}/trust-estate-server:latest
    container_name: trust-estate-server
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 4 — CodeDeploy Files
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;appspec.yml&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/app
    overwrite: true

permissions:
  - object: /home/ubuntu/app/scripts
    pattern: "**"
    owner: ubuntu
    group: ubuntu
    mode: 755
    type:
      - file

hooks:
  BeforeInstall:
    - location: scripts/before_install.sh
      timeout: 300
      runas: root

  AfterInstall:
    - location: scripts/after_install.sh
      timeout: 300
      runas: root

  ApplicationStart:
    - location: scripts/start_application.sh
      timeout: 300
      runas: root

  ValidateService:
    - location: scripts/validate_service.sh
      timeout: 300
      runas: root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next for the Scripts Folder &lt;/p&gt;

&lt;p&gt;&lt;code&gt;scripts/before_install.sh&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

echo "========================================="
echo "BEFORE INSTALL - Cleanup old deployment"
echo "========================================="

echo "Stopping existing container..."
docker stop trust-estate-server || true

echo "Removing existing container..."
docker rm trust-estate-server || true

echo "Removing old Docker images..."
docker rmi $(docker images -q) -f || true

echo "Cleanup complete."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scripts/after_install.sh&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

echo "========================================="
echo "AFTER INSTALL - Pulling new Docker image"
echo "========================================="

cd /home/ubuntu/app

echo "Pulling latest image..."
docker pull $DOCKER_USERNAME/trust-estate-server:latest

echo "Image pulled successfully."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scripts/validate_service.sh&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

echo "========================================="
echo "VALIDATE SERVICE - Health check"
echo "========================================="

MAX_RETRIES=10
RETRY_INTERVAL=10
HEALTH_URL="http://localhost:3000/health"

echo "Checking health at: $HEALTH_URL"

for i in $(seq 1 $MAX_RETRIES); do
  echo "Attempt $i of $MAX_RETRIES..."

  HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL)

  if [ "$HTTP_STATUS" -eq 200 ]; then
    echo "✓ Health check passed - trust-estate-server is healthy"
    exit 0
  fi

  echo "✗ Status: $HTTP_STATUS. Retrying in ${RETRY_INTERVAL}s..."
  sleep $RETRY_INTERVAL
done

echo "✗ trust-estate-server failed health checks after $MAX_RETRIES attempts"
exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 5 — GitHub Actions Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/pipeline.yml&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: CI/CD Pipeline - Trust Estate Server

on:
  push:
    branches: ['main']

env:
  DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/trust-estate-server

jobs:

  build:
    name: Build &amp;amp; Push Docker Image
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKER_HUB_TOKEN }}" | \
          docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Build Docker Image
        run: |
          docker build \
            --tag ${{ env.DOCKER_IMAGE }}:latest \
            --tag ${{ env.DOCKER_IMAGE }}:${{ github.sha }} \
            .

      - name: Push Docker Image
        run: |
          docker push ${{ env.DOCKER_IMAGE }}:latest
          docker push ${{ env.DOCKER_IMAGE }}:${{ github.sha }}

  deploy:
    name: Deploy to EC2 via CodeDeploy
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Create Deployment Bundle
        run: |
          zip -r deployment-bundle.zip . \
            -x "*.git*" \
            -x "node_modules/*" \
            -x ".env*" \
            -x ".github/*"

      - name: Upload Bundle to S3
        run: |
          aws s3 cp deployment-bundle.zip \
            s3://${{ secrets.S3_BUCKET_NAME }}/deployments/deployment-${{ github.sha }}.zip

      - name: Trigger CodeDeploy Deployment
        run: |
          DEPLOYMENT_ID=$(aws deploy create-deployment \
            --application-name trust-estate \
            --deployment-group-name production-group \
            --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=deployments/deployment-${{ github.sha }}.zip \
            --deployment-config-name CodeDeployDefault.OneAtATime \
            --description "Commit: ${{ github.sha }} by ${{ github.actor }}" \
            --query 'deploymentId' \
            --output text)

          echo "Deployment ID: $DEPLOYMENT_ID"
          echo "DEPLOYMENT_ID=$DEPLOYMENT_ID" &amp;gt;&amp;gt; $GITHUB_ENV

      - name: Wait for Deployment to Complete
        run: |
          echo "Waiting for deployment ${{ env.DEPLOYMENT_ID }}..."
          aws deploy wait deployment-successful \
            --deployment-id ${{ env.DEPLOYMENT_ID }}
          echo "✓ Deployment completed successfully."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now,&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;&lt;a href="https://github.com" rel="noopener noreferrer"&gt;https://github.com&lt;/a&gt;&lt;/strong&gt; and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Click + (top right)
→ New Repository
→ Repository name: trust-estate-server
→ Visibility: Public or Private (your choice)
→ Do NOT check "Add README"
→ Click Create Repository

Push Your Code to GitHub
In your terminal, **inside the trust-estate-server folder**:

`
bash# Initialise git
git init

# Stage all files
git add .

# First commit
git commit -m "initial project setup with CI/CD pipeline"

# Connect to your GitHub repo
# Replace YOUR_USERNAME with your actual GitHub username
git remote add origin https://github.com/YOUR_USERNAME/trust-estate-server.git

# Push to main branch
git branch -M main
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to GitHub and refresh the page. You should see all your files there.&lt;/p&gt;




&lt;h3&gt;
  
  
  Next Step — Add GitHub Secrets
&lt;/h3&gt;

&lt;p&gt;This is where you store all sensitive values so they never appear in your code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub → Your Repository
       → Settings (top menu)
       → Secrets and Variables (left sidebar)
       → Actions
       → New Repository Secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add these secrets &lt;strong&gt;one by one&lt;/strong&gt;. Click &lt;strong&gt;New Repository Secret&lt;/strong&gt; for each one:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret 1:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  DOCKER_USERNAME
Value: your Docker Hub username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret 2:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  DOCKER_HUB_TOKEN
Value: the token you copied in Step A2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret 3:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  AWS_REGION
Value: eu-west-1
       (use YOUR actual region — check your EC2 instances)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret 4:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  S3_BUCKET_NAME
Value: leave this empty for now
       (you will come back and add this after Step D3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The AWS credential secrets (&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, &lt;code&gt;AWS_SESSION_TOKEN&lt;/code&gt;) will be added after you get them from the Learner Lab in Part D.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Next PART — AWS SETUP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;(This is the biggest part — take it one step at a time)&lt;/em&gt;
&lt;/h3&gt;




&lt;h3&gt;
  
  
  Step on AWS-1 — Get Your AWS Credentials from Learner Lab
&lt;/h3&gt;

&lt;p&gt;Open your &lt;strong&gt;AWS Academy Learner Lab&lt;/strong&gt; and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Click: Start Lab (wait until the circle goes green)
Click: AWS Details
Click: Show (next to AWS CLI)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see three values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws_access_key_id     = ASIA...
aws_secret_access_key = xxxx...
aws_session_token     = xxxx... (very long string)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy each one and go back to &lt;strong&gt;GitHub → Settings → Secrets → Actions&lt;/strong&gt; and add:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret 5:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  AWS_ACCESS_KEY_ID
Value: paste aws_access_key_id value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret 6:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  AWS_SECRET_ACCESS_KEY
Value: paste aws_secret_access_key value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret 7:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:  AWS_SESSION_TOKEN
Value: paste aws_session_token value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt; Every time you start a new Learner Lab session, you must update these 3 secrets with fresh values.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Now, enter your AWS homepage. Our next step is create two instances using a single security group.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Order We Will Follow
&lt;/h2&gt;

&lt;p&gt;Step 1: Create Security Group      ← Rules for what traffic is allowed&lt;br&gt;
Step 2: Create EC2 Instance A      ← First server&lt;br&gt;
Step 3: Create EC2 Instance B      ← Second server&lt;br&gt;
Step 4: Install software on both   ← Docker + CodeDeploy agent&lt;br&gt;
Step 5: Create Target Group        ← Groups both servers together&lt;br&gt;
Step 6: Create Load Balancer       ← Distributes traffic to both servers&lt;br&gt;
Step 7: Create S3 Bucket           ← Stores deployment files&lt;br&gt;
Step 8: Create CodeDeploy App      ← Manages deployments&lt;br&gt;
Step 9: Tag EC2 Instances          ← Labels for CodeDeploy to find them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_**STEP 1 — Create a Security Group**_

A Security Group is a firewall that controls what traffic can reach your EC2 instances. Think of it as a bouncer at a door — it decides who gets in and who does not.Navigate to Security Groups
AWS Console → EC2
           → Security Groups (left sidebar, under Network &amp;amp; Security)
           → Create Security Group
Add Inbound Rules

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inbound rules control traffic coming into your server. Click Add Rule for each of the following:&lt;br&gt;
   Rule 1 — SSH (for you to connect to the server):&lt;br&gt;
       Type:        SSH&lt;br&gt;
       Protocol:    TCP&lt;br&gt;
       Port:        22&lt;br&gt;
       Source:      My IP&lt;br&gt;
       (AWS automatically fills your current IP address)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rule 2 — HTTP (for web traffic):

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type:        HTTP&lt;br&gt;
Protocol:    TCP&lt;br&gt;
Port:        80&lt;br&gt;
Source:      Anywhere IPv4  (0.0.0.0/0)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rule 3 — Your App Port:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type:        Custom TCP&lt;br&gt;
Protocol:    TCP&lt;br&gt;
Port:        3000&lt;br&gt;
Source:      Anywhere IPv4  (0.0.0.0/0)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Outbound Rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Leave outbound rules as the default. The default allows all outbound traffic, which your instances need to pull Docker images and communicate with AWS services. Click &lt;strong&gt;Create the Security Group&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;STEP 2 — Create EC2 Instance A&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Navigate to EC2 Launch&lt;br&gt;
&lt;code&gt;AWS Console → EC2&lt;br&gt;
           → Instances (left sidebar)&lt;br&gt;
           → Launch Instances (orange button, top right)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 1 — Name and Tags&lt;br&gt;
&lt;code&gt;Name: trust-estate-server-A&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 2 — Application and OS Image (AMI)&lt;br&gt;
&lt;code&gt;Search: Ubuntu&lt;br&gt;
Select: Ubuntu Server 22.04 LTS (HVM), SSD Volume Type&lt;br&gt;
        (make sure it says Free tier eligible)&lt;br&gt;
Architecture: 64-bit (x86)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 3 — Instance Type&lt;br&gt;
&lt;code&gt;Select: t2.micro&lt;br&gt;
        (Free tier eligible — enough for learning and testing)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 4 — Key Pair&lt;br&gt;
This is how you SSH into your server. This is very important — do not skip this.&lt;br&gt;
&lt;code&gt;Click: Create new key pair&lt;br&gt;
A dialog box appears:&lt;br&gt;
Key pair name: trust-estate-key&lt;br&gt;
Key pair type: RSA&lt;br&gt;
Private key file format: .pem  (for Mac/Linux)&lt;br&gt;
                          .ppk  (for Windows with PuTTY)&lt;br&gt;
Click Create key pair. A file will automatically download to your computer.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Save this file somewhere safe. If you lose it, you can never SSH into your instances again. Put it in a folder you will remember, such as ~/aws-keys/trust-estate-key.pem.&lt;/p&gt;

&lt;p&gt;Section 5 — Network Settings&lt;br&gt;
&lt;code&gt;Click Edit on the Network Settings section:&lt;br&gt;
VPC:                     default VPC&lt;br&gt;
Subnet:                  select any available subnet&lt;br&gt;
                         (note which Availability Zone it is in&lt;br&gt;
                          e.g. eu-west-1a)&lt;br&gt;
Auto-assign public IP:   Enable&lt;br&gt;
For Firewall (Security Groups):&lt;br&gt;
Select: Select existing security group&lt;br&gt;
Choose: trust-estate-sg  (the one you created in Step 1)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 6 — Configure Storage&lt;br&gt;
&lt;code&gt;Size: 8 GiB  (default is fine)&lt;br&gt;
Type: gp2&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Section 7 — Advanced Details&lt;br&gt;
`Scroll all the way down to Advanced Details. Find the IAM instance profile field:&lt;br&gt;
IAM instance profile: LabRole&lt;/p&gt;

&lt;p&gt;This gives your EC2 instance permission to communicate with CodeDeploy and S3.`&lt;/p&gt;

&lt;p&gt;Launch Instance A&lt;br&gt;
Click &lt;strong&gt;Launch Instance&lt;/strong&gt;. AWS will show a success screen with your Instance ID. Click View All Instances to go back to the instances list.&lt;br&gt;
Wait until Instance A shows:&lt;br&gt;
&lt;code&gt;Instance State:  running&lt;br&gt;
Status Checks:   2/2 checks passed&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  STEP 3 — Create EC2 Instance B
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repeat the exact same process as Step 2 with these differences:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;`Name:   trust-estate-server-B&lt;br&gt;
Subnet: select a DIFFERENT subnet from Instance A&lt;br&gt;
        (different Availability Zone e.g. eu-west-1b)&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
Why a &lt;strong&gt;different subnet&lt;/strong&gt;? Placing instances in different Availability Zones means if one AWS data centre has a problem, your other instance in a different location keeps running. This is what makes your application highly available.&lt;/p&gt;
&lt;h2&gt;
  
  
  STEP 4 — Install Software on Both Instances
&lt;/h2&gt;

&lt;p&gt;Get access to both of your instances(instance A &amp;amp; instance B) and install the required software. &lt;br&gt;
&lt;strong&gt;&lt;code&gt;Install CodeDeploy Agent&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Update the package list
sudo apt-get update -y

# Install required tools
sudo apt-get install ruby wget curl -y

# Move to home directory
cd /home/ubuntu

# Download the CodeDeploy installer
# IMPORTANT: Replace eu-west-1 with YOUR actual AWS region
wget https://aws-codedeploy-eu-west-1.s3.eu-west-1.amazonaws.com/latest/install

# Make it executable
chmod +x ./install

# Run the installer
sudo ./install auto

# Start the CodeDeploy agent service
sudo systemctl start codedeploy-agent

# Make it start automatically when instance reboots
sudo systemctl enable codedeploy-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now verify it is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl status codedeploy-agent

You should see this output:
● codedeploy-agent.service - LSB: AWS CodeDeploy Host Agent
   Active: active (running) since ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Install Docker&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Download and run the official Docker install script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add ubuntu user to docker group
# This allows running docker without sudo
sudo usermod -aG docker ubuntu

# Install Docker Compose plugin
sudo apt-get install docker-compose-plugin -y

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Docker works:&lt;br&gt;
&lt;code&gt;docker --version&lt;br&gt;
docker compose version&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Next — Create the Target Group
&lt;/h2&gt;

&lt;p&gt;Where are we right now?????&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Step 1: Security Group created
✓ Step 2: EC2 Instance A created
✓ Step 3: EC2 Instance B created
✓ Step 4: Software installed on both instances
→ Step 5: Create Target Group          ← YOU ARE HERE
  Step 6: Create Load Balancer
  Step 7: Create S3 Bucket
  Step 8: Create CodeDeploy Application
  Step 9: Tag EC2 Instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Target Group is simply a list of your servers that the Load Balancer will send traffic to. Think of it like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lgf7eckfpxi4847vy86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lgf7eckfpxi4847vy86.png" alt=" " width="643" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;****Navigate to Target Groups&lt;br&gt;
         &lt;code&gt;AWS Console → EC2&lt;br&gt;
           → Scroll down the LEFT sidebar&lt;br&gt;
           → Under "Load Balancing"&lt;br&gt;
           → Click: Target Groups&lt;br&gt;
           → Click: Create Target Group (top right)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Page 1 — Basic Configuration&lt;br&gt;
You will see a form. Fill in each field exactly:&lt;br&gt;
Choose a target type:&lt;br&gt;
● Instances     ← select this one&lt;br&gt;
○ IP addresses&lt;br&gt;
○ Lambda function&lt;br&gt;
○ Application Load Balancer&lt;br&gt;
Target group name:&lt;br&gt;
trust-estate-tg&lt;br&gt;
Protocol:&lt;br&gt;
HTTP&lt;br&gt;
Port:&lt;br&gt;
3000&lt;/p&gt;

&lt;p&gt;This must be 3000 because your Express app runs on port 3000 inside Docker.&lt;/p&gt;

&lt;p&gt;IP address type:&lt;br&gt;
IPv4&lt;br&gt;
VPC:&lt;br&gt;
Select: default VPC&lt;br&gt;
        (the same VPC your EC2 instances are in)&lt;br&gt;
Protocol version:&lt;br&gt;
HTTP1&lt;/p&gt;

&lt;p&gt;Health Checks Section&lt;br&gt;
This is very important. The Load Balancer uses health checks to decide if an instance is healthy enough to receive traffic.&lt;br&gt;
Health check protocol: HTTP&lt;br&gt;
Health check path:     /health&lt;br&gt;
Now click Advanced health check settings to expand it:&lt;br&gt;
Port:                  Traffic port&lt;br&gt;
Healthy threshold:     2&lt;br&gt;
Unhealthy threshold:   2&lt;br&gt;
Timeout:               5&lt;br&gt;
Interval:              30&lt;br&gt;
Success codes:         200&lt;/p&gt;

&lt;p&gt;You should see it finally like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│ Health check path:  /health         │
│ Healthy threshold:  2 checks        │
│ Unhealthy threshold: 2 checks       │
│ Timeout:            5 seconds       │
│ Interval:           30 seconds      │
│ Success codes:      200             │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page 2&lt;/strong&gt; — Register Targets&lt;br&gt;
This is where you add your two EC2 instances into the group.&lt;br&gt;
You will see a table listing your available instances:&lt;/p&gt;

&lt;p&gt;Assign(marked checked) both these instances to the target group named &lt;code&gt;trust-estate-tg&lt;/code&gt; .&lt;br&gt;
then Click &lt;strong&gt;Create Target Group&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Next step is to create the Application Load Balancer
&lt;/h2&gt;

&lt;p&gt;Navigate to Load Balancers&lt;br&gt;
&lt;code&gt;AWS Console → EC2&lt;br&gt;
           → Left sidebar under "Load Balancing"&lt;br&gt;
           → Click: Load Balancers&lt;br&gt;
           → Click: Create Load Balancer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 1 — Basic Configuration&lt;br&gt;
&lt;code&gt;Load balancer name: trust-estate-alb&lt;br&gt;
Scheme:             Internet-facing&lt;br&gt;
                    ← This means it accepts traffic from the internet&lt;br&gt;
IP address type:    IPv4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 2 — Network Mapping&lt;br&gt;
&lt;code&gt;VPC:&lt;br&gt;
Select: default VPC&lt;br&gt;
Availability Zones and Subnets:&lt;br&gt;
You must select at least two Availability Zones. This is a requirement for a Load Balancer.&lt;br&gt;
☑ eu-west-1a  → select the subnet shown&lt;br&gt;
☑ eu-west-1b  → select the subnet shown&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Match these to the Availability Zones where you launched your EC2 instances. You can check this by going to EC2 → Instances and looking at the Availability Zone column.&lt;/p&gt;

&lt;p&gt;Section 3 — Security Groups&lt;br&gt;
&lt;code&gt;Remove: default (click the X next to it)&lt;br&gt;
Add:    trust-estate-sg  ← the one you created earlier&lt;br&gt;
Click in the security groups dropdown and select trust-estate-sg.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Section 4 — Listeners and Routing&lt;br&gt;
&lt;code&gt;This tells the Load Balancer what to do with incoming traffic:&lt;br&gt;
Protocol: HTTP&lt;br&gt;
Port:     80&lt;br&gt;
Default action: Forward to → trust-estate-tg&lt;/code&gt;&lt;br&gt;
Your listener should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────┐
│ HTTP : 80  →  Forward to: trust-estate-tg      │
└────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Section 5 — Summary and Create&lt;br&gt;
Leave everything else as default. Scroll to the bottom and click Create Load Balancer.&lt;br&gt;
AWS will show a success screen. Click View Load Balancer.&lt;/p&gt;

&lt;p&gt;Save Your Load Balancer DNS Name&lt;br&gt;
Click on trust-estate-alb in the list. In the details panel below, find:&lt;br&gt;
&lt;code&gt;DNS name: trust-estate-alb-123456789.eu-west-1.elb.amazonaws.com&lt;/code&gt;&lt;br&gt;
Copy and save this URL. This is the public address of your application. Once everything is deployed, this is what you open in the browser to see your app running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the S3 Bucket
&lt;/h2&gt;

&lt;p&gt;Navigate to S3&lt;br&gt;
&lt;code&gt;AWS Console → S3&lt;br&gt;
           → Create Bucket (top right)&lt;/code&gt;&lt;br&gt;
AWS Region:&lt;br&gt;
Select the SAME region your EC2 instances are in&lt;br&gt;
Object Ownership:&lt;br&gt;
ACLs disabled (default)&lt;br&gt;
Block Public Access:&lt;br&gt;
☑ Block all public access  ← leave this ON&lt;br&gt;
Versioning:&lt;br&gt;
Disable (default)&lt;br&gt;
Everything else leave as default. &lt;strong&gt;Click Create Bucket.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Update Your GitHub Secret&lt;br&gt;
Now go back to GitHub and update the S3 secret:&lt;br&gt;
&lt;code&gt;GitHub → trust-estate-server repository&lt;br&gt;
       → Settings&lt;br&gt;
       → Secrets and Variables&lt;br&gt;
       → Actions&lt;br&gt;
       → Find S3_BUCKET_NAME&lt;br&gt;
       → Click Edit (pencil icon)&lt;br&gt;
       → Enter your bucket name&lt;br&gt;
       → Click Save&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the CodeDeploy Application
&lt;/h2&gt;

&lt;p&gt;Navigate to CodeDeploy&lt;br&gt;
AWS Console → search "CodeDeploy" in the top search bar&lt;br&gt;
           → Click: CodeDeploy&lt;br&gt;
           → Click: Applications (left sidebar)&lt;br&gt;
           → Click: Create Application&lt;br&gt;
&lt;strong&gt;Fill in the Details&lt;/strong&gt;&lt;br&gt;
Application name: trust-estate&lt;br&gt;
Compute platform: EC2/On-Premises&lt;br&gt;
Click &lt;strong&gt;Create Application.&lt;/strong&gt;&lt;br&gt;
Now &lt;code&gt;Create the Deployment Group&lt;/code&gt;&lt;br&gt;
You will be taken inside the trust-estate application. Click:&lt;br&gt;
&lt;strong&gt;Create Deployment Group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fill in each section:&lt;br&gt;
Section 1 — Name and Role:&lt;br&gt;
Deployment group name: production-group&lt;br&gt;
Service role:          LabRole&lt;/p&gt;

&lt;p&gt;Click the dropdown for Service role and search for LabRole.&lt;/p&gt;

&lt;p&gt;Section 2 — Deployment Type:&lt;br&gt;
● In-place        ← select this&lt;br&gt;
○ Blue/green&lt;br&gt;
Section 3 — Environment Configuration:&lt;br&gt;
● Amazon EC2 instances   ← select this&lt;/p&gt;

&lt;p&gt;Tag group 1:&lt;br&gt;
Key:   App&lt;br&gt;
Value: trust-estate&lt;br&gt;
After typing the tag, you should see:&lt;br&gt;
&lt;code&gt;1 unique matched instance&lt;/code&gt;&lt;br&gt;
Wait — this shows only 1 because you have not tagged both instances yet. That is fine. We do the tagging in Step 9 and come back to verify.&lt;/p&gt;

&lt;p&gt;Section 4 — Agent Configuration:&lt;br&gt;
Install AWS CodeDeploy Agent: Never&lt;/p&gt;

&lt;p&gt;Select Never because you already installed it manually on both instances.&lt;/p&gt;

&lt;p&gt;Section 5 — Deployment Settings:&lt;br&gt;
Deployment configuration: CodeDeployDefault.OneAtATime&lt;br&gt;
Section 6 — Load Balancer:&lt;br&gt;
☑ Enable load balancing    ← check this box&lt;/p&gt;

&lt;p&gt;Load balancer:&lt;br&gt;
● Application Load Balancer or Network Load Balancer&lt;/p&gt;

&lt;p&gt;Target group: trust-estate-tg&lt;br&gt;
Click Create Deployment Group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tag Both EC2 Instances&lt;/strong&gt;&lt;br&gt;
Now you attach the labels that CodeDeploy uses to find your instances.&lt;/p&gt;

&lt;p&gt;Tag Instance A&lt;br&gt;
AWS Console → EC2&lt;br&gt;
           → Instances&lt;br&gt;
           → Click: trust-estate-server-A&lt;br&gt;
At the bottom of the screen you will see tabs:&lt;br&gt;
Details | Security | Networking | Storage | Status checks | Monitoring | Tags&lt;br&gt;
Click the Tags tab:&lt;br&gt;
Click: Manage Tags&lt;br&gt;
Click: Add Tag&lt;/p&gt;

&lt;p&gt;Key:   App&lt;br&gt;
Value: trust-estate&lt;/p&gt;

&lt;p&gt;Click: Save&lt;/p&gt;

&lt;p&gt;Tag Instance B&lt;br&gt;
Go back to Instances list&lt;br&gt;
Click: trust-estate-server-B&lt;br&gt;
Tags tab → Manage Tags → Add Tag&lt;/p&gt;

&lt;p&gt;Key:   App&lt;br&gt;
Value: trust-estate&lt;/p&gt;

&lt;p&gt;Click: Save&lt;/p&gt;

&lt;p&gt;Verify CodeDeploy Found Both Instances&lt;br&gt;
CodeDeploy → Applications → trust-estate&lt;br&gt;
           → production-group&lt;br&gt;
           → Click Edit (top right)&lt;br&gt;
           → Scroll to Environment Configuration&lt;br&gt;
You should now see:&lt;br&gt;
Key:   App&lt;br&gt;
Value: trust-estate&lt;br&gt;
Matching instances: 2   ✓&lt;br&gt;
If you see 2 — everything is connected correctly.&lt;/p&gt;

&lt;p&gt;Now &lt;/p&gt;

&lt;h2&gt;
  
  
  Congratulation
&lt;/h2&gt;

&lt;p&gt;You have completed your CI/CD pipeline with CodeDeploy. Now, if you push your code to the main branch of your repository. You can see, in the action section github action is working automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j0ehkgk8lugwwn81bbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j0ehkgk8lugwwn81bbo.png" alt=" " width="630" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github Link : &lt;a href="https://github.com/Shongkor/trust-estate-server/actions/runs/22524715828" rel="noopener noreferrer"&gt;https://github.com/Shongkor/trust-estate-server/actions/runs/22524715828&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>docker</category>
      <category>backend</category>
    </item>
    <item>
      <title>Deploy a Node Application on an AWS EC2 Instance using CI/CD Pipeline.</title>
      <dc:creator>Shongkor Talukdar</dc:creator>
      <pubDate>Sun, 22 Feb 2026 20:12:22 +0000</pubDate>
      <link>https://forem.com/shongkor_talukdar/deploy-node-application-on-an-ec2-instance-using-cicd-pipeline-3c8h</link>
      <guid>https://forem.com/shongkor_talukdar/deploy-node-application-on-an-ec2-instance-using-cicd-pipeline-3c8h</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This is a minimal Node.js/Express backend server. The application itself is straightforward — a single Express 5 app (index.js) that listens on port 3000 and returns "Hello World in AWS EC2!" at the root route. The real substance of the project lies in its deployment infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;The pipeline is split across two GitHub Actions workflows that chain together to form a complete automated delivery process from code commit to live deployment on AWS EC2.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F618d0syac16d5bcljo78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F618d0syac16d5bcljo78.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
There are two parts of these settings. In your root directory, create a file named &lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
COPY . .
RUN npm install
EXPOSE 3000
RUN ["chmod", "+x", "./entrypoint.sh"]
ENTRYPOINT [ "sh","./entrypoint.sh" ]

`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For best practice, run your project using the entrypoint.sh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 #!/bin/bash
 node index.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then in order to set DOCKER_HUB_TOKEN &amp;amp; DOCKER_USER_NAME , &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
First register on &lt;a href="https://app.docker.com" rel="noopener noreferrer"&gt;https://app.docker.com&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a repository named the same as Github repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to settings and generate a new access token and copy that token.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to your created GitHub repository settings section. In the &lt;strong&gt;secret and variable&lt;/strong&gt; section, you will find &lt;strong&gt;Action&lt;/strong&gt; and create a &lt;strong&gt;repository secret key&lt;/strong&gt; named DOCKER_HUB_TOKEN --&amp;gt; paste your copied dockerhub tocken. In the same way, create another variable named DOCKER_USER_NAME --&amp;gt; copy the username of your Dockerhub account and paste here.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stage 1 — Build and Publish to Docker Hub (docker_registry.yml)&lt;/strong&gt;&lt;br&gt;
This workflow triggers on every push to the main branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(docker_registry.yml)
`
name: Publish Docker Image to Docker Hub

on:
  push:
    branches: ['main']

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Login to docker hub
        run: echo "${{secrets.DOCKER_HUB_TOKEN}}" | docker login -u "${{secrets.DOCKER_USER_NAME}}" --password-stdin

      - name: Build Docker Image
        run: docker build . --file Dockerfile --tag "${{secrets.DOCKER_USER_NAME}}"/study-abroad-agency-server:latest

      - name: Push Docker Image
        run: docker push "${{secrets.DOCKER_USER_NAME}}"/study-abroad-agency-server:latest

`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs on a GitHub-hosted Ubuntu runner and performs three steps: it checks out the code, logs into Docker Hub using stored secrets (DOCKER_HUB_TOKEN and DOCKER_USER_NAME), builds a Docker image from the Dockerfile, and pushes it to Docker Hub tagged as latest.&lt;br&gt;
The Dockerfile itself is lean — it uses the node:18-alpine base image, copies the application files, installs dependencies via npm install, exposes port 3000, and delegates startup to entrypoint.sh, which simply runs node index.js.&lt;/p&gt;

&lt;p&gt;Finally, you need to set up your newly created AWS EC2 instance. Get access to your instance. Install Docker on your EC2 instance by following &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04" rel="noopener noreferrer"&gt;https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Following this documentation, you can install Docker on your EC2 server. Then:&lt;br&gt;
using the below command logIn to your Docker that is installed on your EC2 server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo passwd ${USER}

Set a password for using Docker on your EC2 for administration(don't need to use sudo for executing commands)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, before you get into the next stage. In your GitHub project repository, go to the &lt;strong&gt;Settings ** section, find the **Action&lt;/strong&gt; from the sidebar, click on &lt;strong&gt;Runners&lt;/strong&gt;, and create a new self-hosted runner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tygtq5tro36sh0758m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tygtq5tro36sh0758m6.png" alt=" " width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then run all of those commands that you found from the GitHub self-hosted runner section on your EC2 instance. &lt;br&gt;
Note: when you run the last command from GitHub self-hosted&lt;br&gt;
&lt;code&gt;./run.sh&lt;/code&gt;&lt;br&gt;
This will accept your code as long as the terminal is open. If you want that functionality all the time, whenever you push to GitHub, the runner will accept the code and execute the command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;br&gt;
sudo ./svc.sh install&lt;br&gt;
sudo ./svc.sh start&lt;br&gt;
sudo ./svc.sh status&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
You will see listening for job.&lt;br&gt;
&lt;strong&gt;Stage 2 — Deploy to AWS EC2 (awsec2deploy.yml)&lt;/strong&gt;&lt;br&gt;
This workflow is triggered not by a git event, but by the completion of the first workflow (workflow_run trigger).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(awsec2deploy.yml)

`
name: Publish Docker Images to AWS EC2

on:
  workflow_run:
    workflows: ['Publish Docker Image']
    types: [completed]

jobs:
  build:
    runs-on: self-hosted

    steps:
      - name: Stop the old container
        run: docker stop study-abroad-agency-server || true

      - name: Delete the old container
        run: docker rm study-abroad-agency-server || true

      - name: Delete the old image
        run: docker rmi ${{secrets.DOCKER_USER_NAME}}/study-abroad-agency-server:latest || true

      - name: Pull the image from dockerhub
        run: docker pull ${{secrets.DOCKER_USER_NAME}}/study-abroad-agency-server:latest

      - name: Run the image
        run: docker compose up -d

`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key architectural decision — it creates a sequential dependency between the two pipelines, ensuring deployment only proceeds after a successful image publish.&lt;br&gt;
This workflow runs on a self-hosted runner, meaning GitHub Actions is executing directly on the EC2 instance itself. This is how it achieves direct deployment without needing SSH or additional tooling like CodeDeploy. The steps follow a clean replace-and-redeploy pattern: stop and remove the old container, delete the old image, pull the fresh latest image from Docker Hub, and finally bring the container back up using docker compose up -d.&lt;br&gt;
The docker-compose.yml is minimal — it references the published image, names the container, and uses network_mode: host so the container shares the EC2 instance's network stack directly, making the app accessible on port 3000 without port mapping.&lt;/p&gt;

&lt;p&gt;Runner will show an error "no configuration file provided" when deploying on EC2. This is for not having the compose file. On your EC2 root. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano docker-compose.yml&lt;/code&gt;&lt;br&gt;
It will open an input terminal push the below code on that file&lt;br&gt;
&lt;code&gt;&lt;br&gt;
services:&lt;br&gt;
  app:&lt;br&gt;
    image: shongkor/study-abroad-agency-server:latest&lt;br&gt;
    container_name: study-abroad-agency-server&lt;br&gt;
    network_mode: 'host'&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
for save --&amp;gt;  ctrl + X then Ctrl + c&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foosah4zt45lh8qt6fd35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foosah4zt45lh8qt6fd35.png" alt=" " width="682" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-End Flow
&lt;/h2&gt;

&lt;p&gt;In summary, a developer pushing to main sets off the following chain:&lt;br&gt;
&lt;strong&gt;_Git push → GitHub Actions builds Docker image → Pushes to Docker Hub → EC2 self-hosted runner pulls new image → Restarts container via Docker Compose&lt;br&gt;
This is a practical and cost-effective CI/CD setup for a single-server deployment. _&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main trade-off is that network_mode: host and a self-hosted runner introduce some operational coupling to the specific EC2 instance, which would need to be revisited if the architecture scaled to multiple instances or required zero-downtime deployments.&lt;/p&gt;

&lt;p&gt;Github Code link : [&lt;a href="https://github.com/Shongkor/study-abroad-agency-server" rel="noopener noreferrer"&gt;https://github.com/Shongkor/study-abroad-agency-server&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>docker</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>try/catch/finally,Arrow-Function,Semicolon,Coding-Style,Comment,Spread operator,Caching,Cross Browser Testing,Data Types.</title>
      <dc:creator>Shongkor Talukdar</dc:creator>
      <pubDate>Mon, 10 Mar 2025 17:25:42 +0000</pubDate>
      <link>https://forem.com/shongkor_talukdar/trycatchfinallyarrow-functionsemicoloncoding-stylecommentspread-operatorcachingcross-1660</link>
      <guid>https://forem.com/shongkor_talukdar/trycatchfinallyarrow-functionsemicoloncoding-stylecommentspread-operatorcachingcross-1660</guid>
      <description>&lt;p&gt;&lt;strong&gt;Caching:&lt;/strong&gt;&lt;br&gt;
Consuming APIs is every day's meal in JavaScript, this also has its limitations, amount of requests per second is the most common, in this case, we are going to implement an algorithm to cache data based on time. Let's say we are sure that the data we are going to fetch is not going to change in a certain period of time then we can cache the data for that span. Some data can be cached by seconds, minutes, or even days.&lt;br&gt;
&lt;strong&gt;Spread Operator:&lt;/strong&gt;&lt;br&gt;
Three very powerful dots, that allow you to expand arrays or iterable objects in places where array elements or key-value pairs would be expected.&lt;br&gt;
const arr1 = ['one', 'two'];&lt;br&gt;
const arr2 = [...arr1, 'three', 'four', 'five'];//['one', 'two','three', 'four', 'five']&lt;br&gt;
&lt;strong&gt;Comment:&lt;/strong&gt;&lt;br&gt;
Comments in JavaScript enable the developer to add notes about the script or comment out sections of script that should not be executed by the interpreter. &lt;/p&gt;

&lt;p&gt;Comments can be single line comments (using the // marker) &lt;br&gt;
or &lt;br&gt;
multi-line (beginning with /* and ending with */).&lt;br&gt;
Commenting is considered to be good practice. Regardless of how well you understand the logic of some JavaScript, there is a good chance you may one day have to return to that script and modify it. Often this can be months, or even years later and what seemed obvious to you at the time you wrote it may seem less obvious in the future. Also, it is often likely that some other person will have to work on your scripts in the future. For both these reasons it is a good idea to provide at least some basic amount of commenting in your scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coding Style:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnahjimcuyjfnsofijgdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnahjimcuyjfnsofijgdp.png" alt="Here is a summery with some suggested rules" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From: JAVASCRIP.INFO&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semicolons:&lt;/strong&gt;&lt;br&gt;
Semicolons should be written after each line of statement.&lt;br&gt;
Arrow functions:&lt;br&gt;
The core structure looks like this:&lt;br&gt;
(argument1, argument2, ... argumentN) =&amp;gt; {&lt;br&gt;
  // function body&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;try/catch/finally:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lrorcfdkgl36mlypupc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lrorcfdkgl36mlypupc.png" alt="try/catch/finally" width="582" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;try/catch/finally syntax:&lt;br&gt;
=&amp;gt; try...catch only works for runtime errors.&lt;br&gt;
=&amp;gt; try...catch works synchronously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross Browser Testing:&lt;/strong&gt;&lt;br&gt;
Cross-browser testing is simply what its name means - that is, to test your website or application in multiple browsers- and making sure that it works consistently and as in intended without any dependencies, or compromise in Quality.&lt;br&gt;
&lt;strong&gt;Data Types:&lt;/strong&gt;&lt;br&gt;
JavaScript has total nine types of data or you may say values in two major categories. The primitive types and the Objects and Functions. Primitive types are values which we can use only not modify them.&lt;br&gt;
Objects and Functions&lt;br&gt;
● Objects​ ({} and others), used to group related data and code.&lt;br&gt;
● Functions​ (x =&amp;gt; x * 2 and others), used to refer to code.&lt;br&gt;
Anything else than those above types falls into the Objects Category.&lt;br&gt;
Primitive Types&lt;br&gt;
● Undefined​ (undefined), used for unintentionally missing values.&lt;br&gt;
● Null​ (null), used for intentionally missing values.&lt;br&gt;
● Booleans​ (true and false), used for logical operations.&lt;br&gt;
● Numbers​ (-100, 3.14, and others), used for math calculations.&lt;br&gt;
● Strings​ ("hello", "something", and others), used for text.&lt;br&gt;
● Symbols​ (uncommon), used to hide implementation details.&lt;br&gt;
● BigInts​ (uncommon and new), used for math on big numbers.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
