<?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: Opatola Bolaji Prince</title>
    <description>The latest articles on Forem by Opatola Bolaji Prince (@prince27).</description>
    <link>https://forem.com/prince27</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%2F1200766%2F5acdc290-f042-4027-9d74-7a64cd99344f.png</url>
      <title>Forem: Opatola Bolaji Prince</title>
      <link>https://forem.com/prince27</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/prince27"/>
    <language>en</language>
    <item>
      <title>Case Study: Deploying a Full-Stack MERN Application on AWS EC2 Using Docker &amp; GitHub Actions</title>
      <dc:creator>Opatola Bolaji Prince</dc:creator>
      <pubDate>Wed, 29 Oct 2025 12:54:37 +0000</pubDate>
      <link>https://forem.com/prince27/case-study-deploying-a-full-stack-mern-application-on-aws-ec2-using-docker-github-actions-4ea4</link>
      <guid>https://forem.com/prince27/case-study-deploying-a-full-stack-mern-application-on-aws-ec2-using-docker-github-actions-4ea4</guid>
      <description>&lt;p&gt;If you’ve ever built a full-stack app and asked yourself:&lt;/p&gt;

&lt;p&gt;“How do I deploy this professionally in production with DevOps best practices?”&lt;/p&gt;

&lt;p&gt;This case study walks through exactly how I achieved that — step-by-step — including the challenges, mistakes, and real fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project Overview&lt;/strong&gt;  I built and deployed a production-grade full-stack note-taking application called Notify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technologies involved:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React + Vite for the frontend&lt;/li&gt;
&lt;li&gt;Node.js + Express for the backend&lt;/li&gt;
&lt;li&gt;MongoDB as the database&lt;/li&gt;
&lt;li&gt;Docker &amp;amp; Docker Compose for containerization&lt;/li&gt;
&lt;li&gt;AWS EC2 for hosting&lt;/li&gt;
&lt;li&gt;GitHub Actions CI/CD for automated deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt;  Push code → App auto-deploys to AWS → No manual server changes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phase 1 — Dockerizing the MERN Application
The first step was packaging each part of the application into its own container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend 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:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["npm", "start"]

Frontend Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend is built and then served using Nginx — making it production-ready.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Phase 2 — Docker Compose for Multi-Service Orchestration
To run all three containers together and ensure they communicate properly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  mongo:
    image: mongo:6
    container_name: mongo-db
    ports:
      - "27017:27017"
    restart: always

  backend:
    build: ./backend
    env_file: ./backend/.env
    environment:
      - MONGODB_URI=mongodb://mongo:27017/notifydb
    ports:
      - "5000:5000"
    depends_on:
      - mongo

  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt;&lt;br&gt;
 The backend connects to MongoDB using the container name mongo, not localhost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Phase 3 — Deploying to AWS EC2&lt;br&gt;
I launched an Amazon Linux 2023 EC2 instance and prepared it for containerized deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed Docker Compose&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added ec2-user to Docker group&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security groups configured correctly&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opened inbound ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80 → Web traffic&lt;/li&gt;
&lt;li&gt;5000 → Backend API&lt;/li&gt;
&lt;li&gt;22 → SSH Access&lt;/li&gt;
&lt;li&gt;Then deployed:&lt;/li&gt;
&lt;li&gt;git clone &lt;a href="https://github.com/emperorbj/notify.git" rel="noopener noreferrer"&gt;https://github.com/emperorbj/notify.git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;cd notify&lt;/li&gt;
&lt;li&gt;docker compose up -d --build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎉 Site instantly reachable via:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Phase 4 — Enabling CI/CD With GitHub Actions
The goal was zero manual deployments after setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📌 Every time I push to main:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; EC2 pulls new code&lt;/li&gt;
&lt;li&gt; Docker rebuilds&lt;/li&gt;
&lt;li&gt; Containers restart automatically&lt;/li&gt;
&lt;li&gt; Old images removed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub Actions workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy to EC2

on:
  push:
    branches: ["main"]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: SSH Agent
        uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Add Known Hosts
        run: ssh-keyscan -H ${{ secrets.EC2_HOST }} &amp;gt;&amp;gt; ~/.ssh/known_hosts

      - name: Deploy Over SSH
        run: |
          ssh ec2-user@${{ secrets.EC2_HOST }} &amp;lt;&amp;lt; 'EOF'
            cd ~/notify
            git pull origin main
            docker compose down || true
            docker compose up -d --build
            docker image prune -f
          EOF

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

&lt;/div&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;Real-World Problems I Had to Solve&lt;/strong&gt; &lt;br&gt;
This wasn’t smooth — and that’s exactly the valuable part.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Problems + Fixes:
Backend couldn’t connect to MongoDB → switched URI to container name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment failing due to wrong Docker version → updated Docker + enabled compose&lt;/p&gt;

&lt;p&gt;Frontend wouldn’t load → exposed port 80, not Vite’s local 5173&lt;/p&gt;

&lt;p&gt;SSH denied → fixed key pairing and GitHub secrets&lt;/p&gt;

&lt;p&gt;Docker commands failing → added EC2 user to Docker group&lt;/p&gt;

&lt;p&gt;Every issue taught me lessons developers don’t learn until production ✨&lt;/p&gt;

&lt;p&gt;You can watch the process here :&lt;br&gt;
&lt;a href="https://www.loom.com/share/897f49af1b274e3fba6921c842d0653b" rel="noopener noreferrer"&gt;https://www.loom.com/share/897f49af1b274e3fba6921c842d0653b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 What This Project Proved&lt;/p&gt;

&lt;p&gt;I now understand:&lt;/p&gt;

&lt;p&gt;🔥 Container orchestration with Docker&lt;br&gt;
🔥 Cloud deployment using AWS EC2&lt;br&gt;
🔥 Networking between services in production&lt;br&gt;
🔥 Continuous Deployment pipelines&lt;br&gt;
🔥 Debugging real infrastructure failures&lt;/p&gt;

&lt;p&gt;This project took me from “I can code this” to:&lt;/p&gt;

&lt;p&gt;✅ “I can run this in production — automatically.”&lt;/p&gt;

&lt;p&gt;✅ Final Result ✅&lt;/p&gt;

&lt;p&gt;✔ Full MERN App running live on AWS&lt;br&gt;
✔ 100% Dockerized microservices&lt;br&gt;
✔ Push-to-deploy automation&lt;br&gt;
✔ Secure, scalable architecture&lt;br&gt;
✔ Excellent DevOps experience gained&lt;/p&gt;

&lt;p&gt;This is now a foundation I can reuse for multiple future projects&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>docker</category>
      <category>devops</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
