<?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: Rajeev</title>
    <description>The latest articles on DEV Community by Rajeev (@elonerajeev1).</description>
    <link>https://dev.to/elonerajeev1</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2193964%2F11c3a6bc-8289-4da0-b0d2-12200216676b.png</url>
      <title>DEV Community: Rajeev</title>
      <link>https://dev.to/elonerajeev1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elonerajeev1"/>
    <language>en</language>
    <item>
      <title>I Accidentally Deleted Our Production S3 Bucket — Here's What Happened in the Next 4 Hours</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:37:39 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/i-accidentally-deleted-our-production-s3-bucket-heres-what-happened-in-the-next-4-hours-2b55</link>
      <guid>https://dev.to/elonerajeev1/i-accidentally-deleted-our-production-s3-bucket-heres-what-happened-in-the-next-4-hours-2b55</guid>
      <description>&lt;h1&gt;
  
  
  I Accidentally Deleted Our Production S3 Bucket — Here's What Happened in the Next 4 Hours
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The command ran in under a second. The recovery took 4 hours. Here's the full story, what we lost, what we saved, and what we now have in place so it can never happen again.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;There's a moment every cloud engineer dreads.&lt;/p&gt;

&lt;p&gt;The cursor blinks. The command runs. And then a silence that feels different from normal silence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 rb s3://my-app-production-uploads &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was targeting the staging bucket. I was in the wrong terminal window.&lt;/p&gt;

&lt;p&gt;In under a second, 3 years of user-uploaded files — profile pictures, documents, exported reports — were gone.&lt;/p&gt;

&lt;p&gt;This is what happened next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First 60 Seconds
&lt;/h2&gt;

&lt;p&gt;The first thing you do when something goes catastrophically wrong is nothing.&lt;/p&gt;

&lt;p&gt;Not because you're calm. Because your brain genuinely cannot process what just happened. I stared at the terminal output for probably 10 seconds before the word &lt;em&gt;production&lt;/em&gt; registered.&lt;/p&gt;

&lt;p&gt;Then I checked the AWS console. The bucket was gone. Not empty — &lt;em&gt;gone&lt;/em&gt;. No objects, no metadata, no bucket at all. S3 bucket deletion removes the bucket itself, not just the contents.&lt;/p&gt;

&lt;p&gt;I looked at the clock: &lt;strong&gt;11:47 PM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I typed one message in Slack: &lt;em&gt;"I need help. Production S3 bucket just got deleted. Staging terminal wrong window."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three people came online within 2 minutes. That response time, at midnight, is what actually saved us.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was in the Bucket
&lt;/h2&gt;

&lt;p&gt;Before I explain the recovery, you need to understand what we lost — or thought we lost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User profile pictures&lt;/strong&gt; — every account photo uploaded since launch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document uploads&lt;/strong&gt; — files users had submitted through the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exported reports&lt;/strong&gt; — generated PDFs that users could re-download&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some application assets&lt;/strong&gt; — a handful of static files referenced in the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user-facing impact was immediate. Profile pictures showed broken image icons. Document download links returned 403. The export feature threw errors.&lt;/p&gt;

&lt;p&gt;It was 11:47 PM on a Tuesday. Low traffic. That was the only lucky part.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Recovery Attempt — Hour 1
&lt;/h2&gt;

&lt;p&gt;The first thing we checked: &lt;strong&gt;S3 Versioning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;S3 Versioning, when enabled, keeps every version of every object including delete markers. When you delete an object with versioning on, S3 adds a delete marker rather than actually removing the data. The object is "deleted" but recoverable.&lt;/p&gt;

&lt;p&gt;We had versioning enabled on the production bucket.&lt;/p&gt;

&lt;p&gt;The problem: deleting the &lt;em&gt;bucket itself&lt;/em&gt; with &lt;code&gt;--force&lt;/code&gt; does something different. It first deletes all objects and versions, then deletes the bucket. By the time the bucket is gone, the versioned objects are gone too.&lt;/p&gt;

&lt;p&gt;Versioning alone didn't save us. But it was a clue that we were thinking about this correctly — we just needed another layer.&lt;/p&gt;

&lt;p&gt;Then someone asked the question that changed the next 3 hours:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do we have Cross-Region Replication set up?"&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Recovery — Hour 2
&lt;/h2&gt;

&lt;p&gt;We did have Cross-Region Replication (CRR) configured. Set up 8 months earlier as a compliance requirement, never actually tested, mostly forgotten about.&lt;/p&gt;

&lt;p&gt;CRR continuously replicates objects from a source bucket to a destination bucket in another region. It runs automatically, in the background, silently.&lt;/p&gt;

&lt;p&gt;We checked the destination bucket.&lt;/p&gt;

&lt;p&gt;It was there. All of it. Every object, replicated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source bucket (deleted):      0 objects
Destination bucket (intact):  47,832 objects ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;47,832 files. All of them. The replication had been running every time someone uploaded anything, for 8 months, without us ever consciously thinking about it.&lt;/p&gt;

&lt;p&gt;The recovery process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new source bucket with the same name in the original region&lt;/li&gt;
&lt;li&gt;Copy all objects from the replica bucket back to the new source bucket&lt;/li&gt;
&lt;li&gt;Update bucket policies, CORS configuration, and lifecycle rules to match original&lt;/li&gt;
&lt;li&gt;Re-enable CRR on the new bucket pointing to the replica&lt;/li&gt;
&lt;li&gt;Verify application could read all files
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy everything from replica back to new source bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  s3://my-app-production-uploads-replica &lt;span class="se"&gt;\&lt;/span&gt;
  s3://my-app-production-uploads &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-region&lt;/span&gt; us-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sync took 47 minutes for 47,832 files.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;2:34 AM&lt;/strong&gt; — 2 hours and 47 minutes after the deletion — every broken image was loading again. Every download link worked. Every export was accessible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total permanent data loss: zero.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Lost That We Couldn't Recover
&lt;/h2&gt;

&lt;p&gt;Not everything was perfect.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;bucket policy&lt;/strong&gt; was gone. We had to reconstruct it from memory and the IAM policy attached to our application role. Close enough, but not exact — there were edge case permissions we discovered were missing over the following week when specific features started throwing permission errors.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;lifecycle rules&lt;/strong&gt; were gone. We had automated deletion of files older than 365 days for GDPR compliance. Those rules had to be manually recreated. We found them eventually in a Terraform file that was slightly out of date.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;CORS configuration&lt;/strong&gt; took three attempts to get right. We had it in a config file but the file wasn't fully updated — there were two origin entries we'd added manually and never committed.&lt;/p&gt;

&lt;p&gt;Lesson: the &lt;em&gt;bucket&lt;/em&gt; itself is infrastructure. Treat it like infrastructure. Version-control every policy, every lifecycle rule, every CORS config.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 4 Layers We Now Have in Place
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Layer 1 — S3 Versioning (Was Already On)
&lt;/h3&gt;

&lt;p&gt;Versioning doesn't protect against bucket deletion with &lt;code&gt;--force&lt;/code&gt; but it protects against accidental object deletion and overwrites. Keep it on. Always.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — Cross-Region Replication (Was Already On, Saved Us)
&lt;/h3&gt;

&lt;p&gt;CRR was our actual recovery mechanism. The lesson: don't set it up and forget it. &lt;strong&gt;Test it.&lt;/strong&gt; We had never run a recovery drill. We got lucky that it was working correctly when we needed it.&lt;/p&gt;

&lt;p&gt;Test your replication quarterly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete a test object from source&lt;/li&gt;
&lt;li&gt;Verify it still exists in replica&lt;/li&gt;
&lt;li&gt;Restore it&lt;/li&gt;
&lt;li&gt;Confirm restoration worked&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 3 — S3 Object Lock (Added After)
&lt;/h3&gt;

&lt;p&gt;S3 Object Lock in Compliance mode prevents objects from being deleted by &lt;em&gt;anyone&lt;/em&gt; — including root account users — for a defined retention period.&lt;/p&gt;

&lt;p&gt;For our document uploads that have legal retention requirements, we now use Object Lock with a 90-day retention window. Even &lt;code&gt;aws s3 rb --force&lt;/code&gt; cannot delete locked objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4 — IAM Boundary on Deployment Credentials (Added After)
&lt;/h3&gt;

&lt;p&gt;The access key I was using had full S3 permissions. It didn't need them. Our deployment pipeline only needs &lt;code&gt;PutObject&lt;/code&gt; and &lt;code&gt;GetObject&lt;/code&gt; on specific buckets.&lt;/p&gt;

&lt;p&gt;We added a permission boundary that explicitly denies &lt;code&gt;s3:DeleteBucket&lt;/code&gt; and &lt;code&gt;s3:DeleteObject&lt;/code&gt; on production buckets for all non-root credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObjectVersion"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-app-production-*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-app-production-*/*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy means even if someone runs &lt;code&gt;aws s3 rb --force&lt;/code&gt; on a production bucket, the API call returns AccessDenied. The bucket stays.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Conversation We Had to Have
&lt;/h2&gt;

&lt;p&gt;At 3 AM, with everything recovered and the immediate crisis over, the four of us stayed on the call for another 30 minutes.&lt;/p&gt;

&lt;p&gt;Not to assign blame. Not to write a post-mortem. Just to talk through what had actually happened and what it meant.&lt;/p&gt;

&lt;p&gt;The thing nobody said but everyone was thinking: &lt;strong&gt;we got lucky.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lucky that CRR was set up. Lucky that it had been running correctly for 8 months without us verifying it. Lucky that traffic was low. Lucky that three people responded at midnight.&lt;/p&gt;

&lt;p&gt;A production incident that resolves with zero data loss still costs something. It costs 4 hours of sleep, a spike of cortisol that doesn't fully fade for days, and a team that now moves a little more carefully around production resources than they did before.&lt;/p&gt;

&lt;p&gt;That carefulness is worth more than any technical safeguard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Protects Your S3 Data
&lt;/h2&gt;

&lt;p&gt;Not one thing. All of these, in layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Layer 1 — Versioning
  Protects against: accidental object deletion, overwrites
  Does NOT protect: bucket deletion with --force

Layer 2 — Cross-Region Replication  
  Protects against: bucket deletion, regional failure
  Requirement: test it before you need it

Layer 3 — S3 Object Lock
  Protects against: everything, including root account
  Best for: compliance data, legal holds, critical assets

Layer 4 — IAM Deny on DeleteBucket/DeleteObject
  Protects against: credential misuse, wrong terminal window
  Best practice: least privilege on all non-root credentials

Layer 5 — AWS Backup for S3
  Protects against: all of the above
  Adds: point-in-time recovery, automated backup schedules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need all five on every bucket. But you need to know which layers you have, verify they work, and match protection level to data criticality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Checklist I Wish I Had Before That Night
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before every AWS CLI session:
☐ Check which terminal window is active
☐ Check which AWS profile is set (aws sts get-caller-identity)
☐ Double-check the resource name before any destructive command

For every production S3 bucket:
☐ Versioning: enabled
☐ Cross-Region Replication: enabled and tested
☐ MFA Delete: enabled for critical buckets
☐ IAM Deny policy: DeleteBucket + DeleteObject on prod buckets
☐ Bucket policy + lifecycle rules: version-controlled in git

Recovery drills (quarterly):
☐ Delete a test object, verify replica has it, restore it
☐ Confirm CORS, bucket policy, lifecycle rules are in source control
☐ Know the exact sync command to run if you need it at 2am
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  One Last Thing
&lt;/h2&gt;

&lt;p&gt;Before you close this tab — run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check which account you're in right now.&lt;/p&gt;

&lt;p&gt;If the answer is your production account and you don't need to be there, switch profiles before you do anything else.&lt;/p&gt;

&lt;p&gt;That command takes 2 seconds. The recovery takes 4 hours.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have a recovery story of your own? Drop it in the comments. The more of these we share, the less likely someone else has to learn the same lesson at midnight.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#AWS&lt;/code&gt; &lt;code&gt;#S3&lt;/code&gt; &lt;code&gt;#DevOps&lt;/code&gt; &lt;code&gt;#Cloud&lt;/code&gt; &lt;code&gt;#DataRecovery&lt;/code&gt; &lt;code&gt;#Lessons&lt;/code&gt; &lt;code&gt;#Backend&lt;/code&gt; &lt;code&gt;#Infrastructure&lt;/code&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Our ECS Fargate Task Was Silently Failing for Days — Here's Exactly How We Found It</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Wed, 26 Aug 2026 15:07:51 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/our-ecs-fargate-task-was-silently-failing-for-days-heres-exactly-how-we-found-it-ljc</link>
      <guid>https://dev.to/elonerajeev1/our-ecs-fargate-task-was-silently-failing-for-days-heres-exactly-how-we-found-it-ljc</guid>
      <description>&lt;h1&gt;
  
  
  Our ECS Fargate Task Was Silently Failing for Days — Here's Exactly How We Found It
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;No alarm fired. No Slack alert. No PagerDuty page. Just a service quietly burning compute while cycling through thousands of failed tasks. Here's the full story.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;It started with a routine check.&lt;/p&gt;

&lt;p&gt;I opened the ECS console to verify a deployment and noticed something wrong in the numbers. The running task count was 0. The desired count was 1. And the service had been in that state for longer than I wanted to admit.&lt;/p&gt;

&lt;p&gt;No notification. No alert. No one on the team had noticed.&lt;/p&gt;

&lt;p&gt;The task was starting, failing its health check, getting killed by ECS, and restarting — on a 60-second loop. Silently. Automatically. Expensively.&lt;/p&gt;

&lt;p&gt;This is the story of how we found it, what caused it, and the three things we now have in place so it never goes undetected again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Happening
&lt;/h2&gt;

&lt;p&gt;ECS Fargate has a deceptively simple failure loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task starts
    ↓
Health check runs
    ↓
Health check fails
    ↓
ECS kills the task
    ↓
ECS starts a new task (desired count not met)
    ↓
Repeat indefinitely
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the outside, your service looks like it's "working" — ECS is doing exactly what it's designed to do, trying to maintain your desired count. From the inside, your application is never actually serving traffic. And unless you're watching the right metrics, you won't know.&lt;/p&gt;

&lt;p&gt;The console shows this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service: my-backend
Desired: 1 | Running: 0 | Pending: 1

Recent events:
  service my-backend has started 1 tasks
  service my-backend: task failed health check, will be stopped
  service my-backend has started 1 tasks
  service my-backend: task failed health check, will be stopped
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every 60 seconds. Over and over.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;Our health check was configured to hit &lt;code&gt;/api/health&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The container was serving the application — but on a different path after a recent refactor. The endpoint had moved. The health check hadn't been updated.&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 expected:  GET /api/health  → 200 OK
Container was serving:  GET /health      → 200 OK
                        GET /api/health  → 404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One forgotten update. That's it.&lt;/p&gt;

&lt;p&gt;ECS saw a 404 on every health check. Interpreted it as unhealthy. Killed the task. Started a new one. Repeat.&lt;/p&gt;

&lt;p&gt;The application code was perfect. The deployment was clean. The task definition was correct. The only problem was a single path string in the health check configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  How We Found It
&lt;/h2&gt;

&lt;p&gt;Three signals that told us something was wrong — none of them were the alarm we should have had:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal 1 — ECS console service events&lt;/strong&gt;&lt;br&gt;
The events tab in the ECS service console shows every task start and stop. When a service is stuck in a failure loop, this list fills up fast. A healthy service might have 5-10 events. Ours had hundreds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal 2 — CloudWatch metric: RunningTaskCount&lt;/strong&gt;&lt;br&gt;
This metric drops to 0 when all tasks fail. If you graph it, you see a flat line at 0 instead of the expected flat line at 1. We weren't graphing it. We should have been.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal 3 — CloudWatch logs — or the absence of them&lt;/strong&gt;&lt;br&gt;
Application logs stop when the task dies. If your log stream has a gap or just stops entirely, the task isn't running. Check the timestamp on the last log line.&lt;/p&gt;

&lt;p&gt;None of these required any special setup. They were all there, visible, waiting. We just weren't watching.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Fix — 3 Layers We Now Have in Place
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Layer 1 — Health Check Path Validation
&lt;/h3&gt;

&lt;p&gt;Before every deployment we now verify health check path matches the actual endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application route:   /health        ✅
Health check path:   /health        ✅ match — good to deploy

Application route:   /health        ✅  
Health check path:   /api/health    ❌ mismatch — fix before deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple manual check. Takes 30 seconds. Would have prevented the entire incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — CloudWatch Alarm on RunningTaskCount
&lt;/h3&gt;

&lt;p&gt;The metric &lt;code&gt;RunningTaskCount&lt;/code&gt; dropping below your desired count for more than 5 minutes means something is wrong. This alarm now fires to Slack the moment it happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Metric:     ECS/RunningTaskCount
Condition:  &amp;lt; 1 for 5 consecutive minutes
Action:     SNS → Slack notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We missed days of failure because this didn't exist. It takes about 10 minutes to set up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3 — ECS Service Events in CloudWatch
&lt;/h3&gt;

&lt;p&gt;ECS service events can be streamed to CloudWatch Logs via EventBridge. Once there, you can search them, alert on patterns, and see the full history without opening the console.&lt;/p&gt;

&lt;p&gt;The event pattern to watch for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws.ecs"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ECS Service Action"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"WARN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any WARN or ERROR from ECS service events goes straight to a log group. From there a metric filter and alarm handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Fixed Architecture Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ECS Task starts
    ↓
Health check: GET /health → 200 OK ✅
    ↓
Task marked healthy
    ↓
Traffic routed to task

        +-- CloudWatch Alarm watching RunningTaskCount
        |   fires if task count &amp;lt; desired for 5 mins
        |
        +-- EventBridge capturing ECS WARN/ERROR events
        |   streams to CloudWatch Logs
        |
        +-- Slack notification on any alarm state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three independent detection layers. Any one of them would have caught the original failure within minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost of Not Watching
&lt;/h2&gt;

&lt;p&gt;Every failed task in ECS Fargate still consumes compute during its startup window. If your task takes 60 seconds to start before failing the health check, and it restarts every 60 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fargate 0.25 vCPU + 0.5GB = ~$0.015/hour
Constant restart loop = full billing, zero useful work

Over 3 days undetected:  3 × 24 × $0.015 = $1.08 wasted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a huge number — but that's a small task. Scale this to larger Fargate tasks or multiple services and it adds up fast. More importantly, your application was completely down the entire time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. ECS will not tell you when it's failing — by design.&lt;/strong&gt;&lt;br&gt;
ECS is doing its job: trying to maintain desired count. It has no concept of "this has been broken for too long." That judgement is yours to implement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Health check path is a contract.&lt;/strong&gt;&lt;br&gt;
Change your application routes, update your health check. Always. Make it part of your PR checklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. RunningTaskCount alarm is non-optional.&lt;/strong&gt;&lt;br&gt;
If you run ECS Fargate and don't have this alarm, you are flying blind. It takes 10 minutes to set up and covers an entire class of silent failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Log gaps are signals.&lt;/strong&gt;&lt;br&gt;
If your application log stream suddenly stops, the task stopped. The absence of logs is as important as the presence of errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Silent failures are more dangerous than loud ones.&lt;/strong&gt;&lt;br&gt;
A crashing application that sends an alert is manageable. An application that silently fails for days while appearing to run is much harder to deal with — because by the time you find it, the damage is done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Checklist Before Your Next ECS Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before deploying:
☐ Health check path matches current application route
☐ Health check interval and threshold are reasonable
☐ Container port matches task definition port mapping

After deploying:
☐ RunningTaskCount reaches desired count within 5 minutes
☐ Application logs are appearing in CloudWatch
☐ Health check is returning 200 in the target group

Monitoring (set up once, never remove):
☐ CloudWatch alarm: RunningTaskCount &amp;lt; desired for 5 mins
☐ EventBridge rule: ECS service WARN/ERROR events to CloudWatch
☐ Slack/SNS notification on alarm state change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The most expensive outages are the ones nobody notices.&lt;/p&gt;

&lt;p&gt;A loud failure — exception thrown, service returns 500, alarm fires — gets fixed fast. A silent failure — task cycling, health check failing, no traffic getting through — can run for days before anyone looks at the right dashboard.&lt;/p&gt;

&lt;p&gt;ECS is excellent infrastructure. But it will not babysit your application for you. The monitoring layer is yours to own.&lt;/p&gt;

&lt;p&gt;Set up the RunningTaskCount alarm today. It takes 10 minutes. Future you will be grateful.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Running ECS Fargate? What monitoring do you have in place for silent failures? Drop it in the comments — curious what patterns people are using.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#AWS&lt;/code&gt; &lt;code&gt;#ECS&lt;/code&gt; &lt;code&gt;#Fargate&lt;/code&gt; &lt;code&gt;#DevOps&lt;/code&gt; &lt;code&gt;#CloudWatch&lt;/code&gt; &lt;code&gt;#Monitoring&lt;/code&gt; &lt;code&gt;#Backend&lt;/code&gt; &lt;code&gt;#SRE&lt;/code&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>devops</category>
      <category>cloudwatch</category>
    </item>
    <item>
      <title>How We Cut AWS Staging Costs by 87% With EventBridge Scheduler (Zero Code Changes)</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:17:47 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/how-we-cut-aws-staging-costs-by-87-with-eventbridge-scheduler-zero-code-changes-2k3c</link>
      <guid>https://dev.to/elonerajeev1/how-we-cut-aws-staging-costs-by-87-with-eventbridge-scheduler-zero-code-changes-2k3c</guid>
      <description>&lt;h1&gt;
  
  
  How We Cut AWS Staging Costs by 87% With EventBridge Scheduler
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;No code changes. No Lambda functions. No complex scripts. Just 4 schedulers and a realization that nobody uses staging at 3am.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Here's a question every engineering team should ask themselves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"When was the last time someone actually used our staging environment at 2am?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For us? Never. Not once.&lt;/p&gt;

&lt;p&gt;Yet we were paying for it — EC2 running, ECS Fargate tasks spinning, compute burning money — every single hour of every single day, including weekends, holidays, and the 21 hours per day when nobody on our team was even awake.&lt;/p&gt;

&lt;p&gt;That's the hidden tax of staging environments. And most teams never fix it because the solution feels complicated.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;This is how we cut our staging compute costs by &lt;strong&gt;87.5%&lt;/strong&gt; — using AWS EventBridge Scheduler, zero Lambda functions, and zero lines of application code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Staging Was Running 24/7 For No Reason
&lt;/h2&gt;

&lt;p&gt;Our staging environment had two resources running around the clock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2 instance&lt;/strong&gt; — our staging app server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS Fargate service&lt;/strong&gt; — our backend API container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our team actively uses staging for roughly &lt;strong&gt;3 hours a day&lt;/strong&gt;. That's it.&lt;/p&gt;

&lt;p&gt;The math was embarrassing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running:    24 hours/day
Used:        3 hours/day
Wasted:     21 hours/day = 87.5% of compute going nowhere
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monthly cost breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 + ECS Fargate (24x7):   ~$19.18/month
EC2 + ECS Fargate (3hr/day): ~$2.40/month

Monthly saving:  $16.78
Yearly saving:   $201.35
Reduction:       87.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;$201/year saved on staging compute alone&lt;/strong&gt; — with 45 minutes of setup and zero application code changes.&lt;/p&gt;

&lt;p&gt;Multiply that across dev environments, QA clusters, review apps, and load test environments. The savings compound fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: AWS EventBridge Scheduler
&lt;/h2&gt;

&lt;p&gt;Most engineers reach for Lambda when they need to automate AWS tasks on a schedule. That works — but it means writing code, managing runtimes, setting up CloudWatch Logs, and maintaining a function forever.&lt;/p&gt;

&lt;p&gt;EventBridge Scheduler is the better tool here. It lets you &lt;strong&gt;call any AWS SDK action directly on a cron schedule&lt;/strong&gt; — no Lambda, no code, no runtime to maintain.&lt;/p&gt;

&lt;p&gt;The mental model is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHEN  →  cron expression (with native timezone support)
WHAT  →  any AWS API call (StartInstances, UpdateService, StopDBInstance...)
WHO   →  a dedicated IAM role with just enough permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire service. And it's free for the first 14 million invocations per month.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Built: 4 Schedulers
&lt;/h2&gt;

&lt;p&gt;We created 4 EventBridge Schedulers targeting our staging resources:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scheduler&lt;/th&gt;
&lt;th&gt;Fires At&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staging-ec2-start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9:00 PM IST daily&lt;/td&gt;
&lt;td&gt;Start staging EC2 instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staging-ec2-stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12:00 AM IST daily&lt;/td&gt;
&lt;td&gt;Stop staging EC2 instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staging-ecs-scale-up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9:00 PM IST daily&lt;/td&gt;
&lt;td&gt;ECS service desiredCount to 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staging-ecs-scale-down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12:00 AM IST daily&lt;/td&gt;
&lt;td&gt;ECS service desiredCount to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Result:&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;Timeline (IST):
─────────────────────────────────────────────────────
12:00 AM  |████████████ OFF (21 hrs) ████████████| 9:00 PM
                                                  │
                                               9:00 PM
                                          ┌──────────────┐
                                          │ EC2 starts   │
                                          │ ECS → 1 task │
                                          └──────┬───────┘
                                                 │
                                          ┌──────▼───────┐
                                          │  RUNNING     │
                                          │  (3 hours)   │
                                          └──────┬───────┘
                                                 │
                                           12:00 AM
                                          ┌──────────────┐
                                          │ EC2 stops    │
                                          │ ECS → 0 tasks│
                                          └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Staging wakes up exactly when the team needs it. Goes dark at midnight. Every day. Automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS EventBridge Scheduler
│
├── staging-ec2-start    ──► 9 PM IST  ──► ec2:StartInstances
├── staging-ec2-stop     ──► 12 AM IST ──► ec2:StopInstances
├── staging-ecs-scale-up ──► 9 PM IST  ──► ecs:UpdateService (count=1)
└── staging-ecs-scale-down──► 12 AM IST ──► ecs:UpdateService (count=0)
         │
         └── All using: staging-scheduler-role (least privilege IAM)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One dedicated IAM role. Four schedulers. Zero Lambda functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  EventBridge Scheduler vs Lambda + Cron
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Lambda + EventBridge Rule&lt;/th&gt;
&lt;th&gt;EventBridge Scheduler&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code to write&lt;/td&gt;
&lt;td&gt;~30 lines per action&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime maintenance&lt;/td&gt;
&lt;td&gt;Lambda versions, updates&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timezone handling&lt;/td&gt;
&lt;td&gt;Manual UTC math&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Built-in&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry on failure&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Built-in&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;20-30 mins per action&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5 mins per action&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;CloudWatch Logs setup&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native console&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For simple time-based AWS automation, EventBridge Scheduler removes an entire layer of complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Things to Know Before You Implement
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The IAM role is the foundation — and where most people get stuck.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EventBridge Scheduler needs a role with the correct trust principal. There's a very common mistake here — getting this one thing wrong means your schedulers silently do nothing. Scope the role permissions to specific resource ARNs only. A staging scheduler role should be physically incapable of touching production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron expressions use UTC by default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set the timezone parameter to your team's actual timezone. Forgetting this means your staging shuts down at the wrong time and your team spends 20 minutes debugging before realizing it's a UTC offset issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;desiredCount: 0&lt;/code&gt; is not the same as deleting the ECS service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Setting desired count to zero stops all running tasks but preserves the full service definition, task definition, and all configuration. Scale back up to 1 and everything restarts exactly as before — same image, same env vars, same everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stopInstances&lt;/code&gt; is not &lt;code&gt;terminateInstances&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stopping preserves the EBS volume and all data. Terminating destroys it permanently. Our schedulers stop — never terminate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account for application warm-up time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EC2 boot + ECS task startup + application initialization can take 3-5 minutes total. Schedule your start 10-15 minutes before your team actually needs it. We start at 8:45 PM for a 9 PM working session.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Else Can You Schedule?
&lt;/h2&gt;

&lt;p&gt;Once you understand this pattern, every always-on non-production resource is a candidate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RDS instances&lt;/strong&gt; — stop during nights, save $15-50/month per instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DocumentDB clusters&lt;/strong&gt; — same pattern, significant savings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Scaling Groups&lt;/strong&gt; — scale to minimum overnight&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional ECS services&lt;/strong&gt; — any non-prod service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElastiCache&lt;/strong&gt; — stop non-prod caches at night&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every resource running at 3am that nobody needs is costing you money right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE:  $19.18/month  (24x7 always on)
AFTER:    $2.40/month  (3hrs/day scheduled)

Saving:  $16.78/month
         $201.35/year
         87.5% reduction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero code changes. Zero Lambda functions. Zero new infrastructure.&lt;/p&gt;

&lt;p&gt;Four schedulers calling two AWS APIs on a cron. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Analyze your actual usage before setting the schedule.&lt;/strong&gt;&lt;br&gt;
We assumed our team used staging 9 AM to 6 PM. The access logs showed 9 PM to midnight. Data beats assumptions every single time. Check your logs first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. EventBridge Scheduler and EventBridge Rules are completely different services.&lt;/strong&gt;&lt;br&gt;
Scheduler is time-driven. Rules are event-driven. The IAM trust principal, console location, and configuration format are all different. If you Google EventBridge automation and follow a tutorial, make sure it's for Scheduler — not Rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Separate schedulers per resource type.&lt;/strong&gt;&lt;br&gt;
EC2 and ECS need different API targets. Keep them as separate schedulers — cleaner, easier to debug, independently adjustable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Weekend-only shutdown pushes savings higher.&lt;/strong&gt;&lt;br&gt;
If your team doesn't use staging on weekends, a single cron parameter change takes savings from 87.5% to ~94%. Worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Always have a manual override plan.&lt;/strong&gt;&lt;br&gt;
Sometimes you need staging at 2am — production incident, urgent hotfix, client demo. Know how to start resources manually in 30 seconds without waiting for the next scheduled start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Non-Production Cost Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Today:
☐ Identify all always-on non-production EC2 instances
☐ Identify all always-on non-production ECS services
☐ Calculate actual usage hours vs paid running hours

This week:
☐ Set up EventBridge Schedulers for staging resources
☐ Stop non-prod RDS during off-hours
☐ Remove unused Elastic IPs (charged even when unattached)

Next sprint:
☐ Right-size non-prod instances based on actual usage
☐ Audit unused snapshots, AMIs, old EBS volumes
☐ Check for idle NAT Gateways (~$32/month each, easy to forget)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;This isn't really about the $201.&lt;/p&gt;

&lt;p&gt;It's about the &lt;strong&gt;habit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every always-on non-production resource is a signal that your team treats cloud infrastructure like physical servers — provision once, leave forever. That habit is slightly expensive at small scale. At growth scale, it becomes a serious line item that nobody owns.&lt;/p&gt;

&lt;p&gt;EventBridge Scheduler is the simplest tool to break that habit. Set it up once, forget it, and let AWS enforce the discipline automatically every single night.&lt;/p&gt;

&lt;p&gt;Applied across all your non-production environments, teams typically see &lt;strong&gt;40-70% reduction&lt;/strong&gt; in total non-production AWS spend.&lt;/p&gt;

&lt;p&gt;The cost of doing this: 45 minutes.&lt;br&gt;
The cost of not doing this: compounding waste, every month, forever.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Implementing something similar or have a different approach? Drop it in the comments — I read everything.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this saved you money, share it with someone still paying for an always-on staging environment.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#AWS&lt;/code&gt; &lt;code&gt;#DevOps&lt;/code&gt; &lt;code&gt;#CloudCost&lt;/code&gt; &lt;code&gt;#ECS&lt;/code&gt; &lt;code&gt;#EventBridge&lt;/code&gt; &lt;code&gt;#CostOptimization&lt;/code&gt; &lt;code&gt;#Fargate&lt;/code&gt; &lt;code&gt;#EC2&lt;/code&gt; &lt;code&gt;#FinOps&lt;/code&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloud</category>
      <category>costreduction</category>
    </item>
    <item>
      <title>We Ditched MongoDB Atlas for AWS DocumentDB — Here's Exactly How We Did It</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:02:24 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/we-ditched-mongodb-atlas-for-aws-documentdb-heres-exactly-how-we-did-it-with-real-commands-3aak</link>
      <guid>https://dev.to/elonerajeev1/we-ditched-mongodb-atlas-for-aws-documentdb-heres-exactly-how-we-did-it-with-real-commands-3aak</guid>
      <description>&lt;h1&gt;
  
  
  We Ditched MongoDB Atlas for AWS DocumentDB — Here's Exactly How We Did It
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;One connection string change. Zero downtime. One very long night. Here's the full story.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I want to tell you something most engineering blogs won't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrations are messy.&lt;/strong&gt; They go sideways at 11pm. They reveal assumptions you didn't know you had. And the "5-minute fix" always takes 3 hours.&lt;/p&gt;

&lt;p&gt;This is the real story of how we migrated our production MongoDB database from Atlas to AWS DocumentDB — every command, every mistake, every fix, and every lesson learned the hard way.&lt;/p&gt;

&lt;p&gt;If you're planning a similar migration, read this before you start. It will save you hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Even Did This
&lt;/h2&gt;

&lt;p&gt;Our stack was already 100% on AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ECS Fargate&lt;/strong&gt; running our Node.js backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2&lt;/strong&gt; for our app server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS PostgreSQL&lt;/strong&gt; for relational data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3&lt;/strong&gt; for file storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALB&lt;/strong&gt; for load balancing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then there was &lt;strong&gt;MongoDB Atlas&lt;/strong&gt; — sitting outside AWS, on MongoDB's cloud, billed separately, with data crossing the public internet every single request.&lt;/p&gt;

&lt;p&gt;The CTO asked a simple question: &lt;em&gt;"Why are we paying Atlas when we're already on AWS?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That question started this migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goals were clear:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidate to one cloud provider&lt;/li&gt;
&lt;li&gt;Cut cross-cloud data transfer costs&lt;/li&gt;
&lt;li&gt;Put the database inside our VPC (zero public internet exposure)&lt;/li&gt;
&lt;li&gt;One bill, one security boundary, one team managing everything&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What We Were Working With
&lt;/h2&gt;

&lt;p&gt;Before writing a single command, we mapped the full picture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source — MongoDB Atlas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~32 collections&lt;/li&gt;
&lt;li&gt;~2,400 documents (and growing daily)&lt;/li&gt;
&lt;li&gt;Used by 2 ECS services (prod + staging) and an EC2 backend&lt;/li&gt;
&lt;li&gt;Publicly accessible via &lt;code&gt;mongodb+srv://&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Target — Amazon DocumentDB:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MongoDB 5.0 compatible&lt;/li&gt;
&lt;li&gt;Lives inside a &lt;strong&gt;private VPC&lt;/strong&gt; (no public endpoint — this matters, a lot)&lt;/li&gt;
&lt;li&gt;Managed by AWS (auto backups, failover, CloudWatch)&lt;/li&gt;
&lt;li&gt;Tight integration with IAM, VPC Security Groups, KMS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The architecture we were building toward:&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;┌──────────────────────────────────────────────────┐
│                  AWS VPC (private)               │
│                                                  │
│  ┌─────────────────┐      ┌──────────────────┐  │
│  │  ECS Fargate    │      │  EC2 App Server  │  │
│  │  prod + staging │      │  Public IP       │  │
│  └────────┬────────┘      └────────┬─────────┘  │
│           │                        │             │
│           └──────────┬─────────────┘             │
│                      │ private network            │
│           ┌──────────▼──────────┐               │
│           │   Amazon DocumentDB  │               │
│           │   port 27017        │               │
│           │   encrypted at rest │               │
│           └─────────────────────┘               │
└──────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. Secure. Everything private. No data crossing the public internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the Migration Strategy
&lt;/h2&gt;

&lt;p&gt;We had three options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Downtime&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS DMS (live CDC)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Large DBs, can't afford downtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mongodump + mongorestore&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Small-medium DBs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom change streams script&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Custom transformations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We chose &lt;strong&gt;mongodump + mongorestore&lt;/strong&gt;. Here's why:&lt;/p&gt;

&lt;p&gt;Our database was under 1GB. Our staging environment would absorb the testing. And &lt;code&gt;mongodump/mongorestore&lt;/code&gt; is the most battle-tested MongoDB migration tool in existence — boring is good when it comes to production data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Infrastructure Setup (The Foundation)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — DocumentDB Subnet Group
&lt;/h3&gt;

&lt;p&gt;DocumentDB needs subnets across &lt;strong&gt;at least 2 availability zones&lt;/strong&gt; for redundancy. We used our existing private subnets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws docdb create-db-subnet-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-subnet-group-name&lt;/span&gt; myapp-docdb-subnet-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-subnet-group-description&lt;/span&gt; &lt;span class="s2"&gt;"Subnet group for DocumentDB"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet-ids&lt;/span&gt; subnet-xxxxxxxx subnet-yyyyyyyy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Security Group (Port 27017, VPC Only)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Never open 27017 to the internet.&lt;/strong&gt; VPC CIDR only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create security group&lt;/span&gt;
aws ec2 create-security-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; myapp-docdb-sg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"DocumentDB security group"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxxxxxxxxx

&lt;span class="c"&gt;# Allow port 27017 from VPC only&lt;/span&gt;
aws ec2 authorize-security-group-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-id&lt;/span&gt; sg-xxxxxxxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; tcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 27017 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cidr&lt;/span&gt; 10.0.0.0/16

&lt;span class="c"&gt;# Also allow from your ECS security group&lt;/span&gt;
aws ec2 authorize-security-group-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-id&lt;/span&gt; sg-xxxxxxxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; tcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 27017 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-group&lt;/span&gt; sg-your-ecs-sg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 — Create the Cluster + Instance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create cluster (encrypted at rest by default)&lt;/span&gt;
aws docdb create-db-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-cluster-identifier&lt;/span&gt; myapp-docdb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine&lt;/span&gt; docdb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-username&lt;/span&gt; admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-user-password&lt;/span&gt; &lt;span class="s2"&gt;"YourStrongPassword123!"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-subnet-group-name&lt;/span&gt; myapp-docdb-subnet-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-security-group-ids&lt;/span&gt; sg-xxxxxxxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-encrypted&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1

&lt;span class="c"&gt;# Add an instance to the cluster&lt;/span&gt;
aws docdb create-db-instance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; myapp-docdb-instance-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-cluster-identifier&lt;/span&gt; myapp-docdb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-class&lt;/span&gt; db.t3.medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine&lt;/span&gt; docdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⏱️ &lt;strong&gt;Wait 8-10 minutes&lt;/strong&gt; for the cluster to become available. Grab a coffee.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: The Migration — Where It Gets Interesting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem Nobody Warns You About
&lt;/h3&gt;

&lt;p&gt;Here's what I wish someone had told me before I started:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DocumentDB has no public endpoint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It lives inside your VPC. Period. You cannot run &lt;code&gt;mongorestore&lt;/code&gt; from your laptop to DocumentDB. You need a machine inside the VPC to act as a bridge.&lt;/p&gt;

&lt;p&gt;The architecture looks 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;Your Laptop
    │
    │ 1. mongodump ──► reads Atlas
    ▼
/tmp/dump/ (BSON files on your machine)
    │
    │ 2. scp ──► copies files to EC2
    ▼
EC2 Instance (inside VPC) ◄── this is your bridge
    │
    │ 3. mongorestore ──► writes to DocumentDB
    ▼
Amazon DocumentDB (private, inside VPC) ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used our existing EC2 app server as the bridge. No new infrastructure needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Dump Everything from Atlas
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full database dump&lt;/span&gt;
mongodump &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongodb+srv://user:password@cluster.mongodb.net/mydb"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/myapp_backup

&lt;span class="c"&gt;# Verify the dump&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; /tmp/myapp_backup/mydb/
&lt;span class="c"&gt;# You should see .bson + .metadata.json for every collection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5 — Transfer to EC2
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy all dump files to EC2&lt;/span&gt;
scp &lt;span class="nt"&gt;-i&lt;/span&gt; your-keypair.pem &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/myapp_backup/ &lt;span class="se"&gt;\&lt;/span&gt;
  ubuntu@&amp;lt;ec2-public-ip&amp;gt;:/tmp/myapp_backup/

&lt;span class="c"&gt;# Verify files arrived&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-keypair.pem ubuntu@&amp;lt;ec2-ip&amp;gt; &lt;span class="s2"&gt;"ls /tmp/myapp_backup/mydb/ | wc -l"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 — Download TLS Certificate on EC2
&lt;/h3&gt;

&lt;p&gt;DocumentDB requires TLS. The cert needs to be on the machine running &lt;code&gt;mongorestore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-keypair.pem ubuntu@&amp;lt;ec2-ip&amp;gt;

&lt;span class="c"&gt;# Inside EC2:&lt;/span&gt;
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7 — Restore to DocumentDB
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run this inside EC2&lt;/span&gt;
mongorestore &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongodb://admin:password@myapp-docdb.cluster-xxxxx.region.docdb.amazonaws.com:27017/mydb?tls=true&amp;amp;tlsCAFile=global-bundle.pem&amp;amp;replicaSet=rs0&amp;amp;retryWrites=false"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mydb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/myapp_backup/mydb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--drop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--drop&lt;/code&gt; flag is crucial — it drops each collection before restoring. This makes the command &lt;strong&gt;idempotent&lt;/strong&gt; (safe to re-run if something goes wrong).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8 — Verify Every Collection Count
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Never skip this step.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Atlas counts&lt;/span&gt;
mongosh &lt;span class="s2"&gt;"atlas-connection-string"&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--eval&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"db.getCollectionNames().forEach(c =&amp;gt; print(db[c].countDocuments() + '&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;' + c))"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;

&lt;span class="c"&gt;# Check DocumentDB counts (from EC2)&lt;/span&gt;
mongosh &lt;span class="s2"&gt;"docdb-connection-string"&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--eval&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"db.getCollectionNames().forEach(c =&amp;gt; print(db[c].countDocuments() + '&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;' + c))"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every single number must match. If one doesn't — fix it before moving on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: The Connection String — The Devil Is In The Details
&lt;/h2&gt;

&lt;p&gt;This is where most migrations break silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atlas string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongodb+srv://user:pass@cluster.mongodb.net/mydb?retryWrites&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;w&lt;span class="o"&gt;=&lt;/span&gt;majority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DocumentDB string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongodb://user:pass@myapp-docdb.cluster-xxxxx.docdb.amazonaws.com:27017/mydb?tls&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;tlsAllowInvalidCertificates&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;replicaSet&lt;span class="o"&gt;=&lt;/span&gt;rs0&amp;amp;retryWrites&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&amp;amp;authMechanism&lt;span class="o"&gt;=&lt;/span&gt;SCRAM-SHA-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical differences:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Atlas&lt;/th&gt;
&lt;th&gt;DocumentDB&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongodb+srv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongodb://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SRV not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retryWrites&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DocumentDB doesn't support it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;replicaSet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not needed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;rs0&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Required for DocumentDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;auto (SRV)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;27017&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must be explicit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS cert&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;global-bundle.pem&lt;/code&gt; needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Miss any of these and your app either crashes silently or throws cryptic errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: Application Switchover
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Always Staging First. Always.
&lt;/h3&gt;

&lt;p&gt;We updated the ECS task definition environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register new task definition revision with updated MONGODB_URI&lt;/span&gt;
aws ecs register-task-definition &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--family&lt;/span&gt; myapp-backend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--container-definitions&lt;/span&gt; &lt;span class="s1"&gt;'[{
    "name": "backend",
    "environment": [
      {
        "name": "MONGODB_URI",
        "value": "mongodb://user:pass@docdb-endpoint:27017/mydb?tls=true&amp;amp;tlsAllowInvalidCertificates=true&amp;amp;replicaSet=rs0&amp;amp;retryWrites=false&amp;amp;authMechanism=SCRAM-SHA-1&amp;amp;maxPoolSize=10&amp;amp;minPoolSize=2&amp;amp;connectTimeoutMS=5000&amp;amp;socketTimeoutMS=30000&amp;amp;serverSelectionTimeoutMS=5000"
      }
    ]
  }]'&lt;/span&gt;

&lt;span class="c"&gt;# Deploy to staging ONLY first&lt;/span&gt;
aws ecs update-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; myapp-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; myapp-staging &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task-definition&lt;/span&gt; myapp-backend:NEW_REVISION &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-new-deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the logs until you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 Server running on port 5000
MongoDB connected successfully ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Phase 5: The 504 Nightmare
&lt;/h2&gt;

&lt;p&gt;After switching staging to DocumentDB — &lt;strong&gt;login started taking 3-5 seconds.&lt;/strong&gt; Users were getting 504 Gateway Timeout errors.&lt;/p&gt;

&lt;p&gt;This one hurt.&lt;/p&gt;

&lt;p&gt;The root cause: &lt;strong&gt;DocumentDB connection latency is higher than Atlas.&lt;/strong&gt; Without connection pool configuration, every request was creating a new DB connection from scratch. Cold connections on DocumentDB take 3-5 seconds.&lt;/p&gt;

&lt;p&gt;The fix was adding connection pool parameters to the URI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;maxPoolSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;minPoolSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;connectTimeoutMS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;socketTimeoutMS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30000&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;serverSelectionTimeoutMS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;heartbeatFrequencyMS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full optimized string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongodb://user:pass@docdb-endpoint:27017/mydb?tls&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;tlsAllowInvalidCertificates&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;replicaSet&lt;span class="o"&gt;=&lt;/span&gt;rs0&amp;amp;retryWrites&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&amp;amp;authMechanism&lt;span class="o"&gt;=&lt;/span&gt;SCRAM-SHA-1&amp;amp;maxPoolSize&lt;span class="o"&gt;=&lt;/span&gt;10&amp;amp;minPoolSize&lt;span class="o"&gt;=&lt;/span&gt;2&amp;amp;connectTimeoutMS&lt;span class="o"&gt;=&lt;/span&gt;5000&amp;amp;socketTimeoutMS&lt;span class="o"&gt;=&lt;/span&gt;30000&amp;amp;serverSelectionTimeoutMS&lt;span class="o"&gt;=&lt;/span&gt;5000&amp;amp;heartbeatFrequencyMS&lt;span class="o"&gt;=&lt;/span&gt;10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Login went from 3-5 seconds → under 500ms.&lt;/strong&gt; ✅&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minPoolSize=2&lt;/code&gt; is the key — it keeps 2 warm connections alive at all times, so the first request never has to wait for a cold connection.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 6: The Re-sync Problem
&lt;/h2&gt;

&lt;p&gt;We migrated on Day 1. We deployed to staging on Day 3. We did a final check on Day 16.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atlas had 157 new documents that DocumentDB didn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is normal and expected. Any live database will accumulate new data between your migration and your cutover date. The fix is a re-sync run right before prod switchover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Fresh dump from Atlas&lt;/span&gt;
mongodump &lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"atlas-uri"&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/resync

&lt;span class="c"&gt;# Step 2: Transfer to EC2&lt;/span&gt;
scp &lt;span class="nt"&gt;-r&lt;/span&gt; /tmp/resync/ ubuntu@ec2-ip:/tmp/

&lt;span class="c"&gt;# Step 3: Restore with --drop (fully safe, idempotent)&lt;/span&gt;
mongorestore &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"docdb-uri"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/resync/mydb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--drop&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: Verify counts again&lt;/span&gt;
&lt;span class="c"&gt;# Atlas count == DocumentDB count? ✅ Good to go.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this re-sync as close to prod cutover as possible — ideally within hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Different in DocumentDB vs Atlas
&lt;/h2&gt;

&lt;p&gt;Before you migrate — test these in your app:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Atlas&lt;/th&gt;
&lt;th&gt;DocumentDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic CRUD&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mongoose ODM&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;$lookup&lt;/code&gt; aggregation&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions&lt;/td&gt;
&lt;td&gt;✅ Full ACID&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change streams&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atlas Search&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retryWrites=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ Must be false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text search&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If your app uses basic Mongoose CRUD — you're fine.&lt;/strong&gt; If you rely heavily on complex aggregations, transactions, or Atlas Search — test everything on staging before touching prod.&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer Access After Migration
&lt;/h2&gt;

&lt;p&gt;This confused our dev team at first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atlas:&lt;/strong&gt; Anyone with the connection string can connect from anywhere.&lt;br&gt;
&lt;strong&gt;DocumentDB:&lt;/strong&gt; Private VPC. Can't connect from outside without a tunnel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dev Laptop ──► SSH Tunnel ──► EC2 ──► DocumentDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH tunnel command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Keep this running in a terminal&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-keypair.pem &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 27017:docdb-endpoint:27017 &lt;span class="se"&gt;\&lt;/span&gt;
  ubuntu@ec2-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect to &lt;code&gt;127.0.0.1:27017&lt;/code&gt; from your app or MongoDB Compass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our recommendation:&lt;/strong&gt; Keep Atlas for local development. Use DocumentDB for prod/staging only. Zero friction, cleanest separation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rollback Plan (Non-Negotiable)
&lt;/h2&gt;

&lt;p&gt;Before every prod migration, write your rollback plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If DocumentDB fails:
1. Update ECS task def → revert MONGODB_URI to Atlas string
2. aws ecs update-service --force-new-deployment
3. Back on Atlas in under 5 minutes

Rule: Never delete Atlas until DocumentDB runs in prod for 30+ days.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We kept Atlas running as a safety net the entire time.&lt;/strong&gt; This is not optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (Atlas)&lt;/th&gt;
&lt;th&gt;After (DocumentDB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cross-cloud data transfer&lt;/td&gt;
&lt;td&gt;Charged per GB&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Free&lt;/strong&gt; (same VPC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public internet exposure&lt;/td&gt;
&lt;td&gt;✅ DB accessible publicly&lt;/td&gt;
&lt;td&gt;❌ DB fully private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security surface&lt;/td&gt;
&lt;td&gt;Atlas IP whitelist&lt;/td&gt;
&lt;td&gt;AWS Security Groups + VPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Atlas UI&lt;/td&gt;
&lt;td&gt;CloudWatch + Alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup&lt;/td&gt;
&lt;td&gt;Atlas managed&lt;/td&gt;
&lt;td&gt;AWS automated + S3 cron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login response time (after pool fix)&lt;/td&gt;
&lt;td&gt;~200ms&lt;/td&gt;
&lt;td&gt;~450ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Complete Migration Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFRASTRUCTURE
☐ DocumentDB subnet group (private subnets, 2+ AZs)
☐ Security group (port 27017, VPC only)
☐ Allow ECS + EC2 security groups in DocDB SG
☐ DocumentDB cluster (storage encrypted)
☐ DocumentDB instance (right size for workload)

MIGRATION
☐ mongodump full backup from Atlas
☐ Verify dump files exist for all collections
☐ scp files to EC2
☐ Download global-bundle.pem on EC2
☐ mongorestore with --drop flag
☐ Verify doc counts match on EVERY collection

APPLICATION
☐ New MONGODB_URI with all required params
☐ Add connection pool params to URI
☐ Deploy to staging first
☐ Watch logs for "MongoDB connected successfully"
☐ Test all critical API endpoints on staging
☐ Monitor for 24h on staging before touching prod
☐ Re-sync Atlas → DocumentDB right before prod cutover
☐ Deploy to prod
☐ Monitor prod logs and CloudWatch

POST-MIGRATION
☐ Set up daily mongodump → S3 backup cron
☐ Configure CloudWatch alarms (connections, CPU, latency)
☐ Keep Atlas alive for 30 days minimum
☐ Delete Atlas cluster only after team sign-off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The bridge EC2 pattern is your best friend.&lt;/strong&gt;&lt;br&gt;
DocumentDB's private-only design is a feature. Use your existing EC2 as the migration bridge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Connection pool tuning is not optional.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;minPoolSize=2&lt;/code&gt; alone will save you from 504 nightmares. Add it from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Always staging first.&lt;/strong&gt;&lt;br&gt;
We caught the 504 issue on staging before it ever touched prod. This is why staging exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Plan for re-sync.&lt;/strong&gt;&lt;br&gt;
Your live database never stops growing. Always re-sync within hours of prod cutover, not days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Keep Atlas longer than you think you need to.&lt;/strong&gt;&lt;br&gt;
The cost of one extra month of Atlas is nothing compared to the cost of a production incident with no rollback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Your app code changes nothing.&lt;/strong&gt;&lt;br&gt;
With Mongoose, only the &lt;code&gt;MONGODB_URI&lt;/code&gt; env var changes. Zero code changes required.&lt;/p&gt;




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

&lt;p&gt;Moving from Atlas to DocumentDB is fundamentally a security and infrastructure consolidation play. Your data moves from the public internet into a private VPC. Your costs consolidate to one provider. Your team manages one less external service.&lt;/p&gt;

&lt;p&gt;The technical work is real — but it's manageable. The hardest part isn't the commands. It's the patience to test everything on staging, verify every collection count, and resist the urge to rush to prod.&lt;/p&gt;

&lt;p&gt;Do it right, and your app code never knows the difference.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions about your own migration? Drop them in the comments — happy to help.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this saved you time, consider sharing it with someone who's about to go through the same thing.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#AWS&lt;/code&gt; &lt;code&gt;#MongoDB&lt;/code&gt; &lt;code&gt;#DocumentDB&lt;/code&gt; &lt;code&gt;#DevOps&lt;/code&gt; &lt;code&gt;#DatabaseMigration&lt;/code&gt; &lt;code&gt;#BackendEngineering&lt;/code&gt; &lt;code&gt;#CloudArchitecture&lt;/code&gt; &lt;code&gt;#NodeJS&lt;/code&gt; &lt;code&gt;#ECS&lt;/code&gt; &lt;code&gt;#SoftwareEngineering&lt;/code&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mongodb</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <item>
      <title>💻 AWS RDS Services: Comprehensive Guide</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Sun, 02 Mar 2025 05:40:43 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/-aws-rds-services-comprehensive-guide-19i4</link>
      <guid>https://dev.to/elonerajeev1/-aws-rds-services-comprehensive-guide-19i4</guid>
      <description>&lt;p&gt;AWS Relational Database Service (&lt;strong&gt;RDS&lt;/strong&gt;) is a managed service for setting up, operating, and scaling relational databases in the cloud. This guide will help you understand RDS theoretically and implement it practically.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 &lt;strong&gt;What is AWS RDS?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AWS RDS is a &lt;strong&gt;fully managed relational database service&lt;/strong&gt; designed to simplify database management tasks like setup, scaling, patching, and backups while providing high availability and durability. It supports popular database engines such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Aurora&lt;/strong&gt; (PostgreSQL and MySQL-compatible)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft SQL Server&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📋 &lt;strong&gt;Key Features of RDS&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Managed Service&lt;/strong&gt;: Automates database administration tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ Deployment&lt;/strong&gt;: Ensures high availability and automatic failover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Easy to scale storage and compute capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup and Recovery&lt;/strong&gt;: Automated backups, manual snapshots, and point-in-time recovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Encryption at rest and in transit, IAM integration, and VPC support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: Integrated with CloudWatch for performance metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for Read Replicas&lt;/strong&gt;: Improves read performance by replicating data across multiple instances.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🛠️ &lt;strong&gt;Practical Guide: Getting Started with RDS&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Part 1: Create an RDS Instance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log in to AWS Console&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;RDS Dashboard&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Click on "Create Database"&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Standard Create&lt;/strong&gt; for advanced configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose Database Engine&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select an engine like &lt;strong&gt;MySQL&lt;/strong&gt;, &lt;strong&gt;PostgreSQL&lt;/strong&gt;, or &lt;strong&gt;Amazon Aurora&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specify Instance Details&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DB Instance Class&lt;/strong&gt;: Choose based on your workload (e.g., db.t2.micro for testing).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: Allocate storage (default: 20 GB).
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Connectivity&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt;: Choose a VPC or create a new one.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Access&lt;/strong&gt;: Enable or disable based on your requirements.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups&lt;/strong&gt;: Ensure proper inbound/outbound rules.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Additional Settings&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable &lt;strong&gt;Multi-AZ Deployment&lt;/strong&gt; for production environments.
&lt;/li&gt;
&lt;li&gt;Set up automated backups and specify a backup retention period.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Launch the Instance&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review and click &lt;strong&gt;"Create Database"&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Part 2: Connect to the RDS Instance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Retrieve Connection Information&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the RDS dashboard, select your instance, and copy the &lt;strong&gt;Endpoint&lt;/strong&gt; and &lt;strong&gt;Port&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access via a SQL Client&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a client like MySQL Workbench, pgAdmin, or any terminal-based tool.
&lt;/li&gt;
&lt;li&gt;Example for MySQL:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; mysql &lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;endpoint&amp;gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;username&amp;gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test the Connection&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and query a sample database:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;testdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;testdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Part 3: Enable Read Replicas for Scalability&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a Read Replica&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the RDS instance details.
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;"Create Read Replica"&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the Replica&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specify instance class and storage.
&lt;/li&gt;
&lt;li&gt;Assign a unique identifier and launch it.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use the Replica for Read Operations&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to the replica using its endpoint for read-only queries.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Part 4: Automate Backup and Recovery&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automated Backups&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabled by default. Modify settings under &lt;strong&gt;Backup Configuration&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manual Snapshots&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the RDS dashboard, select your instance, and click &lt;strong&gt;"Take Snapshot"&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restore from Snapshot&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to Snapshots, choose a snapshot, and click &lt;strong&gt;"Restore Snapshot"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔒 &lt;strong&gt;RDS Security Best Practices&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VPC Isolation&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy RDS instances within private subnets of a VPC.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IAM Policies&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restrict access to RDS resources based on roles.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable encryption for data at rest using AWS KMS.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Control&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use security groups to define inbound/outbound traffic rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📊 &lt;strong&gt;Monitoring and Optimization&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CloudWatch Metrics&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor CPU utilization, IOPS, connections, and latency.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Insights&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use AWS RDS Performance Insights for analyzing query performance.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scaling&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify the instance class or use &lt;strong&gt;Aurora Serverless&lt;/strong&gt; for automatic scaling.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🛠️ &lt;strong&gt;AWS CLI Commands for RDS&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create RDS Instance&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws rds create-db-instance &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; mydbinstance &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--db-instance-class&lt;/span&gt; db.t2.micro &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--engine&lt;/span&gt; mysql &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--allocated-storage&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--master-username&lt;/span&gt; admin &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--master-user-password&lt;/span&gt; mypassword &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--backup-retention-period&lt;/span&gt; 7 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; us-east-1a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List RDS Instances&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws rds describe-db-instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Delete RDS Instance&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws rds delete-db-instance &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; mydbinstance &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--skip-final-snapshot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔗 &lt;strong&gt;Use Cases of AWS RDS&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Web Applications&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store application data for websites built with frameworks like Django, Laravel, or Spring.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Warehousing&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use read replicas to offload analytical queries.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ERP and CRM Systems&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host relational data for enterprise-grade systems.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📂 &lt;strong&gt;Additional Resources&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/rds/" rel="noopener noreferrer"&gt;AWS RDS Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://calculator.aws/#/" rel="noopener noreferrer"&gt;AWS Pricing Calculator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Start leveraging &lt;strong&gt;AWS RDS&lt;/strong&gt; for your database needs to save time, increase scalability, and ensure data reliability. 🚀  &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;

</description>
      <category>aws</category>
      <category>rds</category>
      <category>database</category>
    </item>
    <item>
      <title>🚀 Learn AWS EC2: Ultimate Beginner's Guide 🌐</title>
      <dc:creator>Rajeev</dc:creator>
      <pubDate>Sun, 02 Mar 2025 05:39:08 +0000</pubDate>
      <link>https://dev.to/elonerajeev1/learn-aws-ec2-ultimate-beginners-guide-30l5</link>
      <guid>https://dev.to/elonerajeev1/learn-aws-ec2-ultimate-beginners-guide-30l5</guid>
      <description>&lt;p&gt;Welcome to the &lt;strong&gt;AWS EC2 Tutorial&lt;/strong&gt;! This guide will take you through everything you need to know about Amazon Elastic Compute Cloud (&lt;strong&gt;EC2&lt;/strong&gt;)—from launching instances to hosting your first webpage!  &lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 &lt;strong&gt;What is AWS EC2?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Amazon EC2 is a web service that provides resizable compute capacity in the cloud. It enables businesses to deploy applications quickly without hardware investments.  &lt;/p&gt;




&lt;h2&gt;
  
  
  📋 &lt;strong&gt;Table of Contents&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
🌟 Why Learn EC2?
&lt;/li&gt;
&lt;li&gt;
🚀 Getting Started
&lt;/li&gt;
&lt;li&gt;
📦 EC2 Instance Types
&lt;/li&gt;
&lt;li&gt;
⚙️ Launching an EC2 Instance
&lt;/li&gt;
&lt;li&gt;
🌐 Hosting a Simple Webpage
&lt;/li&gt;
&lt;li&gt;
🔒 Security Best Practices
&lt;/li&gt;
&lt;li&gt;
📈 Monitoring and Scaling
&lt;/li&gt;
&lt;li&gt;
🎉 Conclusion
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🌟 &lt;strong&gt;Why Learn EC2?&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🚀 Cloud Computing Mastery&lt;/strong&gt;: EC2 is the backbone of AWS services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💼 Career Growth&lt;/strong&gt;: EC2 knowledge is crucial for DevOps, cloud engineers, and developers.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🌍 Real-World Applications&lt;/strong&gt;: Use EC2 to host websites, deploy applications, or run data pipelines.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 &lt;strong&gt;Getting Started&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you begin, ensure you have:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ An &lt;strong&gt;AWS account&lt;/strong&gt; (free-tier eligible).
&lt;/li&gt;
&lt;li&gt;✅ Basic knowledge of cloud computing.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📦 &lt;strong&gt;EC2 Instance Types&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AWS EC2 offers various instance types tailored for different workloads:  &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;General&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Balanced performance&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;t2.micro&lt;/code&gt;, &lt;code&gt;t3.small&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-performance computing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;c5.large&lt;/code&gt;, &lt;code&gt;c6i.xlarge&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory-intensive apps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;r5.large&lt;/code&gt;, &lt;code&gt;r6g.xlarge&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data-heavy workloads&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;i3.large&lt;/code&gt;, &lt;code&gt;i4i.xlarge&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Machine learning, gaming&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;g5.large&lt;/code&gt;, &lt;code&gt;p3.2xlarge&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚙️ &lt;strong&gt;Launching an EC2 Instance&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Follow these steps to launch your first EC2 instance:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Login to AWS Console&lt;/strong&gt; 🖥️  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to &lt;strong&gt;EC2&lt;/strong&gt; under the "Compute" section.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Click "Launch Instance"&lt;/strong&gt; 🚀  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a name for your instance.
&lt;/li&gt;
&lt;li&gt;Select an &lt;strong&gt;AMI (Amazon Machine Image)&lt;/strong&gt; like &lt;strong&gt;Ubuntu&lt;/strong&gt;, &lt;strong&gt;Amazon Linux&lt;/strong&gt;, or &lt;strong&gt;Windows Server&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose an Instance Type&lt;/strong&gt; 🖱️  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For beginners, choose &lt;code&gt;t2.micro&lt;/code&gt; (free-tier eligible).
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Security Groups&lt;/strong&gt; 🔒  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add rules to allow inbound traffic for &lt;strong&gt;HTTP (port 80)&lt;/strong&gt; and &lt;strong&gt;SSH (port 22)&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key Pair&lt;/strong&gt; 🗝️  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create or select an existing key pair for secure access.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Launch Your Instance&lt;/strong&gt; 🎉  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Launch Instance&lt;/strong&gt; and wait for it to initialize.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🌐 &lt;strong&gt;Hosting a Simple Webpage&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s how you can host a basic &lt;code&gt;index.html&lt;/code&gt; file on your EC2 instance.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install a Web Server
&lt;/h3&gt;

&lt;p&gt;Log into your EC2 instance via SSH and run the following commands to install a web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update the package manager&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update  

&lt;span class="c"&gt;# Install Apache2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;apache2 &lt;span class="nt"&gt;-y&lt;/span&gt;  

&lt;span class="c"&gt;# Start Apache2 service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start apache2  

&lt;span class="c"&gt;# Enable Apache2 to start on boot&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;apache2  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create Your Webpage
&lt;/h3&gt;

&lt;p&gt;Navigate to the default web server directory and create an &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to the default directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/html  

&lt;span class="c"&gt;# Create or edit the index.html file&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano index.html  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following content to your &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Welcome to My Webpage&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;🚀 Welcome to My First AWS EC2 Hosted Webpage!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is a simple webpage hosted on an EC2 instance.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit (&lt;code&gt;Ctrl+O&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Ctrl+X&lt;/code&gt;).  &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Open Your Webpage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;In your browser, navigate to:
&lt;code&gt;http://&amp;lt;Public_IP_of_EC2_Instance&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should see your &lt;code&gt;index.html&lt;/code&gt; webpage live! 🎉  &lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 &lt;strong&gt;Security Best Practices&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Security Groups&lt;/strong&gt;: Restrict inbound traffic to only required ports.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key Management&lt;/strong&gt;: Keep your &lt;code&gt;.pem&lt;/code&gt; file secure.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles&lt;/strong&gt;: Assign least privilege roles to your instances.
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🎉 &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With this guide, you’ve:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launched your first EC2 instance.
&lt;/li&gt;
&lt;li&gt;Installed a web server.
&lt;/li&gt;
&lt;li&gt;Hosted your first &lt;code&gt;index.html&lt;/code&gt; webpage.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continue exploring AWS to enhance your cloud computing skills!  &lt;/p&gt;

&lt;h3&gt;
  
  
  Need Help? Reach out on &lt;a href="https://forums.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Forums&lt;/a&gt; or join the &lt;a href="https://aws.amazon.com/community/" rel="noopener noreferrer"&gt;AWS Community&lt;/a&gt;.
&lt;/h3&gt;




&lt;p&gt;&lt;strong&gt;Happy Hosting!&lt;/strong&gt; 🚀&lt;/p&gt;



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

By Rajeev Kumar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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