<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[From Render to AWS How I Built a Production-Grade Backend with CI/CD for Kaizen]]></title><description><![CDATA[From Render to AWS How I Built a Production-Grade Backend with CI/CD for Kaizen]]></description><link>https://kaizen-production.hashnode.dev</link><image><url>https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/logos/685b9abdc854507dbb29feab/3ef7ae88-ca22-4fad-b3d2-92f97d5c74a7.jpg</url><title>From Render to AWS How I Built a Production-Grade Backend with CI/CD for Kaizen</title><link>https://kaizen-production.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 07:57:08 GMT</lastBuildDate><atom:link href="https://kaizen-production.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Render to AWS
How I Built a Production-Grade Backend
with CI/CD for Kaizen]]></title><description><![CDATA[My main project, Kaizen a habit and productivity tracker had its backend running on Render’s free tier. For a while, it worked fine. But there was one…]]></description><link>https://kaizen-production.hashnode.dev/from-render-to-aws-how-i-built-a-production-grade-backend-with-ci-cd-for-kaizen</link><guid isPermaLink="true">https://kaizen-production.hashnode.dev/from-render-to-aws-how-i-built-a-production-grade-backend-with-ci-cd-for-kaizen</guid><dc:creator><![CDATA[Sai Surve]]></dc:creator><pubDate>Fri, 20 Feb 2026 18:00:51 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/685b9abdc854507dbb29feab/685bd4cd-dd9c-437a-bbfa-794b22d62cac.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>The Problem That Started Everything</strong></h3>
<p>My main project, <strong>Kaizen</strong> a habit and productivity tracker had its backend running on Render’s free tier.</p>
<p>For a while, it worked fine. But there was one critical issue:</p>
<p>The server went to sleep after <strong>15 minutes of inactivity</strong>. Every time a user opened the app after inactivity, they faced a <strong>cold start</strong> sometimes waiting <strong>30–50 seconds</strong> for the backend to wake up. That’s unacceptable for any production app.</p>
<p>For a discipline-focused product, it felt ironic.</p>
<p>A friend suggested AWS EC2 not just to fix the issue, but to gain real-world DevOps exposure. AWS also offers a 12-month free tier.</p>
<p>So I decided to do it properly. No shortcuts. No half-measures.</p>
<hr />
<h2><strong>Part 1: Setting Up the AWS EC2 Instance</strong></h2>
<p>I created an <strong>Ubuntu 22.04 LTS EC2 instance (t2.micro, free tier eligible).</strong></p>
<h3><strong>Key Steps</strong></h3>
<ul>
<li><p>Selected Ubuntu 22.04 LTS AMI</p>
</li>
<li><p>Created a .pem key pair for SSH access</p>
</li>
<li><p>Configured <strong>Security Groups</strong></p>
</li>
</ul>
<p>Security groups act as cloud firewalls.</p>
<p>I configured:</p>
<ul>
<li><p>Port 22 (SSH) → Allowed only from my IP</p>
</li>
<li><p>Port 80 (HTTP) → Public</p>
</li>
<li><p>Port 443 (HTTPS) → Public</p>
</li>
<li><p>Port 3000 → <strong>Blocked from public access</strong></p>
</li>
</ul>
<p>This ensures backend services are not directly exposed.</p>
<h3><strong>Connecting via SSH</strong></h3>
<pre><code class="language-plaintext">ssh -i "my-key.pem" ubuntu@&lt;ec2-public-ip&gt;
</code></pre>
<p>This was my first time managing a production Linux server directly.</p>
<hr />
<h2><strong>Part 2: Deploying the Node.js Backend with systemd</strong></h2>
<p>To run the backend persistently, I evaluated two options:</p>
<ul>
<li><p>PM2 (Node.js process manager)</p>
</li>
<li><p>systemd (Linux native process manager)</p>
</li>
</ul>
<p>I chose <strong>systemd</strong> because:</p>
<ul>
<li><p>Native to Linux</p>
</li>
<li><p>No external dependency</p>
</li>
<li><p>Starts on reboot</p>
</li>
<li><p>Auto restarts on crash</p>
</li>
<li><p>Cleaner integration with OS-level logging</p>
</li>
</ul>
<h3><strong>Service File</strong></h3>
<p>Created at:</p>
<pre><code class="language-plaintext">/etc/systemd/system/kaizen-backend.service
</code></pre>
<pre><code class="language-plaintext">[Unit]
Description=Kaizen Backend Node.js Service
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/kaizen-backend
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
EnvironmentFile=/home/ubuntu/kaizen-backend/.env

[Install]
WantedBy=multi-user.target
</code></pre>
<h3><strong>Enable and Start</strong></h3>
<pre><code class="language-plaintext">sudo systemctl daemon-reload
sudo systemctl enable kaizen-backend
sudo systemctl start kaizen-backend
</code></pre>
<p>Now the backend:</p>
<ul>
<li><p>Runs in the background</p>
</li>
<li><p>Restarts automatically on crash</p>
</li>
<li><p>Starts on server reboot</p>
</li>
<li><p>Loads environment variables securely</p>
</li>
</ul>
<p>This is how real services operate in production.</p>
<hr />
<h2><strong>Part 3: Nginx Reverse Proxy + HTTPS</strong></h2>
<p>Exposing port 3000 publicly is a security risk.</p>
<p>So I installed <strong>Nginx</strong> as a reverse proxy.</p>
<p>All external traffic hits port 80 or 443.</p>
<p>Nginx forwards requests internally to localhost:3000.</p>
<h3><strong>Nginx Configuration</strong></h3>
<pre><code class="language-plaintext">server {
    listen 80;
    server_name api.kaizen-tracker.in;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
</code></pre>
<h3><strong>Enabling HTTPS with Certbot</strong></h3>
<pre><code class="language-plaintext">sudo certbot --nginx -d api.kaizen-tracker.in
</code></pre>
<p>Certbot:</p>
<ul>
<li><p>Configured SSL automatically</p>
</li>
<li><p>Set up HTTP → HTTPS redirect</p>
</li>
<li><p>Issued a valid Let’s Encrypt certificate</p>
</li>
</ul>
<p>Backend is now live at:</p>
<pre><code class="language-plaintext">https://api.kaizen-tracker.in
</code></pre>
<hr />
<h2><strong>Part 4: Monitoring with CloudWatch</strong></h2>
<p>Monitoring is part of production not an afterthought.</p>
<p>I configured two AWS CloudWatch alarms:</p>
<ul>
<li><p>CPU Utilization &gt; 15%</p>
</li>
<li><p>StatusCheckFailed ≥ 1</p>
</li>
</ul>
<p>Now I get notified if:</p>
<ul>
<li><p>The server is overloaded</p>
</li>
<li><p>The instance health fails</p>
</li>
</ul>
<p>Issues are detected before users complain.</p>
<hr />
<h2><strong>Part 5: CI/CD Pipeline with GitHub Actions</strong></h2>
<p>Manual deployment is fragile.</p>
<p>Goal:</p>
<p>Every push to main (backend folder only) should automatically deploy to EC2.</p>
<h3><strong>Repository Secrets Added</strong></h3>
<ul>
<li><p>EC2_HOST</p>
</li>
<li><p>EC2_SSH_KEY</p>
</li>
<li><p>EC2_USERNAME</p>
</li>
</ul>
<h3><strong>Pipeline Flow</strong></h3>
<p>On every qualifying push:</p>
<ol>
<li><p>Back up live code</p>
</li>
<li><p>Deploy updated backend files</p>
</li>
<li><p>Restart systemd service</p>
</li>
<li><p>Run health check</p>
</li>
<li><p>Auto rollback if failure</p>
</li>
</ol>
<hr />
<h2><strong>Backup Before Deployment</strong></h2>
<pre><code class="language-plaintext">cp -r /home/ubuntu/kaizen-backend \
/home/ubuntu/kaizen-backup-$(date +%Y%m%d%H%M%S)
</code></pre>
<hr />
<h2><strong>Deploy and Restart</strong></h2>
<pre><code class="language-plaintext">cd /home/ubuntu/kaizen-backend
git fetch origin main
git checkout origin/main -- backend/
npm install --production
sudo systemctl restart kaizen-backend
</code></pre>
<hr />
<h2><strong>Health Check + Auto Rollback</strong></h2>
<pre><code class="language-plaintext">sleep 5
if systemctl is-active --quiet kaizen-backend; then
  echo "Deployment successful."
else
  LATEST_BACKUP=$(ls -td /home/ubuntu/kaizen-backup-* | head -1)
  rm -rf /home/ubuntu/kaizen-backend
  cp -r $LATEST_BACKUP /home/ubuntu/kaizen-backend
  sudo systemctl restart kaizen-backend
  echo "Rollback complete."
  exit 1
fi
</code></pre>
<p>If deployment fails, the server restores the previous working state automatically.</p>
<p>That’s production-grade resilience.</p>
<hr />
<h1><strong>The 10-Second Switch</strong></h1>
<p>After everything was configured, migrating the frontend was one line:</p>
<pre><code class="language-plaintext">NEXT_PUBLIC_API_URL=https://api.kaizen-tracker.in
</code></pre>
<p>Redeploy frontend.</p>
<p>Done.</p>
<p>From the outside: 10 seconds.</p>
<p>From the inside: a full week of infrastructure engineering.</p>
<hr />
<h1><strong>Key Takeaways</strong></h1>
<h3><strong>AWS EC2</strong></h3>
<p>Hands-on cloud infrastructure teaches more than any theory.</p>
<p>Understanding:</p>
<ul>
<li><p>Security groups</p>
</li>
<li><p>SSH</p>
</li>
<li><p>Cloud monitoring</p>
</li>
<li><p>Server architecture</p>
</li>
</ul>
<p>is essential for backend engineers.</p>
<hr />
<h3><strong>systemd</strong></h3>
<p>Real services in Linux don’t rely on ad-hoc scripts.</p>
<p>They run as managed system services.</p>
<hr />
<h3><strong>Nginx</strong></h3>
<p>Reverse proxy + SSL termination is a standard production pattern.</p>
<hr />
<h3><strong>CI/CD + Rollback</strong></h3>
<p>Automation with health checks separates hobby projects from real systems.</p>
<hr />
<h3><strong>Security Mindset</strong></h3>
<ul>
<li><p>Never expose internal ports</p>
</li>
<li><p>Restrict SSH access</p>
</li>
<li><p>Always enforce HTTPS</p>
</li>
<li><p>Store secrets securely</p>
</li>
</ul>
<hr />
<h1><strong>Try Kaizen</strong></h1>
<p>The backend is now:</p>
<ul>
<li><p>Always-on</p>
</li>
<li><p>Monitored</p>
</li>
<li><p>Auto-deploying</p>
</li>
<li><p>Self-healing on failure</p>
</li>
</ul>
<p>You can try it here:</p>
<p>👉 <a href="https://www.kaizentracker.in">https://www.kaizentracker.in</a></p>
<hr />
<h1><strong>Sources &amp; References</strong></h1>
<ul>
<li><p>AWS EC2 Deployment Guide</p>
<p><a href="https://youtu.be/nQdyiK7-VlQ?si=sR6PuvzS75CJ7nho">https://youtu.be/nQdyiK7-VlQ?si=sR6PuvzS75CJ7nho</a></p>
</li>
<li><p>GitHub DevOps Deployment Reference</p>
<p><a href="https://github.com/TusharVashishth/Devops/blob/main/nodejs-backend/deploy.md">https://github.com/TusharVashishth/Devops/blob/main/nodejs-backend/deploy.md</a></p>
</li>
</ul>
<hr />
<p>If you have questions about the setup, feel free to connect on LinkedIn or drop a comment.</p>
<p>This sprint pushed me from “writing backend code” to understanding infrastructure ownership.</p>
<p>And that shift changes everything.</p>
]]></content:encoded></item></channel></rss>