[{"data":1,"prerenderedAt":1599},["ShallowReactive",2],{"navigation":3,"/blog/aws-webdev":50},[4],{"title":5,"path":6,"stem":7,"children":8,"page":49},"Blog","/blog","blog",[9,13,17,21,25,29,33,37,41,45],{"title":10,"path":11,"stem":12},"Unlocking the Power of Django: A Developer-Friendly Architecture Guide","/blog/django-arhitecture","blog/01.django-arhitecture",{"title":14,"path":15,"stem":16},"Getting Started with Go: Why You Should Learn Golang","/blog/intro-go","blog/02.intro-go",{"title":18,"path":19,"stem":20},"Recursion in JavaScript: The Art of Functions Calling Themselves","/blog/recursion-js","blog/03.recursion-js",{"title":22,"path":23,"stem":24},"What Go Got Right and Wrong: A Thoughtful Look Back","/blog/golang-pro-and-cons","blog/04.golang-pro-and-cons",{"title":26,"path":27,"stem":28},"JavaScript Performance Optimization: A Practical Guide for Better Web Applications","/blog/js-perf","blog/05.js-perf",{"title":30,"path":31,"stem":32},"Understanding Go Slices: A Powerful Data Structure","/blog/go-slices-article","blog/06.go-slices-article",{"title":34,"path":35,"stem":36},"The Model Context Protocol (MCP): Giving AI \"Hands\"","/blog/mcp-article-complete","blog/07.mcp-article-complete",{"title":38,"path":39,"stem":40},"CSS Grid Mastery: Advanced Layout Techniques for Intermediate Developers","/blog/css-grid","blog/08.css-grid",{"title":42,"path":43,"stem":44},"Modern JavaScript: Mastering the Fetch API with Async/Await","/blog/fetch-api","blog/09.fetch-api",{"title":46,"path":47,"stem":48},"How to Use AWS for Cloud-Based Web Development","/blog/aws-webdev","blog/10.aws-webdev",false,[51,1596],{"id":52,"title":46,"author":53,"badge":59,"body":61,"date":1583,"description":1584,"extension":1585,"image":1586,"meta":1587,"navigation":150,"path":47,"readingTime":1588,"seo":1593,"stem":48,"tags":1594,"__hash__":1595},"blog/blog/10.aws-webdev.md",[54],{"name":55,"to":56,"avatar":57},"Norbert Br3tt","https://www.linkedin.com/in/norbert-brett/",{"src":58,"alt":55},"https://res.cloudinary.com/nbrett/image/upload/v1725625689/IMG_0698_d0nhun.jpg",{"label":60},"AWS, Cloud, Backend",{"type":62,"value":63,"toc":1572},"minimark",[64,68,72,75,78,83,94,102,105,173,184,188,191,194,305,312,319,323,326,356,359,589,596,599,637,640,644,647,718,729,732,838,841,891,894,898,901,988,994,997,1153,1156,1160,1163,1329,1332,1428,1431,1435,1438,1446,1449,1452,1456,1459,1508,1511,1515,1518,1521,1524,1527,1532,1568],[65,66,46],"h1",{"id":67},"how-to-use-aws-for-cloud-based-web-development",[69,70,71],"p",{},"Storytime: the first time I deployed something to AWS, I opened the console, saw the sidebar with 200+ services listed, and immediately closed the tab. I came back two days later, deployed to the wrong region, and then spent an afternoon wondering why my app was slow before realizing my database was in Ohio and my users were in Europe.",[69,73,74],{},"Nobody tells you this stuff. They just say \"use AWS\" like it's a single thing.",[69,76,77],{},"AWS is not a single thing. It's a platform of building blocks, and the trick is knowing which three or four blocks you actually need for web development — and ignoring the rest until you need them. Let me walk you through the ones that matter.",[79,80,82],"h2",{"id":81},"before-you-do-anything","Before You Do Anything",[69,84,85,86,93],{},"You'll need an AWS account. Go to ",[87,88,92],"a",{"href":89,"rel":90},"https://aws.amazon.com",[91],"nofollow","aws.amazon.com"," and sign up. You get a generous free tier for 12 months that covers everything in this post.",[69,95,96,97,101],{},"One strong recommendation before you touch a single service: ",[98,99,100],"strong",{},"set up billing alerts immediately."," AWS charges by usage, and it's easy to forget a running instance. Go to the Billing console, set an alert at $5 and another at $20, and you'll never have a surprise invoice. I do this first on every account I create.",[69,103,104],{},"Also: never put your root account credentials anywhere near your code. Create an IAM user with appropriate permissions and use those credentials instead.",[106,107,112],"pre",{"className":108,"code":109,"language":110,"meta":111,"style":111},"language-bash shiki shiki-themes vitesse-dark","# Install the AWS CLI\nnpm install -g aws-cli   # or use the official installer\n\n# Configure with your IAM credentials\naws configure\n# Prompts you for Access Key ID, Secret Access Key, region, output format\n","bash","",[113,114,115,124,145,152,158,167],"code",{"__ignoreMap":111},[116,117,120],"span",{"class":118,"line":119},"line",1,[116,121,123],{"class":122},"sux-A","# Install the AWS CLI\n",[116,125,127,131,135,139,142],{"class":118,"line":126},2,[116,128,130],{"class":129},"sCK9x","npm",[116,132,134],{"class":133},"s7rlk"," install",[116,136,138],{"class":137},"sXjYR"," -g",[116,140,141],{"class":133}," aws-cli",[116,143,144],{"class":122},"   # or use the official installer\n",[116,146,148],{"class":118,"line":147},3,[116,149,151],{"emptyLinePlaceholder":150},true,"\n",[116,153,155],{"class":118,"line":154},4,[116,156,157],{"class":122},"# Configure with your IAM credentials\n",[116,159,161,164],{"class":118,"line":160},5,[116,162,163],{"class":129},"aws",[116,165,166],{"class":133}," configure\n",[116,168,170],{"class":118,"line":169},6,[116,171,172],{"class":122},"# Prompts you for Access Key ID, Secret Access Key, region, output format\n",[69,174,175,176,179,180,183],{},"Pick your region here. Choose whatever's geographically closest to your users — this matters for latency. I use ",[113,177,178],{},"us-east-1"," for US projects and ",[113,181,182],{},"eu-west-1"," for European ones.",[79,185,187],{"id":186},"s3-your-first-stop-for-static-sites","S3: Your First Stop for Static Sites",[69,189,190],{},"If you're deploying a static site — a React, Vue, or plain HTML/CSS app — S3 is where you start. S3 (Simple Storage Service) is object storage, but with one setting flipped, it becomes a web host.",[69,192,193],{},"Let's set it up:",[106,195,197],{"className":108,"code":196,"language":110,"meta":111,"style":111},"# Create a bucket (bucket names must be globally unique)\naws s3 mb s3://my-awesome-app --region us-east-1\n\n# Enable static website hosting\naws s3 website s3://my-awesome-app \\\n  --index-document index.html \\\n  --error-document index.html\n\n# Build your app and deploy\nnpm run build\naws s3 sync ./dist s3://my-awesome-app --delete\n",[113,198,199,204,223,227,232,246,256,265,270,276,287],{"__ignoreMap":111},[116,200,201],{"class":118,"line":119},[116,202,203],{"class":122},"# Create a bucket (bucket names must be globally unique)\n",[116,205,206,208,211,214,217,220],{"class":118,"line":126},[116,207,163],{"class":129},[116,209,210],{"class":133}," s3",[116,212,213],{"class":133}," mb",[116,215,216],{"class":133}," s3://my-awesome-app",[116,218,219],{"class":137}," --region",[116,221,222],{"class":133}," us-east-1\n",[116,224,225],{"class":118,"line":147},[116,226,151],{"emptyLinePlaceholder":150},[116,228,229],{"class":118,"line":154},[116,230,231],{"class":122},"# Enable static website hosting\n",[116,233,234,236,238,241,243],{"class":118,"line":160},[116,235,163],{"class":129},[116,237,210],{"class":133},[116,239,240],{"class":133}," website",[116,242,216],{"class":133},[116,244,245],{"class":137}," \\\n",[116,247,248,251,254],{"class":118,"line":169},[116,249,250],{"class":137},"  --index-document",[116,252,253],{"class":133}," index.html",[116,255,245],{"class":137},[116,257,259,262],{"class":118,"line":258},7,[116,260,261],{"class":137},"  --error-document",[116,263,264],{"class":133}," index.html\n",[116,266,268],{"class":118,"line":267},8,[116,269,151],{"emptyLinePlaceholder":150},[116,271,273],{"class":118,"line":272},9,[116,274,275],{"class":122},"# Build your app and deploy\n",[116,277,279,281,284],{"class":118,"line":278},10,[116,280,130],{"class":129},[116,282,283],{"class":133}," run",[116,285,286],{"class":133}," build\n",[116,288,290,292,294,297,300,302],{"class":118,"line":289},11,[116,291,163],{"class":129},[116,293,210],{"class":133},[116,295,296],{"class":133}," sync",[116,298,299],{"class":133}," ./dist",[116,301,216],{"class":133},[116,303,304],{"class":137}," --delete\n",[69,306,307,308,311],{},"That ",[113,309,310],{},"--delete"," flag removes files from S3 that no longer exist in your build output. Omit it once and you'll be serving old JavaScript indefinitely — I've done this, it's confusing.",[69,313,314,315,318],{},"Your site is now live at a URL like ",[113,316,317],{},"http://my-awesome-app.s3-website-us-east-1.amazonaws.com",". Not pretty, but it works.",[79,320,322],{"id":321},"cloudfront-the-layer-youll-always-want","CloudFront: The Layer You'll Always Want",[69,324,325],{},"S3 hosting alone gives you a slow, ugly URL with no HTTPS. CloudFront is AWS's CDN — it caches your site at edge locations worldwide and adds HTTPS. For any production site, these two live together.",[106,327,329],{"className":108,"code":328,"language":110,"meta":111,"style":111},"# Create a CloudFront distribution pointing at your S3 bucket\naws cloudfront create-distribution \\\n  --distribution-config file://cloudfront-config.json\n",[113,330,331,336,348],{"__ignoreMap":111},[116,332,333],{"class":118,"line":119},[116,334,335],{"class":122},"# Create a CloudFront distribution pointing at your S3 bucket\n",[116,337,338,340,343,346],{"class":118,"line":126},[116,339,163],{"class":129},[116,341,342],{"class":133}," cloudfront",[116,344,345],{"class":133}," create-distribution",[116,347,245],{"class":137},[116,349,350,353],{"class":118,"line":147},[116,351,352],{"class":137},"  --distribution-config",[116,354,355],{"class":133}," file://cloudfront-config.json\n",[69,357,358],{},"The config file handles the details — origin (your S3 bucket), price class, SSL certificate. Here's the minimal version:",[106,360,364],{"className":361,"code":362,"language":363,"meta":111,"style":111},"language-json shiki shiki-themes vitesse-dark","{\n  \"Origins\": {\n    \"Items\": [{\n      \"Id\": \"S3-my-awesome-app\",\n      \"DomainName\": \"my-awesome-app.s3.amazonaws.com\",\n      \"S3OriginConfig\": { \"OriginAccessIdentity\": \"\" }\n    }]\n  },\n  \"DefaultCacheBehavior\": {\n    \"ViewerProtocolPolicy\": \"redirect-to-https\",\n    \"CachePolicyId\": \"658327ea-f89d-4fab-a63d-7e88639e58f6\"\n  },\n  \"DefaultRootObject\": \"index.html\",\n  \"Enabled\": true\n}\n","json",[113,365,366,372,391,406,430,450,479,484,489,502,522,541,546,567,583],{"__ignoreMap":111},[116,367,368],{"class":118,"line":119},[116,369,371],{"class":370},"s_pn2","{\n",[116,373,374,378,382,385,388],{"class":118,"line":126},[116,375,377],{"class":376},"s6USN","  \"",[116,379,381],{"class":380},"sm68I","Origins",[116,383,384],{"class":376},"\"",[116,386,387],{"class":370},":",[116,389,390],{"class":370}," {\n",[116,392,393,396,399,401,403],{"class":118,"line":147},[116,394,395],{"class":376},"    \"",[116,397,398],{"class":380},"Items",[116,400,384],{"class":376},[116,402,387],{"class":370},[116,404,405],{"class":370}," [{\n",[116,407,408,411,414,416,418,422,425,427],{"class":118,"line":154},[116,409,410],{"class":376},"      \"",[116,412,413],{"class":380},"Id",[116,415,384],{"class":376},[116,417,387],{"class":370},[116,419,421],{"class":420},"sNJcY"," \"",[116,423,424],{"class":133},"S3-my-awesome-app",[116,426,384],{"class":420},[116,428,429],{"class":370},",\n",[116,431,432,434,437,439,441,443,446,448],{"class":118,"line":160},[116,433,410],{"class":376},[116,435,436],{"class":380},"DomainName",[116,438,384],{"class":376},[116,440,387],{"class":370},[116,442,421],{"class":420},[116,444,445],{"class":133},"my-awesome-app.s3.amazonaws.com",[116,447,384],{"class":420},[116,449,429],{"class":370},[116,451,452,454,457,459,461,464,466,469,471,473,476],{"class":118,"line":169},[116,453,410],{"class":376},[116,455,456],{"class":380},"S3OriginConfig",[116,458,384],{"class":376},[116,460,387],{"class":370},[116,462,463],{"class":370}," {",[116,465,421],{"class":376},[116,467,468],{"class":380},"OriginAccessIdentity",[116,470,384],{"class":376},[116,472,387],{"class":370},[116,474,475],{"class":420}," \"\"",[116,477,478],{"class":370}," }\n",[116,480,481],{"class":118,"line":258},[116,482,483],{"class":370},"    }]\n",[116,485,486],{"class":118,"line":267},[116,487,488],{"class":370},"  },\n",[116,490,491,493,496,498,500],{"class":118,"line":272},[116,492,377],{"class":376},[116,494,495],{"class":380},"DefaultCacheBehavior",[116,497,384],{"class":376},[116,499,387],{"class":370},[116,501,390],{"class":370},[116,503,504,506,509,511,513,515,518,520],{"class":118,"line":278},[116,505,395],{"class":376},[116,507,508],{"class":380},"ViewerProtocolPolicy",[116,510,384],{"class":376},[116,512,387],{"class":370},[116,514,421],{"class":420},[116,516,517],{"class":133},"redirect-to-https",[116,519,384],{"class":420},[116,521,429],{"class":370},[116,523,524,526,529,531,533,535,538],{"class":118,"line":289},[116,525,395],{"class":376},[116,527,528],{"class":380},"CachePolicyId",[116,530,384],{"class":376},[116,532,387],{"class":370},[116,534,421],{"class":420},[116,536,537],{"class":133},"658327ea-f89d-4fab-a63d-7e88639e58f6",[116,539,540],{"class":420},"\"\n",[116,542,544],{"class":118,"line":543},12,[116,545,488],{"class":370},[116,547,549,551,554,556,558,560,563,565],{"class":118,"line":548},13,[116,550,377],{"class":376},[116,552,553],{"class":380},"DefaultRootObject",[116,555,384],{"class":376},[116,557,387],{"class":370},[116,559,421],{"class":420},[116,561,562],{"class":133},"index.html",[116,564,384],{"class":420},[116,566,429],{"class":370},[116,568,570,572,575,577,579],{"class":118,"line":569},14,[116,571,377],{"class":376},[116,573,574],{"class":380},"Enabled",[116,576,384],{"class":376},[116,578,387],{"class":370},[116,580,582],{"class":581},"s3QIE"," true\n",[116,584,586],{"class":118,"line":585},15,[116,587,588],{"class":370},"}\n",[69,590,591,592,595],{},"Notice ",[113,593,594],{},"\"ViewerProtocolPolicy\": \"redirect-to-https\""," — that's the line that forces HTTPS. Always include it.",[69,597,598],{},"One thing that trips people up: CloudFront aggressively caches everything. When you deploy a new version, you need to invalidate the cache:",[106,600,602],{"className":108,"code":601,"language":110,"meta":111,"style":111},"aws cloudfront create-invalidation \\\n  --distribution-id YOUR_DIST_ID \\\n  --paths \"/*\"\n",[113,603,604,615,625],{"__ignoreMap":111},[116,605,606,608,610,613],{"class":118,"line":119},[116,607,163],{"class":129},[116,609,342],{"class":133},[116,611,612],{"class":133}," create-invalidation",[116,614,245],{"class":137},[116,616,617,620,623],{"class":118,"line":126},[116,618,619],{"class":137},"  --distribution-id",[116,621,622],{"class":133}," YOUR_DIST_ID",[116,624,245],{"class":137},[116,626,627,630,632,635],{"class":118,"line":147},[116,628,629],{"class":137},"  --paths",[116,631,421],{"class":420},[116,633,634],{"class":133},"/*",[116,636,540],{"class":420},[69,638,639],{},"Add this to your deploy script. Forget it once, spend 20 minutes wondering why your changes aren't showing up, add it permanently.",[79,641,643],{"id":642},"ec2-when-you-need-a-real-server","EC2: When You Need a Real Server",[69,645,646],{},"S3 + CloudFront covers static sites. But if you're running a Node.js API, a Python backend, or anything server-side, you need EC2 — a virtual machine you control.",[106,648,650],{"className":108,"code":649,"language":110,"meta":111,"style":111},"# Launch an instance (Amazon Linux 2, t3.micro — free tier eligible)\naws ec2 run-instances \\\n  --image-id ami-0c55b159cbfafe1f0 \\\n  --instance-type t3.micro \\\n  --key-name my-keypair \\\n  --security-group-ids sg-12345678 \\\n  --count 1\n",[113,651,652,657,669,679,689,699,709],{"__ignoreMap":111},[116,653,654],{"class":118,"line":119},[116,655,656],{"class":122},"# Launch an instance (Amazon Linux 2, t3.micro — free tier eligible)\n",[116,658,659,661,664,667],{"class":118,"line":126},[116,660,163],{"class":129},[116,662,663],{"class":133}," ec2",[116,665,666],{"class":133}," run-instances",[116,668,245],{"class":137},[116,670,671,674,677],{"class":118,"line":147},[116,672,673],{"class":137},"  --image-id",[116,675,676],{"class":133}," ami-0c55b159cbfafe1f0",[116,678,245],{"class":137},[116,680,681,684,687],{"class":118,"line":154},[116,682,683],{"class":137},"  --instance-type",[116,685,686],{"class":133}," t3.micro",[116,688,245],{"class":137},[116,690,691,694,697],{"class":118,"line":160},[116,692,693],{"class":137},"  --key-name",[116,695,696],{"class":133}," my-keypair",[116,698,245],{"class":137},[116,700,701,704,707],{"class":118,"line":169},[116,702,703],{"class":137},"  --security-group-ids",[116,705,706],{"class":133}," sg-12345678",[116,708,245],{"class":137},[116,710,711,714],{"class":118,"line":258},[116,712,713],{"class":137},"  --count",[116,715,717],{"class":716},"sxA9i"," 1\n",[69,719,720,721,724,725,728],{},"The ",[113,722,723],{},"key-name"," refers to an SSH key pair you create in the EC2 console first. Keep that ",[113,726,727],{},".pem"," file safe — losing it means losing SSH access to your instance.",[69,730,731],{},"Once it's running, SSH in and set up your environment:",[106,733,735],{"className":108,"code":734,"language":110,"meta":111,"style":111},"ssh -i \"my-keypair.pem\" ec2-user@your-instance-public-ip\n\n# On the instance:\nsudo yum update -y\nsudo yum install -y nodejs npm\n\n# Clone your app, install dependencies, start it\ngit clone https://github.com/you/your-app\ncd your-app\nnpm install\nnode server.js\n",[113,736,737,755,759,764,778,795,799,804,815,823,830],{"__ignoreMap":111},[116,738,739,742,745,747,750,752],{"class":118,"line":119},[116,740,741],{"class":129},"ssh",[116,743,744],{"class":137}," -i",[116,746,421],{"class":420},[116,748,749],{"class":133},"my-keypair.pem",[116,751,384],{"class":420},[116,753,754],{"class":133}," ec2-user@your-instance-public-ip\n",[116,756,757],{"class":118,"line":126},[116,758,151],{"emptyLinePlaceholder":150},[116,760,761],{"class":118,"line":147},[116,762,763],{"class":122},"# On the instance:\n",[116,765,766,769,772,775],{"class":118,"line":154},[116,767,768],{"class":129},"sudo",[116,770,771],{"class":133}," yum",[116,773,774],{"class":133}," update",[116,776,777],{"class":137}," -y\n",[116,779,780,782,784,786,789,792],{"class":118,"line":160},[116,781,768],{"class":129},[116,783,771],{"class":133},[116,785,134],{"class":133},[116,787,788],{"class":137}," -y",[116,790,791],{"class":133}," nodejs",[116,793,794],{"class":133}," npm\n",[116,796,797],{"class":118,"line":169},[116,798,151],{"emptyLinePlaceholder":150},[116,800,801],{"class":118,"line":258},[116,802,803],{"class":122},"# Clone your app, install dependencies, start it\n",[116,805,806,809,812],{"class":118,"line":267},[116,807,808],{"class":129},"git",[116,810,811],{"class":133}," clone",[116,813,814],{"class":133}," https://github.com/you/your-app\n",[116,816,817,820],{"class":118,"line":272},[116,818,819],{"class":380},"cd",[116,821,822],{"class":133}," your-app\n",[116,824,825,827],{"class":118,"line":278},[116,826,130],{"class":129},[116,828,829],{"class":133}," install\n",[116,831,832,835],{"class":118,"line":289},[116,833,834],{"class":129},"node",[116,836,837],{"class":133}," server.js\n",[69,839,840],{},"For production, you'll want a process manager so your app restarts if it crashes:",[106,842,844],{"className":108,"code":843,"language":110,"meta":111,"style":111},"npm install -g pm2\npm2 start server.js --name my-app\npm2 startup   # makes pm2 start on system boot\npm2 save\n",[113,845,846,857,874,884],{"__ignoreMap":111},[116,847,848,850,852,854],{"class":118,"line":119},[116,849,130],{"class":129},[116,851,134],{"class":133},[116,853,138],{"class":137},[116,855,856],{"class":133}," pm2\n",[116,858,859,862,865,868,871],{"class":118,"line":126},[116,860,861],{"class":129},"pm2",[116,863,864],{"class":133}," start",[116,866,867],{"class":133}," server.js",[116,869,870],{"class":137}," --name",[116,872,873],{"class":133}," my-app\n",[116,875,876,878,881],{"class":118,"line":147},[116,877,861],{"class":129},[116,879,880],{"class":133}," startup",[116,882,883],{"class":122},"   # makes pm2 start on system boot\n",[116,885,886,888],{"class":118,"line":154},[116,887,861],{"class":129},[116,889,890],{"class":133}," save\n",[69,892,893],{},"I personally reach for pm2 on every Node.js EC2 deployment. It's the difference between \"my app dies at 3am and nobody notices\" and \"my app restarts itself and sends me a notification.\"",[79,895,897],{"id":896},"rds-a-database-you-dont-have-to-babysit","RDS: A Database You Don't Have to Babysit",[69,899,900],{},"Running a database on your EC2 instance works but creates problems — backups, scaling, failover — that RDS (Relational Database Service) handles for you. RDS manages PostgreSQL, MySQL, and others as a managed service.",[106,902,904],{"className":108,"code":903,"language":110,"meta":111,"style":111},"# Create a PostgreSQL database\naws rds create-db-instance \\\n  --db-instance-identifier my-app-db \\\n  --db-instance-class db.t3.micro \\\n  --engine postgres \\\n  --master-username admin \\\n  --master-user-password your-secure-password \\\n  --allocated-storage 20 \\\n  --no-publicly-accessible\n",[113,905,906,911,923,933,943,953,963,973,983],{"__ignoreMap":111},[116,907,908],{"class":118,"line":119},[116,909,910],{"class":122},"# Create a PostgreSQL database\n",[116,912,913,915,918,921],{"class":118,"line":126},[116,914,163],{"class":129},[116,916,917],{"class":133}," rds",[116,919,920],{"class":133}," create-db-instance",[116,922,245],{"class":137},[116,924,925,928,931],{"class":118,"line":147},[116,926,927],{"class":137},"  --db-instance-identifier",[116,929,930],{"class":133}," my-app-db",[116,932,245],{"class":137},[116,934,935,938,941],{"class":118,"line":154},[116,936,937],{"class":137},"  --db-instance-class",[116,939,940],{"class":133}," db.t3.micro",[116,942,245],{"class":137},[116,944,945,948,951],{"class":118,"line":160},[116,946,947],{"class":137},"  --engine",[116,949,950],{"class":133}," postgres",[116,952,245],{"class":137},[116,954,955,958,961],{"class":118,"line":169},[116,956,957],{"class":137},"  --master-username",[116,959,960],{"class":133}," admin",[116,962,245],{"class":137},[116,964,965,968,971],{"class":118,"line":258},[116,966,967],{"class":137},"  --master-user-password",[116,969,970],{"class":133}," your-secure-password",[116,972,245],{"class":137},[116,974,975,978,981],{"class":118,"line":267},[116,976,977],{"class":137},"  --allocated-storage",[116,979,980],{"class":716}," 20",[116,982,245],{"class":137},[116,984,985],{"class":118,"line":272},[116,986,987],{"class":137},"  --no-publicly-accessible\n",[69,989,591,990,993],{},[113,991,992],{},"--no-publicly-accessible",". Your database should never be directly reachable from the internet — only from your EC2 instance (or Lambda, or whatever's in your VPC). This is the setting people skip and then regret.",[69,995,996],{},"Connect from your app using the RDS endpoint:",[106,998,1002],{"className":999,"code":1000,"language":1001,"meta":111,"style":111},"language-javascript shiki shiki-themes vitesse-dark","const { Pool } = require('pg');\n\nconst pool = new Pool({\n  host: 'my-app-db.abc123.us-east-1.rds.amazonaws.com',\n  port: 5432,\n  database: 'myapp',\n  user: process.env.DB_USER,\n  password: process.env.DB_PASSWORD,\n});\n","javascript",[113,1003,1004,1039,1043,1060,1077,1089,1105,1128,1148],{"__ignoreMap":111},[116,1005,1006,1010,1012,1016,1019,1022,1025,1028,1031,1034,1036],{"class":118,"line":119},[116,1007,1009],{"class":1008},"s_wWq","const",[116,1011,463],{"class":370},[116,1013,1015],{"class":1014},"st-jp"," Pool",[116,1017,1018],{"class":370}," }",[116,1020,1021],{"class":370}," =",[116,1023,1024],{"class":129}," require",[116,1026,1027],{"class":370},"(",[116,1029,1030],{"class":420},"'",[116,1032,1033],{"class":133},"pg",[116,1035,1030],{"class":420},[116,1037,1038],{"class":370},");\n",[116,1040,1041],{"class":118,"line":126},[116,1042,151],{"emptyLinePlaceholder":150},[116,1044,1045,1047,1050,1052,1055,1057],{"class":118,"line":147},[116,1046,1009],{"class":1008},[116,1048,1049],{"class":1014}," pool",[116,1051,1021],{"class":370},[116,1053,1054],{"class":1008}," new",[116,1056,1015],{"class":129},[116,1058,1059],{"class":370},"({\n",[116,1061,1062,1065,1067,1070,1073,1075],{"class":118,"line":154},[116,1063,1064],{"class":380},"  host",[116,1066,387],{"class":370},[116,1068,1069],{"class":420}," '",[116,1071,1072],{"class":133},"my-app-db.abc123.us-east-1.rds.amazonaws.com",[116,1074,1030],{"class":420},[116,1076,429],{"class":370},[116,1078,1079,1082,1084,1087],{"class":118,"line":160},[116,1080,1081],{"class":380},"  port",[116,1083,387],{"class":370},[116,1085,1086],{"class":716}," 5432",[116,1088,429],{"class":370},[116,1090,1091,1094,1096,1098,1101,1103],{"class":118,"line":169},[116,1092,1093],{"class":380},"  database",[116,1095,387],{"class":370},[116,1097,1069],{"class":420},[116,1099,1100],{"class":133},"myapp",[116,1102,1030],{"class":420},[116,1104,429],{"class":370},[116,1106,1107,1110,1112,1115,1118,1121,1123,1126],{"class":118,"line":258},[116,1108,1109],{"class":380},"  user",[116,1111,387],{"class":370},[116,1113,1114],{"class":1014}," process",[116,1116,1117],{"class":370},".",[116,1119,1120],{"class":1014},"env",[116,1122,1117],{"class":370},[116,1124,1125],{"class":1014},"DB_USER",[116,1127,429],{"class":370},[116,1129,1130,1133,1135,1137,1139,1141,1143,1146],{"class":118,"line":267},[116,1131,1132],{"class":380},"  password",[116,1134,387],{"class":370},[116,1136,1114],{"class":1014},[116,1138,1117],{"class":370},[116,1140,1120],{"class":1014},[116,1142,1117],{"class":370},[116,1144,1145],{"class":1014},"DB_PASSWORD",[116,1147,429],{"class":370},[116,1149,1150],{"class":118,"line":272},[116,1151,1152],{"class":370},"});\n",[69,1154,1155],{},"Store credentials in environment variables, not in code. Always. RDS gives you automated backups, point-in-time recovery, and easy vertical scaling — things that would take days to build yourself on a raw EC2 instance.",[79,1157,1159],{"id":1158},"lambda-for-the-things-you-dont-run-constantly","Lambda: For the Things You Don't Run Constantly",[69,1161,1162],{},"Not everything needs a persistent server. If you have a webhook handler, an image resize job, a scheduled report, or any function that runs occasionally — Lambda is your friend. You pay only when it runs, and you don't manage any servers.",[106,1164,1166],{"className":999,"code":1165,"language":1001,"meta":111,"style":111},"// A simple Lambda handler\nexports.handler = async (event) => {\n  const body = JSON.parse(event.body);\n  \n  // Do your thing\n  const result = await processWebhook(body);\n  \n  return {\n    statusCode: 200,\n    body: JSON.stringify({ success: true, result }),\n  };\n};\n",[113,1167,1168,1173,1202,1231,1237,1242,1263,1267,1274,1286,1319,1324],{"__ignoreMap":111},[116,1169,1170],{"class":118,"line":119},[116,1171,1172],{"class":122},"// A simple Lambda handler\n",[116,1174,1175,1178,1180,1183,1185,1188,1191,1194,1197,1200],{"class":118,"line":126},[116,1176,1177],{"class":380},"exports",[116,1179,1117],{"class":370},[116,1181,1182],{"class":129},"handler",[116,1184,1021],{"class":370},[116,1186,1187],{"class":1008}," async",[116,1189,1190],{"class":370}," (",[116,1192,1193],{"class":1014},"event",[116,1195,1196],{"class":370},")",[116,1198,1199],{"class":370}," =>",[116,1201,390],{"class":370},[116,1203,1204,1207,1210,1212,1215,1217,1220,1222,1224,1226,1229],{"class":118,"line":147},[116,1205,1206],{"class":1008},"  const",[116,1208,1209],{"class":1014}," body",[116,1211,1021],{"class":370},[116,1213,1214],{"class":1014}," JSON",[116,1216,1117],{"class":370},[116,1218,1219],{"class":129},"parse",[116,1221,1027],{"class":370},[116,1223,1193],{"class":1014},[116,1225,1117],{"class":370},[116,1227,1228],{"class":1014},"body",[116,1230,1038],{"class":370},[116,1232,1233],{"class":118,"line":154},[116,1234,1236],{"class":1235},"sNpkn","  \n",[116,1238,1239],{"class":118,"line":160},[116,1240,1241],{"class":122},"  // Do your thing\n",[116,1243,1244,1246,1249,1251,1254,1257,1259,1261],{"class":118,"line":169},[116,1245,1206],{"class":1008},[116,1247,1248],{"class":1014}," result",[116,1250,1021],{"class":370},[116,1252,1253],{"class":581}," await",[116,1255,1256],{"class":129}," processWebhook",[116,1258,1027],{"class":370},[116,1260,1228],{"class":1014},[116,1262,1038],{"class":370},[116,1264,1265],{"class":118,"line":258},[116,1266,1236],{"class":1235},[116,1268,1269,1272],{"class":118,"line":267},[116,1270,1271],{"class":581},"  return",[116,1273,390],{"class":370},[116,1275,1276,1279,1281,1284],{"class":118,"line":272},[116,1277,1278],{"class":380},"    statusCode",[116,1280,387],{"class":370},[116,1282,1283],{"class":716}," 200",[116,1285,429],{"class":370},[116,1287,1288,1291,1293,1295,1297,1300,1303,1306,1308,1311,1314,1316],{"class":118,"line":278},[116,1289,1290],{"class":380},"    body",[116,1292,387],{"class":370},[116,1294,1214],{"class":1014},[116,1296,1117],{"class":370},[116,1298,1299],{"class":129},"stringify",[116,1301,1302],{"class":370},"({",[116,1304,1305],{"class":380}," success",[116,1307,387],{"class":370},[116,1309,1310],{"class":581}," true",[116,1312,1313],{"class":370},",",[116,1315,1248],{"class":1014},[116,1317,1318],{"class":370}," }),\n",[116,1320,1321],{"class":118,"line":289},[116,1322,1323],{"class":370},"  };\n",[116,1325,1326],{"class":118,"line":543},[116,1327,1328],{"class":370},"};\n",[69,1330,1331],{},"Deploy it:",[106,1333,1335],{"className":108,"code":1334,"language":110,"meta":111,"style":111},"# Zip your function\nzip -r function.zip index.js node_modules/\n\n# Create the Lambda function\naws lambda create-function \\\n  --function-name my-webhook-handler \\\n  --runtime nodejs18.x \\\n  --handler index.handler \\\n  --role arn:aws:iam::YOUR_ACCOUNT:role/lambda-execution-role \\\n  --zip-file fileb://function.zip\n",[113,1336,1337,1342,1359,1363,1368,1380,1390,1400,1410,1420],{"__ignoreMap":111},[116,1338,1339],{"class":118,"line":119},[116,1340,1341],{"class":122},"# Zip your function\n",[116,1343,1344,1347,1350,1353,1356],{"class":118,"line":126},[116,1345,1346],{"class":129},"zip",[116,1348,1349],{"class":137}," -r",[116,1351,1352],{"class":133}," function.zip",[116,1354,1355],{"class":133}," index.js",[116,1357,1358],{"class":133}," node_modules/\n",[116,1360,1361],{"class":118,"line":147},[116,1362,151],{"emptyLinePlaceholder":150},[116,1364,1365],{"class":118,"line":154},[116,1366,1367],{"class":122},"# Create the Lambda function\n",[116,1369,1370,1372,1375,1378],{"class":118,"line":160},[116,1371,163],{"class":129},[116,1373,1374],{"class":133}," lambda",[116,1376,1377],{"class":133}," create-function",[116,1379,245],{"class":137},[116,1381,1382,1385,1388],{"class":118,"line":169},[116,1383,1384],{"class":137},"  --function-name",[116,1386,1387],{"class":133}," my-webhook-handler",[116,1389,245],{"class":137},[116,1391,1392,1395,1398],{"class":118,"line":258},[116,1393,1394],{"class":137},"  --runtime",[116,1396,1397],{"class":133}," nodejs18.x",[116,1399,245],{"class":137},[116,1401,1402,1405,1408],{"class":118,"line":267},[116,1403,1404],{"class":137},"  --handler",[116,1406,1407],{"class":133}," index.handler",[116,1409,245],{"class":137},[116,1411,1412,1415,1418],{"class":118,"line":272},[116,1413,1414],{"class":137},"  --role",[116,1416,1417],{"class":133}," arn:aws:iam::YOUR_ACCOUNT:role/lambda-execution-role",[116,1419,245],{"class":137},[116,1421,1422,1425],{"class":118,"line":278},[116,1423,1424],{"class":137},"  --zip-file",[116,1426,1427],{"class":133}," fileb://function.zip\n",[69,1429,1430],{},"Pair it with API Gateway to expose it over HTTP, or trigger it from S3 events, SQS messages, or a schedule. Lambda is the piece that makes \"serverless\" actually work in practice.",[79,1432,1434],{"id":1433},"how-it-all-fits-together","How It All Fits Together",[69,1436,1437],{},"Here's the architecture I reach for on most web projects:",[106,1439,1444],{"className":1440,"code":1442,"language":1443},[1441],"language-text","Users → CloudFront → S3 (frontend)\n                  ↘\n                   API Gateway → Lambda (lightweight endpoints)\n                              ↘\n                   EC2 (Node/Python API) → RDS (PostgreSQL)\n","text",[113,1445,1442],{"__ignoreMap":111},[69,1447,1448],{},"The frontend is static, served fast from S3/CloudFront. The API sits on EC2 or Lambda depending on whether it needs to run constantly. The database lives in RDS, not accessible from the outside world.",[69,1450,1451],{},"You don't need all of this on day one. Start with S3 + CloudFront for a static frontend, add EC2 + RDS when you have a backend, add Lambda when you have workloads that don't need a persistent server. Build what you need, add pieces as you go.",[79,1453,1455],{"id":1454},"the-one-thing-that-will-save-you-later","The One Thing That Will Save You Later",[69,1457,1458],{},"Use environment-specific configurations from the start. Separate S3 buckets, separate RDS instances, separate everything for dev and prod. It's slightly more work now and will save you enormously from \"I accidentally ran a migration against production\" situations.",[106,1460,1462],{"className":108,"code":1461,"language":110,"meta":111,"style":111},"# Deploy to staging\naws s3 sync ./dist s3://my-app-staging --delete\n\n# Deploy to production (separate command, requires confirmation in your deploy script)\naws s3 sync ./dist s3://my-app-prod --delete\n",[113,1463,1464,1469,1484,1488,1493],{"__ignoreMap":111},[116,1465,1466],{"class":118,"line":119},[116,1467,1468],{"class":122},"# Deploy to staging\n",[116,1470,1471,1473,1475,1477,1479,1482],{"class":118,"line":126},[116,1472,163],{"class":129},[116,1474,210],{"class":133},[116,1476,296],{"class":133},[116,1478,299],{"class":133},[116,1480,1481],{"class":133}," s3://my-app-staging",[116,1483,304],{"class":137},[116,1485,1486],{"class":118,"line":147},[116,1487,151],{"emptyLinePlaceholder":150},[116,1489,1490],{"class":118,"line":154},[116,1491,1492],{"class":122},"# Deploy to production (separate command, requires confirmation in your deploy script)\n",[116,1494,1495,1497,1499,1501,1503,1506],{"class":118,"line":160},[116,1496,163],{"class":129},[116,1498,210],{"class":133},[116,1500,296],{"class":133},[116,1502,299],{"class":133},[116,1504,1505],{"class":133}," s3://my-app-prod",[116,1507,304],{"class":137},[69,1509,1510],{},"That friction is intentional. Make production deploys feel slightly different from staging deploys, and you'll make fewer mistakes.",[79,1512,1514],{"id":1513},"go-deploy-something","Go Deploy Something",[69,1516,1517],{},"Pick the smallest possible thing you have and deploy it. A static landing page to S3, a simple Express API to EC2, anything. The first deployment teaches you more than all the documentation combined.",[69,1519,1520],{},"AWS has a learning curve — the console is overwhelming and the IAM permissions will confuse you at least once. But the underlying model is consistent: create a resource, configure it, connect it to other resources, deploy your code. Once that clicks, the rest is just learning which service does what.",[69,1522,1523],{},"You've got this. Set that billing alert first, then go build something. 🎉",[1525,1526],"hr",{},[69,1528,1529],{},[98,1530,1531],{},"Useful links:",[1533,1534,1535,1544,1552,1560],"ul",{},[1536,1537,1538,1543],"li",{},[87,1539,1542],{"href":1540,"rel":1541},"https://aws.amazon.com/free/",[91],"AWS Free Tier"," — what's included and for how long",[1536,1545,1546,1551],{},[87,1547,1550],{"href":1548,"rel":1549},"https://docs.aws.amazon.com/cli/latest/reference/",[91],"AWS CLI Reference"," — every command documented",[1536,1553,1554,1559],{},[87,1555,1558],{"href":1556,"rel":1557},"https://aws.amazon.com/architecture/well-architected/",[91],"AWS Well-Architected Framework"," — when you're ready to think about production hardening",[1536,1561,1562,1567],{},[87,1563,1566],{"href":1564,"rel":1565},"https://cloudping.info/",[91],"CloudPing"," — find the fastest AWS region for your users",[1569,1570,1571],"style",{},"html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .sXjYR, html code.shiki .sXjYR{--shiki-default:#C99076}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .s6USN, html code.shiki .s6USN{--shiki-default:#B8A96577}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .sxA9i, html code.shiki .sxA9i{--shiki-default:#4C9A91}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}",{"title":111,"searchDepth":126,"depth":126,"links":1573},[1574,1575,1576,1577,1578,1579,1580,1581,1582],{"id":81,"depth":126,"text":82},{"id":186,"depth":126,"text":187},{"id":321,"depth":126,"text":322},{"id":642,"depth":126,"text":643},{"id":896,"depth":126,"text":897},{"id":1158,"depth":126,"text":1159},{"id":1433,"depth":126,"text":1434},{"id":1454,"depth":126,"text":1455},{"id":1513,"depth":126,"text":1514},"2025-12-20T00:00:00.000Z","A practical, no-fluff guide to deploying real web apps on AWS — from S3 static hosting to EC2, RDS, and beyond. Learn the services that actually matter and how they fit together.","md","https://res.cloudinary.com/nbrett/image/upload/v1772967027/aws_cloud_hero_xpq1gp.svg",{},{"text":1589,"minutes":1590,"time":1591,"words":1592},"8 min read",7.085,425100,1417,{"title":46,"description":1584},null,"KU5KCmOI0UdiXEkNdBpOYDd0FSrQC83ifT7OAB55DZo",[1597,1594],{"title":42,"path":43,"stem":44,"description":1598,"children":-1},"Learn how to make clean, readable HTTP requests in JavaScript using the Fetch API and async/await. From basic GET requests to error handling, authentication, and real-world patterns.",1774167727267]