<?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>DEV Community: Suleiman Abdulkadir</title>
    <description>The latest articles on DEV Community by Suleiman Abdulkadir (@suletete).</description>
    <link>https://dev.to/suletete</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%2F1134100%2F1bc50dbc-4e21-4e49-8a1e-abea00988ff6.png</url>
      <title>DEV Community: Suleiman Abdulkadir</title>
      <link>https://dev.to/suletete</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suletete"/>
    <language>en</language>
    <item>
      <title>I built a pipeline that rolls itself back when production breaks</title>
      <dc:creator>Suleiman Abdulkadir</dc:creator>
      <pubDate>Fri, 05 Jun 2026 22:04:55 +0000</pubDate>
      <link>https://dev.to/suletete/i-built-a-pipeline-that-rolls-itself-back-when-production-breaks-30f6</link>
      <guid>https://dev.to/suletete/i-built-a-pipeline-that-rolls-itself-back-when-production-breaks-30f6</guid>
      <description>&lt;p&gt;Deployments that break silently at night bother me. By the time someone checks Slack in the morning, users have been hitting 502s for hours. I built ShipGuard because I wanted the infrastructure to fix itself before I even knew something was wrong.&lt;/p&gt;

&lt;p&gt;It's a CodePipeline that does blue/green deployment with canary traffic shifting to EC2. If the new version starts returning 5xx errors, CodeDeploy shifts traffic back to the old version and kills the broken instances. I don't have to do anything.&lt;/p&gt;

&lt;p&gt;Three CloudFormation templates. Everything in source control. Nothing configured by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline flow
&lt;/h2&gt;

&lt;p&gt;Push to main. Everything after that is automatic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull source from GitHub&lt;/li&gt;
&lt;li&gt;npm audit, Trivy, git-secrets run first. High or critical vuln? Build dies.&lt;/li&gt;
&lt;li&gt;Tests run, TypeScript compiles, artifact gets packaged&lt;/li&gt;
&lt;li&gt;Deploy to staging (one instance, in-place)&lt;/li&gt;
&lt;li&gt;Email lands asking me to approve&lt;/li&gt;
&lt;li&gt;I approve. Blue/green starts on production.&lt;/li&gt;
&lt;li&gt;10% of traffic routes to the new version for 5 minutes&lt;/li&gt;
&lt;li&gt;CloudWatch alarm stays quiet? Remaining traffic shifts over.&lt;/li&gt;
&lt;li&gt;Old instances terminated. Done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the alarm fires during steps 7 or 8, traffic goes 100% back to the previous version. Green instances die. I get an email explaining which alarm triggered the rollback.&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%2Fpfxzsxsnq8nj4o7i0ln7.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%2Fpfxzsxsnq8nj4o7i0ln7.png" alt="ShipGuard Architecture" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bit me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TimeBasedCanary doesn't work for EC2
&lt;/h3&gt;

&lt;p&gt;I spent an afternoon trying to configure &lt;code&gt;TimeBasedCanary&lt;/code&gt; in a custom deployment config. CloudFormation accepted the template at lint time and then failed at deploy time with "Traffic routing configuration should be null for Server deployment configuration."&lt;/p&gt;

&lt;p&gt;Turns out canary percentage configs only exist for ECS and Lambda. For EC2, the ALB and target group weights handle traffic shifting, not a CodeDeploy config. Nowhere in the docs does it say this clearly; you just find out when it breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM role chain from hell
&lt;/h3&gt;

&lt;p&gt;Four roles. They all need to trust different AWS services, and they all need slightly different permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline role needs &lt;code&gt;iam:PassRole&lt;/code&gt; to hand off to CodeDeploy&lt;/li&gt;
&lt;li&gt;CodeBuild role needs S3 access to the artifact bucket plus CloudWatch Logs&lt;/li&gt;
&lt;li&gt;CodeDeploy role needs EC2, ASG, ALB, S3&lt;/li&gt;
&lt;li&gt;EC2 instance profile needs to pull from S3 and push CloudWatch metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Miss one permission and you get "Access Denied" with no indication of which call failed or which role is the problem. I iterated on this more times than I'd like to admit.&lt;/p&gt;

&lt;h3&gt;
  
  
  You need an AMI in your Launch Template
&lt;/h3&gt;

&lt;p&gt;This one's embarrassing. cfn-lint doesn't catch a missing &lt;code&gt;ImageId&lt;/code&gt; in a Launch Template. CloudFormation doesn't catch it either, until the ASG actually tries to spin up an instance and fails. The fix is an SSM parameter that resolves to the latest Amazon Linux 2023 AMI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;LatestAmiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SSM::Parameter::Value&amp;lt;AWS::EC2::Image::Id&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CodeStarNotifications has different tag syntax
&lt;/h3&gt;

&lt;p&gt;I deployed the pipeline stack three times before figuring this out. Every other resource in CloudFormation uses Tags as a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AWS::CodeStarNotifications::NotificationRule&lt;/code&gt; wants a map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudFormation gives you "Properties validation failed" with no hint about what's wrong. I found the answer buried in a GitHub issue after 20 minutes of searching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security scanning
&lt;/h2&gt;

&lt;p&gt;Three scans run before tests. If any exit non-zero, the build stops, and nothing reaches staging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;pre_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm audit --audit-level=high&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;trivy fs --severity HIGH, CRITICAL --exit-code &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git secrets --scan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No third-party service needed. npm audit is already there, Trivy downloads in a few seconds during install, and git-secrets is an AWS open source tool that's one clone away. The pipeline sends a notification identifying which scan killed the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rollback alarm
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Production5xxAlarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::CloudWatch::Alarm&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPCode_Target_5XX_Count&lt;/span&gt;
    &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS/ApplicationELB&lt;/span&gt;
    &lt;span class="na"&gt;Statistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sum&lt;/span&gt;
    &lt;span class="na"&gt;Period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;EvaluationPeriods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
    &lt;span class="na"&gt;TreatMissingData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notBreaching&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10 or more 5xx errors in 60 seconds trigger the alarm. CodeDeploy has &lt;code&gt;DEPLOYMENT_STOP_ON_ALARM&lt;/code&gt; in its rollback config, so it catches the alarm and reverses the deployment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TreatMissingData: notBreaching&lt;/code&gt; is worth noting. Without it, the alarm fires during periods with zero traffic (nights, weekends) because "no data" defaults to "assume breach." That caused a false rollback the first time I tested this on a weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change next time
&lt;/h2&gt;

&lt;p&gt;I'd probably use ECS Fargate. CodeDeploy's blue/green for ECS actually supports &lt;code&gt;TimeBasedCanary&lt;/code&gt; properly, so you can do true 10% -&amp;gt; 50% -&amp;gt; 100% shifts with observation windows between each step. EC2 blue/green is coarser. You get "new instances with traffic control," but the fine-grained percentage steps aren't natively there.&lt;/p&gt;

&lt;p&gt;I stuck with EC2 because I wanted to learn how the instance-level deployment mechanics work. Worth it for the education. Probably not what I'd pick for a real production system in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;Public: &lt;a href="https://github.com/suletetes/ShipGuard" rel="noopener noreferrer"&gt;github.com/suletetes/ShipGuard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three templates, one buildspec, one appspec, four shell scripts. Deploy staging first, then production, then pipeline. Push code. Pipeline picks it up.&lt;/p&gt;

&lt;p&gt;Costs about $45/month with everything running. ALBs are most of that ($16 each). Tear down staging when you're not testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CodePipeline, CodeBuild, CodeDeploy&lt;/li&gt;
&lt;li&gt;EC2 Auto Scaling behind ALBs&lt;/li&gt;
&lt;li&gt;CloudWatch alarms&lt;/li&gt;
&lt;li&gt;SNS&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;CloudFormation&lt;/li&gt;
&lt;li&gt;TypeScript/Express (the app being deployed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've done the "deploy a Lambda" tutorials and want something closer to what production infrastructure actually looks like, this hits the right problems. Cross-stack references, IAM chains, blue/green mechanics, alarm-driven automation. The stuff that takes four tries to get right and nobody warns you about in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deployed infrastructure
&lt;/h2&gt;

&lt;p&gt;Here's what the stacks actually create in AWS:&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFormation stacks
&lt;/h3&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%2F1yp9825xciq8uvk9o48b.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%2F1yp9825xciq8uvk9o48b.png" alt="CloudFormation Stacks" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC subnets (multi-AZ)
&lt;/h3&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%2Fxmrz07hdfdd202pxb6ot.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%2Fxmrz07hdfdd202pxb6ot.png" alt="Subnets" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Security groups
&lt;/h3&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%2F3yqh4v380kw3z76jg7yy.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%2F3yqh4v380kw3z76jg7yy.png" alt="Security Groups" width="800" height="430"&gt;&lt;/a&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%2Fd2y79a23dydqu1o139q0.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%2Fd2y79a23dydqu1o139q0.png" alt="Security Group Rules" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Load balancers
&lt;/h3&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%2Fjoiyz1eiz4o1flpikzrx.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%2Fjoiyz1eiz4o1flpikzrx.png" alt="ALB" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Target groups (blue and green)
&lt;/h3&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%2Fkd24ci62gik06scc5f84.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%2Fkd24ci62gik06scc5f84.png" alt="Target Groups" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto Scaling groups
&lt;/h3&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%2Fj0jn4i5rpvnvpzgxr1o3.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%2Fj0jn4i5rpvnvpzgxr1o3.png" alt="ASG" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  EC2 instances
&lt;/h3&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%2Fqtyvx446c3lkx3t2lbd5.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%2Fqtyvx446c3lkx3t2lbd5.png" alt="Instances" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 artifact bucket
&lt;/h3&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%2Fzwsia67c89xs7a0a75v9.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%2Fzwsia67c89xs7a0a75v9.png" alt="S3" width="799" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SNS topics
&lt;/h3&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%2Fg3igc3blm3qjrul7jtop.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%2Fg3igc3blm3qjrul7jtop.png" alt="SNS" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cicd</category>
      <category>cloudformation</category>
    </item>
    <item>
      <title>Migrating a MERN app to AWS serverless (and what broke)</title>
      <dc:creator>Suleiman Abdulkadir</dc:creator>
      <pubDate>Thu, 28 May 2026 21:02:34 +0000</pubDate>
      <link>https://dev.to/suletete/migrating-a-mern-app-to-aws-serverless-and-what-broke-318m</link>
      <guid>https://dev.to/suletete/migrating-a-mern-app-to-aws-serverless-and-what-broke-318m</guid>
      <description>&lt;p&gt;I built Taskly about a year ago. Standard MERN stack, ran on a $10/month VPS with PM2 and nginx. It worked fine. Nobody was complaining.&lt;/p&gt;

&lt;p&gt;I migrated it to AWS serverless anyway. Partly to learn, partly because I was mass applying to DevOps roles and needed something real to talk about in interviews. "I deployed a hello world Lambda" doesn't cut it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;Task management for small teams. Tasks, projects, teams, calendar, notifications, avatar uploads, productivity stats. About 15 API routes, 6 Mongoose models. React frontend with Context API nothing fancy but enough moving parts that the migration wasn't trivial.&lt;/p&gt;

&lt;p&gt;Original stack: Express, session auth, MongoDB, Cloudinary, Resend for emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I ended up
&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%2Ft0qx27ok2e0qztf2lh4x.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%2Ft0qx27ok2e0qztf2lh4x.png" alt=" " width="800" height="835"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Request flow: users hit CloudFront for the React app, WAF-filtered API Gateway for the backend. Lambda runs Express via serverless-express, talks to DocumentDB in a private VPC, pushes events to EventBridge, and queues emails through SQS to SES.&lt;/em&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%2F3ypo0lfdj43xttj7vq88.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%2F3ypo0lfdj43xttj7vq88.png" alt=" " width="800" height="804"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Network layer: VPC with public and private subnets across two AZs. Security groups restrict DocumentDB to Lambda only (port 27017). VPC endpoints for Secrets Manager. NAT gateway for outbound.&lt;/em&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%2Fb8w79g2vyopdwteuyps2.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%2Fb8w79g2vyopdwteuyps2.png" alt=" " width="800" height="612"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Deployment: GitHub Actions authenticates via OIDC (no stored keys), packages Lambda, does canary traffic shifting, monitors error rate, auto-rolls back if anything breaks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;VPC with private subnets. Security groups. NAT gateway. Secrets Manager. 12 Terraform modules, about 2000 lines of HCL. GitHub Actions CI/CD with OIDC auth and canary deployments.&lt;/p&gt;

&lt;p&gt;Took about 3 weeks of evenings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sessions broke immediately
&lt;/h2&gt;

&lt;p&gt;First thing. My Express app used express-session with a MongoDB store. Lambda spins up new instances per request. Sessions were just gone.&lt;/p&gt;

&lt;p&gt;I ended up with dual mode auth. Sessions for local dev (easy to debug, familiar), Cognito JWT for production (stateless, works with Lambda). The middleware checks which environment it's running in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COGNITO_USER_POOL_ID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COGNITO_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validateCognitoToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({...});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not elegant. Works.&lt;/p&gt;

&lt;h2&gt;
  
  
  DocumentDB is almost MongoDB
&lt;/h2&gt;

&lt;p&gt;95% compatible. The other 5% shows up at the worst times.&lt;/p&gt;

&lt;p&gt;Some aggregation stages behave differently. The connection needs a TLS certificate bundle you have to download from AWS and ship with your Lambda zip. I only caught these because I tested against the actual cluster, not just local Mongo.&lt;/p&gt;

&lt;p&gt;If I'd only tested locally, these would have been production bugs discovered at 2 am.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform got out of hand fast
&lt;/h2&gt;

&lt;p&gt;Started with one main.tf. Lasted a day.&lt;/p&gt;

&lt;p&gt;Split into modules: vpc, lambda, iam, s3, documentdb, waf, ses, api-gateway, cloudfront, secrets, monitoring, disaster-recovery. State in S3 with DynamoDB locking.&lt;/p&gt;

&lt;p&gt;The thing about Terraform: &lt;code&gt;plan&lt;/code&gt; says "2 to add, 0 to destroy" and you feel safe. Then &lt;code&gt;apply&lt;/code&gt; takes 15 minutes because NAT gateways are slow. And if it fails halfway, you get to learn about &lt;code&gt;terraform state rm&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security groups
&lt;/h2&gt;

&lt;p&gt;The mental model that clicked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda SG: egress all (needs DocumentDB, NAT, VPC endpoints)&lt;/li&gt;
&lt;li&gt;DocumentDB SG: ingress port 27017 from Lambda SG only&lt;/li&gt;
&lt;li&gt;VPC Endpoints SG: ingress port 443 from Lambda SG only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three groups. Database unreachable from internet. Lambda can reach database. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary deploys
&lt;/h2&gt;

&lt;p&gt;The CI/CD pipeline packages the code, uploads to S3, publishes a new Lambda version, shifts 10% of traffic to it, waits 5 minutes watching CloudWatch error metrics, and either promotes to 100% or rolls back.&lt;/p&gt;

&lt;p&gt;Saved me twice. Once from a missing env var, once from a dependency that worked locally but not in the Lambda runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change
&lt;/h2&gt;

&lt;p&gt;Skip the VPC for Lambda if possible. The ENI attachment adds cold start latency, and NAT gateways cost $32/month each. DocumentDB forces you into a VPC though, so I was stuck.&lt;/p&gt;

&lt;p&gt;Write smaller Terraform modules. My IAM module has 8 policies in one file. Should be separate.&lt;/p&gt;

&lt;p&gt;Set up CI/CD first, not last. I did manual deploys for weeks. Dumb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Old VPS: $10/month&lt;/li&gt;
&lt;li&gt;AWS serverless: ~$45/month (mostly NAT gateway and DocumentDB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More expensive. But I actually understand VPCs, IAM, security groups, and Terraform now. That's worth more than $35/month to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/suletetes/taskly" rel="noopener noreferrer"&gt;github.com/suletetes/taskly&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure in &lt;code&gt;infrastructure/&lt;/code&gt;, Lambda handler in &lt;code&gt;backend/lambda/handler.js&lt;/code&gt;, CI/CD in &lt;code&gt;.github/workflows/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're doing something similar, start with VPC and DocumentDB. They take the longest to provision and have the most surprises. Get those working, then add Lambda and API Gateway on top.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a SaaS Platform From Scratch. Here's How I Architected It on AWS.</title>
      <dc:creator>Suleiman Abdulkadir</dc:creator>
      <pubDate>Tue, 28 Apr 2026 00:03:29 +0000</pubDate>
      <link>https://dev.to/suletete/i-built-a-saas-platform-from-scratch-heres-how-i-architected-it-on-aws-5li</link>
      <guid>https://dev.to/suletete/i-built-a-saas-platform-from-scratch-heres-how-i-architected-it-on-aws-5li</guid>
      <description>&lt;p&gt;So I've been working on something for a while now. It's called TechVerse. It's a SaaS e-commerce platform, and I built the whole thing from the ground up using the MERN stack.&lt;/p&gt;

&lt;p&gt;I want to talk about the cloud architecture side of things. Not the textbook version. The real version. The one where you're staring at your screen at 2am trying to figure out why your WebSocket connections keep dropping, or why your API response times just spiked to 4 seconds.&lt;/p&gt;

&lt;p&gt;Let me walk you through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Was Trying to Solve
&lt;/h2&gt;

&lt;p&gt;Here's the situation. There are thousands of tech retailers in Nigeria who sell laptops, phones, accessories, all kinds of stuff. Most of them run their entire business from a physical shop. No website. No online store. Nothing.&lt;/p&gt;

&lt;p&gt;Why? Because getting a custom e-commerce site built costs a fortune. And the international platforms? They charge in dollars. That's a dealbreaker when your local currency fluctuates every other week.&lt;/p&gt;

&lt;p&gt;So I thought, what if I built a SaaS platform that lets these businesses spin up a professional online store for a fraction of the cost? Pricing in local currency. Optimized for local internet speeds. Local payment gateways baked right in.&lt;/p&gt;

&lt;p&gt;That was the idea. Now I had to figure out how to actually build and deploy it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Stack
&lt;/h2&gt;

&lt;p&gt;I went with what I know best. MongoDB, Express, React, Node. The classic MERN stack. But I made some specific choices that matter for production.&lt;/p&gt;

&lt;p&gt;React 19 with Vite 7 on the frontend. Vite is ridiculously fast for builds. The dev experience alone is worth it, but more importantly, the production bundles are tiny. That matters when your users are on 3G connections.&lt;/p&gt;

&lt;p&gt;Node.js 20 with Express on the backend. Nothing fancy here. It works. It scales. The ecosystem is massive. I added Socket.io for real-time features like order notifications and live inventory updates.&lt;/p&gt;

&lt;p&gt;MongoDB Atlas for the database. I considered self-hosting on EC2, but honestly, managed databases save you so much headache. Automated backups, point-in-time recovery, monitoring. All handled. I went with an M10 cluster to start.&lt;/p&gt;

&lt;p&gt;Redis through ElastiCache for caching and session management. This was a game changer for performance. More on that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture (And Why Each Piece Exists)
&lt;/h2&gt;

&lt;p&gt;Alright, let's break down the actual AWS setup. I'll explain why I chose each service, not just what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Entry Point: Route 53 and CloudFront
&lt;/h3&gt;

&lt;p&gt;Every request starts at Route 53 for DNS resolution. Simple enough. But the real magic is CloudFront.&lt;/p&gt;

&lt;p&gt;CloudFront is AWS's CDN, and it has edge locations in Lagos. That's huge. It means my users in Nigeria are hitting a server that's physically close to them, not one sitting in Ireland or Virginia.&lt;/p&gt;

&lt;p&gt;I configured CloudFront to do two things. Static file requests go to an S3 bucket where my React build lives. API requests get forwarded to my backend through the Application Load Balancer. One domain, two destinations. Clean and simple.&lt;/p&gt;

&lt;p&gt;I also attached an ACM certificate here. Free SSL. No reason not to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Frontend: S3
&lt;/h3&gt;

&lt;p&gt;The React app gets built by Vite, and the output goes straight into an S3 bucket. No servers involved. S3 serves static files incredibly well, and combined with CloudFront caching, my frontend loads in under 2 seconds on a 3G connection.&lt;/p&gt;

&lt;p&gt;I set up error page redirects so that 404s go back to index.html. That's essential for single-page apps with client-side routing. Without it, refreshing any page that isn't the root would give you a blank screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Backend: EC2 with Auto Scaling
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting. My Node.js API runs on EC2 instances inside a public subnet. I have two instances behind an Application Load Balancer, with an Auto Scaling Group that can spin up to four instances based on CPU utilization.&lt;/p&gt;

&lt;p&gt;Why not Fargate or Lambda? Honestly, for a WebSocket-heavy application, EC2 gives you more control. Lambda has cold starts that would kill the real-time experience. Fargate is great but adds complexity I didn't need yet. EC2 with a good Auto Scaling policy hits the sweet spot.&lt;/p&gt;

&lt;p&gt;The ALB distributes traffic evenly and handles health checks. If one instance goes down, traffic automatically routes to the healthy ones. No manual intervention needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Data Layer: MongoDB Atlas and ElastiCache
&lt;/h3&gt;

&lt;p&gt;MongoDB Atlas sits in a private subnet. It's peered with my VPC, so the connection is fast and secure. No public internet involved.&lt;/p&gt;

&lt;p&gt;ElastiCache Redis handles three things for me. Session storage, so users stay logged in across multiple EC2 instances. Response caching, so repeated database queries don't hit MongoDB every time. And rate limiting, so I can throttle abusive requests without adding load to my application servers.&lt;/p&gt;

&lt;p&gt;Before I added Redis, my average API response time was around 400ms. After? Under 200ms. That's the kind of improvement that users actually feel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring and Email: CloudWatch and SES
&lt;/h3&gt;

&lt;p&gt;CloudWatch collects logs and metrics from everything. EC2 instances, the load balancer, Redis, all of it. I set up alarms for CPU spikes, memory usage, and error rates. If something breaks at 3am, I get a notification.&lt;/p&gt;

&lt;p&gt;Amazon SES handles transactional emails. Order confirmations, password resets, shipping updates. It's cheap and reliable. Way better than trying to manage your own SMTP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backups
&lt;/h3&gt;

&lt;p&gt;Everything gets backed up to S3. MongoDB Atlas handles its own backups, but I also dump snapshots to S3 for extra safety. CloudWatch logs go there too. Storage is cheap. Losing data is not.&lt;/p&gt;

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

&lt;p&gt;This part I'm actually proud of. GitHub Actions handles everything.&lt;/p&gt;

&lt;p&gt;When I push to the main branch, here's what happens. The pipeline runs tests. If they pass, it builds the React frontend and syncs it to S3. Then it deploys the backend to EC2 through the load balancer. Zero downtime. The whole process takes about 4 minutes.&lt;/p&gt;

&lt;p&gt;I also have separate workflows for staging and production. Feature branches deploy to staging automatically. Production requires a manual approval step. That one extra click has saved me from shipping broken code more than once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe Integration
&lt;/h2&gt;

&lt;p&gt;Payments go through Stripe. The integration is bidirectional. My EC2 instances send payment requests to Stripe's API, and Stripe sends webhook events back for things like successful charges, refunds, and subscription updates.&lt;/p&gt;

&lt;p&gt;I handle webhooks on a dedicated endpoint with signature verification. Never trust incoming data without verifying it. That's a lesson you only need to learn once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Actually Costs
&lt;/h2&gt;

&lt;p&gt;Here's the part everyone wants to know. My monthly AWS bill for this setup is roughly $90 to $110. That breaks down to about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 instances (t3.small): $25-30&lt;/li&gt;
&lt;li&gt;MongoDB Atlas (M10): $57&lt;/li&gt;
&lt;li&gt;ElastiCache Redis (t3.micro): $12&lt;/li&gt;
&lt;li&gt;S3 and CloudFront: $5-10&lt;/li&gt;
&lt;li&gt;Route 53 and misc: $2-3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a production SaaS platform with auto-scaling, CDN, managed database, caching, monitoring, and automated deployments, that's pretty reasonable. It can comfortably handle hundreds of concurrent users and thousands of daily requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons I Learned the Hard Way
&lt;/h2&gt;

&lt;p&gt;Let me share a few things that bit me during this process.&lt;/p&gt;

&lt;p&gt;WebSocket connections through CloudFront need specific configuration. You have to set up the right cache behaviors and forward the Upgrade header. I spent an entire weekend debugging why Socket.io worked locally but not in production. The fix was three lines of CloudFront config.&lt;/p&gt;

&lt;p&gt;Don't skip the VPC design. I initially put everything in a public subnet because it was easier. Then I realized my database was exposed to the internet. Moved it to a private subnet immediately. Take the time to set up your network properly from day one.&lt;/p&gt;

&lt;p&gt;Redis connection pooling matters. My first implementation created a new Redis connection for every request. Under load, I was hitting connection limits within minutes. Connection pooling fixed it instantly.&lt;/p&gt;

&lt;p&gt;Auto Scaling needs a cooldown period. Without it, your instances will scale up and down like a yo-yo. I set a 5-minute cooldown, and the scaling became smooth and predictable.&lt;/p&gt;

&lt;p&gt;Environment variables are not optional. I had a brief moment where I accidentally committed a JWT secret to GitHub. Rotated it immediately and moved everything to AWS Systems Manager Parameter Store. Use it. It's free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The architecture I have now works well for the current stage. But I'm already thinking about what comes next as the platform grows.&lt;/p&gt;

&lt;p&gt;I want to add a message queue. Probably SQS. Right now, some of my background tasks like sending emails and processing images run synchronously. That's fine with 50 users. It won't be fine with 500.&lt;/p&gt;

&lt;p&gt;I'm also looking at moving to containers eventually. ECS with Fargate would give me better resource utilization and simpler deployments. But that's a migration I'll do when the current setup starts showing strain, not before.&lt;/p&gt;

&lt;p&gt;And I need better observability. CloudWatch is good for basics, but I want distributed tracing. Probably AWS X-Ray or something like Datadog. When you have multiple services talking to each other, you need to see the full picture of a request's journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a SaaS platform is one thing. Making it production-ready on AWS is a completely different challenge. It forces you to think about things you never consider during development. Network security. Scaling behavior. Cost optimization. Disaster recovery.&lt;/p&gt;

&lt;p&gt;But here's what I've realized. You don't need a perfect architecture on day one. You need one that works, that you understand, and that you can evolve. Start simple. Add complexity only when you have a real reason to.&lt;/p&gt;

&lt;p&gt;If you want to dig into the code, the full project is on GitHub: &lt;a href="https://github.com/suletetes/TechVerse" rel="noopener noreferrer"&gt;TechVerse on GitHub&lt;/a&gt;&lt;/p&gt;




</description>
      <category>aws</category>
      <category>webdev</category>
      <category>cloud</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
