<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kamlesh Cloud & DevOps]]></title><description><![CDATA[I’m documenting my journey into Cloud and DevOps by building real projects from scratch and sharing everything I learn along the way.
This blog is focused on ha]]></description><link>https://blog.kamleshcloud.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Kamlesh Cloud &amp; DevOps</title><link>https://blog.kamleshcloud.com</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 14:23:18 GMT</lastBuildDate><atom:link href="https://blog.kamleshcloud.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Production-Style Serverless Order Management System on AWS
]]></title><description><![CDATA[When I started this project, my goal was not just to build another basic CRUD API.
I wanted to understand how a real cloud backend is designed when we care about scalability, security, monitoring, cos]]></description><link>https://blog.kamleshcloud.com/building-a-production-style-serverless-order-management-system-on-aws</link><guid isPermaLink="true">https://blog.kamleshcloud.com/building-a-production-style-serverless-order-management-system-on-aws</guid><dc:creator><![CDATA[Kamlesh Dubale]]></dc:creator><pubDate>Fri, 12 Jun 2026 16:33:50 GMT</pubDate><content:encoded><![CDATA[<p>When I started this project, my goal was not just to build another basic CRUD API.</p>
<p>I wanted to understand how a real cloud backend is designed when we care about scalability, security, monitoring, cost, and maintainability.</p>
<p>So I built a <strong>Serverless Order Management System on AWS</strong> using:</p>
<ul>
<li><p>Amazon API Gateway</p>
</li>
<li><p>AWS Lambda</p>
</li>
<li><p>Amazon DynamoDB</p>
</li>
<li><p>Amazon Cognito</p>
</li>
<li><p>IAM</p>
</li>
<li><p>Amazon CloudWatch</p>
</li>
<li><p>Amazon SNS</p>
</li>
<li><p>API Gateway throttling</p>
</li>
<li><p>Postman for API testing</p>
</li>
</ul>
<p>GitHub Repository: <a href="https://github.com/KamleshDevopsAndCloud/project-3-serverless-order-management-.git">Serverless Order Management System</a><br />Portfolio: <a href="http://KamleshCloud.com">kamleshcloud.com</a><br />LinkedIn: <a href="https://www.linkedin.com/in/kamlesh-dubale">Kamlesh Dubale</a></p>
<h2>What I Built</h2>
<p>This project is a serverless backend for managing orders.</p>
<p>It supports:</p>
<ul>
<li><p>Creating an order</p>
</li>
<li><p>Getting all orders</p>
</li>
<li><p>Getting a specific order by ID</p>
</li>
<li><p>Deleting an order</p>
</li>
<li><p>Protecting API routes using Cognito authentication</p>
</li>
<li><p>Monitoring Lambda errors with CloudWatch</p>
</li>
<li><p>Sending alerts using SNS</p>
</li>
<li><p>Restricting Lambda permissions using least-privilege IAM</p>
</li>
<li><p>Protecting the API from excessive traffic using throttling</p>
</li>
</ul>
<p>The final architecture is fully serverless, meaning there are no EC2 instances or servers to manage.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/64276c1c-84cd-4cc4-9a85-645fc7f11ad4.png" alt="" style="display:block;margin:0 auto" />

<h2>High-Level Architecture</h2>
<p>The request flow looks like this:</p>
<pre><code class="language-plaintext">User / Postman
    ↓
Amazon Cognito
    ↓
Amazon API Gateway
    ↓
AWS Lambda
    ↓
Amazon DynamoDB
    ↓
CloudWatch Logs / Alarms
    ↓
SNS Email Alerts
</code></pre>
<p>API Gateway acts as the public entry point.</p>
<p>Lambda handles the backend logic.</p>
<p>DynamoDB stores the order records.</p>
<p>Cognito adds authentication.</p>
<p>CloudWatch and SNS provide monitoring and alerting.</p>
<p>IAM controls what Lambda is allowed to access.</p>
<p>API throttling protects the system from excessive traffic.</p>
<h2>Why I Chose Serverless</h2>
<p>Traditional backend systems usually require servers.</p>
<p>That means someone has to think about:</p>
<ul>
<li><p>Operating system updates</p>
</li>
<li><p>Server patching</p>
</li>
<li><p>Scaling servers</p>
</li>
<li><p>Load balancing</p>
</li>
<li><p>Capacity planning</p>
</li>
<li><p>Availability</p>
</li>
<li><p>Runtime maintenance</p>
</li>
</ul>
<p>With serverless, AWS handles most of that.</p>
<p>Lambda only runs when a request comes in. DynamoDB scales automatically. API Gateway gives a managed HTTPS endpoint. Cognito manages authentication. CloudWatch collects logs and metrics.</p>
<p>This makes serverless a very good fit for event-driven APIs, small backend services, prototypes, internal tools, and scalable cloud-native applications.</p>
<h2>API Gateway Routes</h2>
<p>I created four API routes:</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Route</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td>POST</td>
<td><code>/orders</code></td>
<td>Create a new order</td>
</tr>
<tr>
<td>GET</td>
<td><code>/orders</code></td>
<td>List all orders</td>
</tr>
<tr>
<td>GET</td>
<td><code>/orders/{id}</code></td>
<td>Get one order by ID</td>
</tr>
<tr>
<td>DELETE</td>
<td><code>/orders/{id}</code></td>
<td>Delete one order</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/74613131-4304-4782-9ba8-16dd7ea34951.png" alt="" style="display:block;margin:0 auto" />

<p>This route structure follows a simple REST-style API design.</p>
<p>For example:</p>
<pre><code class="language-plaintext">POST /orders
</code></pre>
<p>means create an order.</p>
<pre><code class="language-plaintext">GET /orders
</code></pre>
<p>means list all orders.</p>
<pre><code class="language-plaintext">GET /orders/{id}
</code></pre>
<p>means retrieve one specific order.</p>
<pre><code class="language-plaintext">DELETE /orders/{id}
</code></pre>
<p>means delete one specific order.</p>
<h2>Lambda Functions</h2>
<p>Instead of using one large Lambda function for everything, I created separate Lambda functions:</p>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p>Lambda Function</p></td><td><p>Responsibility</p></td></tr><tr><td><p>CreateOrderFunction</p></td><td><p>Creates a new order</p></td></tr><tr><td><p>GetOrderFunction</p></td><td><p>Gets one order</p></td></tr><tr><td><p>ListOrdersFunction</p></td><td><p>Lists all orders</p></td></tr><tr><td><p>DeleteOrderFunction</p></td><td><p>Deletes an order</p></td></tr></tbody></table>

<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/b0fb2252-f6a8-4c6a-a2e2-953bb0dc6b96.png" alt="" style="display:block;margin:0 auto" />

<p>This follows the single responsibility principle.</p>
<p>Each function has one clear job.</p>
<p>This makes the system easier to debug, easier to secure, and easier to explain.</p>
<h2>DynamoDB Table Design</h2>
<p>The DynamoDB table is called:</p>
<pre><code class="language-plaintext">orders
</code></pre>
<p>The partition key is:</p>
<pre><code class="language-plaintext">OrderId
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/da6b9dfa-bedf-4700-a979-22edd9db18b2.png" alt="" style="display:block;margin:0 auto" />

<p>Each order item contains:</p>
<pre><code class="language-plaintext">{
  "OrderId": "994dd3bc-a43a-4342-b07b-3f5829e3c046",
  "customerName": "Kamlesh",
  "product": "MacBook Pro",
  "quantity": 1
}
</code></pre>
<p>I used <code>OrderId</code> as the partition key because every order needs a unique identifier.</p>
<p>This allows DynamoDB to retrieve a specific order efficiently using <code>GetItem</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/a465e06a-fc0a-4992-b91c-f661e3d5c8ae.png" alt="" style="display:block;margin:0 auto" />

<h2>Create Order Flow</h2>
<p>When a client sends this request:</p>
<pre><code class="language-plaintext">POST /orders
</code></pre>
<p>with this JSON body:</p>
<pre><code class="language-plaintext">{
  "customerName": "Kamlesh",
  "product": "MacBook Pro",
  "quantity": 1
}
</code></pre>
<p>the flow is:</p>
<pre><code class="language-plaintext">Postman
    ↓
API Gateway
    ↓
CreateOrderFunction
    ↓
DynamoDB PutItem
    ↓
Order saved
</code></pre>
<p>The Lambda function generates a unique <code>OrderId</code> using Python’s <code>uuid</code> module.</p>
<p>Then it stores the item in DynamoDB using <code>boto3</code>.</p>
<h2>Input Validation</h2>
<p>A backend API should not blindly accept bad input.</p>
<p>So I added validation for required fields:</p>
<pre><code class="language-plaintext">customerName
product
quantity
</code></pre>
<p>If the request body is missing required fields, the API returns a proper error response instead of crashing.</p>
<p>Example bad request:</p>
<pre><code class="language-plaintext">{
  "customerName": "Kamlesh"
}
</code></pre>
<p>Response:</p>
<pre><code class="language-plaintext">{
  "message": "Missing required fields",
  "missingFields": ["product", "quantity"],
  "requiredFields": ["customerName", "product", "quantity"]
}
</code></pre>
<p>This is important because production APIs should give clear and useful error messages.</p>
<h2>Get All Orders</h2>
<p>The <code>GET /orders</code> endpoint lists all orders from DynamoDB.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/dd577501-b86a-4cbc-a40c-f7c877459e6e.png" alt="" style="display:block;margin:0 auto" />

<p>The response looked like this:</p>
<pre><code class="language-plaintext">[
  {
    "product": "MacBook Pro",
    "quantity": 1,
    "customerName": "Kamlesh",
    "OrderId": "994dd3bc-a43a-4342-b07b-3f5829e3c046"
  }
]
</code></pre>
<p>For this project, I used DynamoDB <code>Scan</code>.</p>
<p>In a real high-scale production system, I would avoid unnecessary scans and design access patterns carefully using partition keys, sort keys, and indexes.</p>
<h2>Get Order by ID</h2>
<p>The <code>GET /orders/{id}</code> endpoint uses a path parameter.</p>
<p>Example:</p>
<pre><code class="language-plaintext">GET /orders/994dd3bc-a43a-4342-b07b-3f5829e3c046
</code></pre>
<p>API Gateway passes the ID to Lambda inside:</p>
<pre><code class="language-plaintext">event["pathParameters"]["id"]
</code></pre>
<p>Lambda then uses DynamoDB <code>GetItem</code> to retrieve the order.</p>
<p>This helped me understand how API Gateway converts HTTP requests into Lambda event objects.</p>
<h2>Delete Order</h2>
<p>The <code>DELETE /orders/{id}</code> endpoint deletes an order from DynamoDB.</p>
<p>The flow is:</p>
<pre><code class="language-plaintext">Client
    ↓
API Gateway
    ↓
DeleteOrderFunction
    ↓
DynamoDB DeleteItem
    ↓
Deletion confirmation
</code></pre>
<p>This completed the main CRUD functionality of the project.</p>
<h2>Cognito Authentication</h2>
<p>After the CRUD APIs were working, I added Amazon Cognito.</p>
<p>The goal was to protect sensitive API routes.</p>
<p>Without authentication, anyone who knows the API URL can send requests.</p>
<p>That is not acceptable for a real backend.</p>
<p>So I created:</p>
<ul>
<li><p>Cognito User Pool</p>
</li>
<li><p>App Client</p>
</li>
<li><p>Cognito Hosted Login Page</p>
</li>
<li><p>JWT Authorizer in API Gateway</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/a7048275-4505-4a69-9b69-e8fcf13be95a.png" alt="" style="display:block;margin:0 auto" />

<p>When an unauthenticated request is sent to the protected <code>POST /orders</code> route, the API returns:</p>
<pre><code class="language-plaintext">{
  "message": "Unauthorized"
}
</code></pre>
<p>This proves that API Gateway is rejecting anonymous requests before they reach Lambda.</p>
<p>That is a strong security boundary.</p>
<h2>IAM Least Privilege</h2>
<p>During development, I temporarily used broader DynamoDB permissions to move quickly.</p>
<p>But after the project was working, I replaced full DynamoDB access with a custom least-privilege policy.</p>
<p>The Lambda execution role was allowed only these actions:</p>
<pre><code class="language-plaintext">dynamodb:PutItem
dynamodb:GetItem
dynamodb:DeleteItem
dynamodb:Scan
</code></pre>
<p>and only on the <code>orders</code> table.</p>
<p>This is very important.</p>
<p>A Lambda function that only needs to access one table should not have permission to modify every DynamoDB table in the account.</p>
<p>This follows the principle of least privilege.</p>
<h2>CloudWatch Monitoring</h2>
<p>A system is not production-ready if we cannot observe it.</p>
<p>So I used CloudWatch Logs to inspect Lambda executions and debug issues.</p>
<p>I also created a CloudWatch alarm for Lambda errors.</p>
<p>Alarm name:</p>
<pre><code class="language-plaintext">CreateOrderFunction-Errors-Alarm
</code></pre>
<p>The alarm checks the Lambda <code>Errors</code> metric.</p>
<p>If the function records one or more errors within a one-minute period, the alarm can trigger an action.</p>
<hr />
<h2>SNS Alerting</h2>
<p>To make the monitoring useful, I connected the CloudWatch alarm to Amazon SNS.</p>
<p>The alerting flow is:</p>
<pre><code class="language-plaintext">Lambda error
    ↓
CloudWatch metric
    ↓
CloudWatch alarm
    ↓
SNS topic
    ↓
Email notification
</code></pre>
<p>This is closer to how real DevOps teams monitor production systems.</p>
<p>It is not enough to only log errors. Someone needs to be notified when something breaks.</p>
<h2>API Throttling</h2>
<p>I also configured API Gateway throttling.</p>
<p>Settings used:</p>
<pre><code class="language-plaintext">Burst limit: 50
Rate limit: 100
</code></pre>
<p>Throttling protects the API from:</p>
<ul>
<li><p>accidental request loops</p>
</li>
<li><p>basic abuse</p>
</li>
<li><p>sudden spikes</p>
</li>
<li><p>uncontrolled client traffic</p>
</li>
</ul>
<p>This is an important production control.</p>
<p>Even if the backend is serverless and scalable, we should still control how much traffic can hit the API.</p>
<p>Problems I Faced and Fixed</p>
<p>This project was not smooth from start to finish. I ran into multiple real-world issues.</p>
<ol>
<li>DynamoDB table name mismatch</li>
</ol>
<p>My code used:</p>
<p>Orders</p>
<p>but the actual table name was:</p>
<p>orders</p>
<p>DynamoDB table names are case-sensitive.</p>
<p>This caused a ResourceNotFoundException.</p>
<ol>
<li>Primary key mismatch</li>
</ol>
<p>The DynamoDB partition key was:</p>
<p>OrderId</p>
<p>but my first Lambda code used:</p>
<p>orderId</p>
<p>That caused a validation error because DynamoDB expected the exact key name.</p>
<ol>
<li>Decimal serialization issue</li>
</ol>
<p>DynamoDB returns numbers as Decimal objects in Python.</p>
<p>When returning an item through API Gateway, Python failed with:</p>
<p>Object of type Decimal is not JSON serializable</p>
<p>I fixed this by adding a custom serializer to convert Decimal values before returning the JSON response.</p>
<ol>
<li>Cognito Unauthorized response</li>
</ol>
<p>After attaching the Cognito authorizer, my POST /orders request started returning:</p>
<p>{ "message": "Unauthorized" }</p>
<p>At first, this looked like an error.</p>
<p>But it was actually the correct behavior because the route was protected and no JWT token was being sent.</p>
<p>That helped me understand authentication at the API Gateway layer.</p>
<hr />
<h2>Why This Project Matters</h2>
<p>This project helped me move beyond simply deploying a Lambda function.</p>
<p>I learned how multiple AWS services work together:</p>
<pre><code class="language-plaintext">Authentication
    ↓
API routing
    ↓
Business logic
    ↓
Database
    ↓
Monitoring
    ↓
Alerting
    ↓
Security controls
</code></pre>
<p>This is the difference between building a demo and designing a real cloud backend.</p>
<hr />
<h2>Final Repository Structure</h2>
<pre><code class="language-plaintext">serverless-order-management-system/
│
├── README.md
├── architecture-diagram.png
│
├── lambda-functions/
│   ├── create_order.py
│   ├── get_order.py
│   ├── list_orders.py
│   └── delete_order.py
│
├── screenshots/
│   ├── API Gateway Routes.png
│   ├── API Throttling.png
│   ├── Architecture diagram.png
│   ├── Cloudwatch Alarm.png
│   ├── Cognito Authorizer.png
│   ├── DynamoDB- Explore Items.png
│   ├── DynamoDB order Items overview.png
│   ├── IAM least privilage role.png
│   ├── Lambda Functions.png
│   ├── POSTMAN - Get all orders.png
│   └── POSTMAN - Unauthorized.png
│
└── docs/
    └── blog-draft.md
</code></pre>
<h2>What I Would Improve Next</h2>
<p>If I continue improving this project, I would add:</p>
<ul>
<li><p>Terraform for Infrastructure as Code</p>
</li>
<li><p>GitHub Actions CI/CD pipeline</p>
</li>
<li><p>Separate dev and prod environments</p>
</li>
<li><p>Custom domain for API Gateway</p>
</li>
<li><p>AWS WAF</p>
</li>
<li><p>Full JWT token testing in Postman</p>
</li>
<li><p>More detailed CloudWatch dashboards</p>
</li>
<li><p>Unit tests for Lambda functions</p>
</li>
<li><p>Better DynamoDB access patterns for larger workloads</p>
</li>
</ul>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/dc89a149-a4e7-4a88-ac4a-ec431ca2fd2b.png" alt="" style="display:block;margin:0 auto" />

<h2>Final Thoughts</h2>
<p>This project gave me practical experience with building a secure, monitored, serverless backend on AWS.</p>
<p>The biggest learning was not just writing Lambda code.</p>
<p>The biggest learning was understanding how cloud components fit together:</p>
<ul>
<li><p>API Gateway exposes the backend</p>
</li>
<li><p>Cognito protects the API</p>
</li>
<li><p>Lambda runs the business logic</p>
</li>
<li><p>DynamoDB stores the data</p>
</li>
<li><p>IAM controls permissions</p>
</li>
<li><p>CloudWatch observes the system</p>
</li>
<li><p>SNS sends alerts</p>
</li>
<li><p>Throttling protects the API</p>
</li>
</ul>
<p>This is the kind of architecture thinking I want to keep building as I continue growing as a Cloud and DevOps Engineer.</p>
<p>GitHub Repository: <a href="https://github.com/KamleshDevopsAndCloud/project-3-serverless-order-management-.git">Serverless Order Management System</a><br />Portfolio: <a href="http://KamleshCloud.com">kamleshcloud.com</a><br />LinkedIn: <a href="https://www.linkedin.com/in/kamlesh-dubale">Kamlesh Dubale</a></p>
]]></content:encoded></item><item><title><![CDATA[Building a Production-Style Three-Tier Architecture on AWS]]></title><description><![CDATA[As I continue learning cloud engineering and DevOps, I wanted to move beyond static website hosting and start understanding how real-world applications are architected inside cloud environments.
Most ]]></description><link>https://blog.kamleshcloud.com/building-a-production-style-three-tier-architecture-on-aws</link><guid isPermaLink="true">https://blog.kamleshcloud.com/building-a-production-style-three-tier-architecture-on-aws</guid><dc:creator><![CDATA[Kamlesh Dubale]]></dc:creator><pubDate>Thu, 07 May 2026 11:27:57 GMT</pubDate><content:encoded><![CDATA[<p>As I continue learning cloud engineering and DevOps, I wanted to move beyond static website hosting and start understanding how real-world applications are architected inside cloud environments.</p>
<p>Most production applications are not hosted on a single server directly exposed to the internet. Instead, they follow layered architectures focused on:</p>
<ul>
<li><p>security</p>
</li>
<li><p>scalability</p>
</li>
<li><p>controlled communication</p>
</li>
<li><p>network isolation</p>
</li>
<li><p>traffic management</p>
</li>
</ul>
<p>To better understand these concepts, I built a production-style three-tier architecture on AWS using:</p>
<ul>
<li><p>Amazon VPC</p>
</li>
<li><p>Public and Private Subnets</p>
</li>
<li><p>EC2</p>
</li>
<li><p>PostgreSQL RDS</p>
</li>
<li><p>Application Load Balancer (ALB)</p>
</li>
<li><p>Security Groups</p>
</li>
<li><p>Target Groups and Health Checks</p>
</li>
</ul>
<p>This project helped me understand not only how AWS services work individually, but more importantly how they communicate with each other inside a real cloud environment.</p>
<h1>What is a Three-Tier Architecture?</h1>
<p>A three-tier architecture separates infrastructure into three logical layers:</p>
<pre><code class="language-text">Presentation Layer → Application Layer → Database Layer
</code></pre>
<p>In this project:</p>
<ul>
<li><p>The <strong>Application Load Balancer (ALB)</strong> acted as the presentation layer</p>
</li>
<li><p>The <strong>EC2 instance</strong> acted as the application layer</p>
</li>
<li><p>The <strong>PostgreSQL RDS database</strong> acted as the database layer</p>
</li>
</ul>
<p>This architecture pattern is commonly used because it improves:</p>
<ul>
<li><p>scalability</p>
</li>
<li><p>security</p>
</li>
<li><p>maintainability</p>
</li>
<li><p>reliability</p>
</li>
</ul>
<h1>Final Architecture</h1>
<pre><code class="language-text">Internet
    ↓
Application Load Balancer
    ↓
EC2 Instance
    ↓
PostgreSQL RDS
</code></pre>
<p>The overall request flow works like this:</p>
<ol>
<li><p>A user opens the application URL</p>
</li>
<li><p>The request reaches the Application Load Balancer</p>
</li>
<li><p>The Load Balancer forwards traffic to the Target Group</p>
</li>
<li><p>The Target Group routes traffic to a healthy EC2 instance</p>
</li>
<li><p>The EC2 instance processes the request</p>
</li>
<li><p>The application can communicate with PostgreSQL RDS if required</p>
</li>
<li><p>The response is returned back to the user</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/d05911c2-dde9-4936-8342-d1b3810e1380.png" alt="" style="display:block;margin:0 auto" />

<h1>Designing the Network Architecture</h1>
<p>The first step was creating a custom VPC to isolate the infrastructure inside a private AWS network.</p>
<p>Inside the VPC, I created multiple subnets to separate internet-facing resources from protected backend services.</p>
<h2>Public Subnets</h2>
<p>The public subnet hosted:</p>
<ul>
<li><p>Application Load Balancer</p>
</li>
<li><p>EC2 Instance</p>
</li>
</ul>
<p>These subnets had internet access configured using:</p>
<pre><code class="language-text">0.0.0.0/0 → Internet Gateway
</code></pre>
<h2>Private Subnets</h2>
<p>The private subnet was used for PostgreSQL RDS.</p>
<p>The idea behind this design was simple:</p>
<ul>
<li><p>Users should access the application</p>
</li>
<li><p>Users should never directly access the database</p>
</li>
</ul>
<p>This separation is one of the most important concepts in cloud architecture.</p>
<h1>Configuring Security Groups</h1>
<p>One of the biggest learning experiences from this project was understanding how communication between services is controlled using Security Groups.</p>
<p>I configured three different Security Groups:</p>
<h2>ALB Security Group</h2>
<p>Allowed HTTP traffic from the internet.</p>
<pre><code class="language-text">HTTP 80 → 0.0.0.0/0
</code></pre>
<h2>EC2 Security Group</h2>
<p>Allowed:</p>
<ul>
<li><p>SSH access only from my public IP</p>
</li>
<li><p>HTTP traffic only from the ALB Security Group</p>
</li>
</ul>
<pre><code class="language-text">SSH 22 → My IP
HTTP 80 → ALB Security Group
</code></pre>
<h2>RDS Security Group</h2>
<p>Allowed PostgreSQL access only from the EC2 Security Group.</p>
<pre><code class="language-text">PostgreSQL 5432 → EC2 Security Group
</code></pre>
<p>This created a controlled communication chain:</p>
<pre><code class="language-text">Internet → ALB → EC2 → RDS
</code></pre>
<p>Understanding this traffic flow was one of the most valuable parts of the project.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/61838977-4b13-4cdc-a891-875ae5685eef.png" alt="" style="display:block;margin:0 auto" />

<h1>Application Load Balancer and Target Groups</h1>
<p>An internet-facing Application Load Balancer (ALB) was created to distribute incoming traffic.</p>
<p>Instead of exposing the EC2 instance directly to users, the ALB acted as the public entry point.</p>
<p>The ALB forwarded traffic to a Target Group containing the EC2 instance.</p>
<p>One concept that became much clearer during this project was the purpose of health checks.</p>
<p>The Target Group continuously checks whether the EC2 instance is healthy and capable of handling traffic.</p>
<p>If the server becomes unhealthy, traffic stops being forwarded automatically.</p>
<p>This is an extremely important reliability feature used in production systems.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/bd328da1-0cb3-411f-98c1-b6dff4c61bbf.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/d3b11ad2-52cd-4b70-9928-20f79f62e5d0.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/b1787e12-8e69-4df0-b7e8-1668e0914a1b.png" alt="" style="display:block;margin:0 auto" />

<h1>Challenges Faced During Implementation</h1>
<p>This project involved a significant amount of troubleshooting and debugging.</p>
<p>Some of the biggest challenges included:</p>
<ul>
<li><p>unhealthy target groups</p>
</li>
<li><p>Security Group configuration mistakes</p>
</li>
<li><p>region mismatch confusion</p>
</li>
<li><p>identifying public vs private subnets</p>
</li>
<li><p>ALB communication issues</p>
</li>
</ul>
<p>One major issue occurred when the Target Group continuously showed the EC2 instance as unhealthy.</p>
<p>After debugging the networking flow carefully, I discovered that the EC2 Security Group was not correctly allowing HTTP traffic from the ALB Security Group.</p>
<p>Fixing this helped me better understand how traffic actually moves through AWS infrastructure.</p>
<p>Another important learning was understanding that subnets become public or private based on Route Table configuration rather than subnet names themselves.</p>
<h1>Key Learnings</h1>
<p>This project helped me gain hands-on understanding of:</p>
<ul>
<li><p>AWS networking</p>
</li>
<li><p>VPC design</p>
</li>
<li><p>Security Groups</p>
</li>
<li><p>Load Balancers</p>
</li>
<li><p>Target Groups</p>
</li>
<li><p>Health Checks</p>
</li>
<li><p>Public vs Private networking</p>
</li>
<li><p>Cloud troubleshooting</p>
</li>
</ul>
<p>One of the biggest takeaways from this project was realising that cloud engineering is heavily focused on:</p>
<ul>
<li><p>networking</p>
</li>
<li><p>communication flow</p>
</li>
<li><p>security</p>
</li>
<li><p>debugging</p>
</li>
</ul>
<p>rather than simply deploying services.</p>
<h1>Conclusion</h1>
<p>This project helped bridge the gap between theoretical AWS knowledge and practical cloud architecture implementation.</p>
<p>Rather than simply launching services, the project focused heavily on understanding:</p>
<ul>
<li><p>secure communication between services</p>
</li>
<li><p>traffic flow</p>
</li>
<li><p>network isolation</p>
</li>
<li><p>cloud troubleshooting</p>
</li>
<li><p>production-style architecture design</p>
</li>
</ul>
<p>The debugging process throughout the project was especially valuable because it provided insight into how real infrastructure problems are identified and resolved in cloud environments.</p>
<p>This project also gave me much more confidence in understanding how different AWS services work together as part of a complete system.</p>
<h2>Connect With Me</h2>
<ul>
<li><p>GitHub: <a href="https://github.com/KamleshDevopsAndCloud">https://github.com/KamleshDevopsAndCloud</a></p>
</li>
<li><p>LinkedIn: <a href="https://linkedin.com/in/kamlesh-dubale">https://linkedin.com/in/kamlesh-dubale</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How I Deployed My First Website on AWS (S3 + CloudFront + Domain + HTTPS + WAF)]]></title><description><![CDATA[Introduction
For a long time, I kept watching tutorials on cloud and DevOps.
I understood bits and pieces — S3, CloudFront, DNS — but nothing really clicked.
So I decided to stop watching and actually]]></description><link>https://blog.kamleshcloud.com/how-i-deployed-my-first-website-on-aws-s3-cloudfront-domain-https-waf</link><guid isPermaLink="true">https://blog.kamleshcloud.com/how-i-deployed-my-first-website-on-aws-s3-cloudfront-domain-https-waf</guid><dc:creator><![CDATA[Kamlesh Dubale]]></dc:creator><pubDate>Sat, 11 Apr 2026 10:31:56 GMT</pubDate><content:encoded><![CDATA[<h2>Introduction</h2>
<p>For a long time, I kept watching tutorials on cloud and DevOps.</p>
<p>I understood bits and pieces — S3, CloudFront, DNS — but nothing really clicked.</p>
<p>So I decided to stop watching and actually build something.</p>
<p>This project started as a simple goal: "I just want to put a website on the internet using AWS."</p>
<p>What I didn’t expect was how much this one project would teach me about how the internet actually works behind the scenes.</p>
<p>By the end, I had my own live website:</p>
<p>👉 <a href="https://kamleshcloud.com">https://kamleshcloud.com</a></p>
<p>And more importantly, I finally understood what happens when someone opens a website.</p>
<h2>What is a static website?</h2>
<p>Before starting this project, I always thought a website needs a server running in the background.</p>
<p>But that’s not always true.</p>
<p>A static website is simply a collection of files like:</p>
<ul>
<li><p>index.html</p>
</li>
<li><p>style.css</p>
</li>
<li><p>script.js</p>
</li>
</ul>
<p>That’s it.</p>
<p>These files are already written and stored somewhere. When a user opens the website, the browser just downloads these files and displays the page.</p>
<p>There is no backend logic or database involved in this setup.</p>
<p>That was one of the first things that surprised me.</p>
<p>It made me realise that for many use cases like portfolios, landing pages, or simple blogs, you don’t actually need a full server setup.</p>
<p>You just need a place to store your files and a way to deliver them to users.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d24c9340c9cabf44d0c3dd/b673c63b-2924-497d-9a9a-63cd331dbdfd.png" alt="" style="display:block;margin:0 auto" />

<h2>Final Architecture</h2>
<p>Once everything was set up, my website followed this flow:</p>
<p>User → Route 53 → WAF → CloudFront → S3</p>
<p>At first, this looked very confusing to me.</p>
<p>But when I broke it down step by step, it started making sense:</p>
<ul>
<li><p>S3 is where my website files are stored</p>
</li>
<li><p>CloudFront is responsible for delivering those files</p>
</li>
<li><p>Route 53 connects my domain name to the website</p>
</li>
<li><p>WAF acts like a security layer in front</p>
</li>
<li><p>ACM helps enable HTTPS</p>
</li>
</ul>
<p>Instead of thinking of these as separate services, I started seeing them as parts of one system working together to serve a simple website.</p>
<h2>Step-by-Step: What I Actually Did</h2>
<h3>1. Built the website locally</h3>
<p>I started by creating a simple website on my local machine using VS Code.</p>
<p>At this stage, the website only worked on my laptop.</p>
<p>This made me realise something very basic but important:</p>
<p>A website is just a collection of files until it is hosted somewhere on the internet.</p>
<p>Until those files are uploaded to a platform like AWS, no one else can access them.</p>
<p>That was my starting point.</p>
<h3>2. Uploaded the files to S3</h3>
<p>The next step was to move my website from my laptop to the cloud.</p>
<p>For this, I used Amazon S3.</p>
<p>I created a bucket and uploaded my website files:</p>
<ul>
<li><p>index.html</p>
</li>
<li><p>style.css</p>
</li>
<li><p>script.js</p>
</li>
</ul>
<p>At this point, my files were finally stored in AWS.</p>
<p>But something interesting happened.</p>
<p>Even though the files were there, the website still wasn’t properly “live” yet.</p>
<p>That’s when I realised:</p>
<p>Uploading files is not the same as hosting a website.</p>
<p>You still need to tell AWS how to serve those files as a website.</p>
<h3>3. Enabled static website hosting</h3>
<p>This step is where things started to come together.</p>
<p>Inside S3, I enabled static website hosting and set:</p>
<p>👉 index.html as the default page</p>
<p>At first, I didn’t fully understand why this was needed.</p>
<p>But then it made sense.</p>
<p>When someone opens a website, they don’t type something like:</p>
<p>/index.html</p>
<p>They just open the main domain.</p>
<p>So AWS needs to know:</p>
<p>“What file should I show by default?”</p>
<p>That file is called the index document.</p>
<p>Once I enabled this, my website started working through the S3 endpoint.</p>
<p>This was the first time I saw my website actually live on the internet.</p>
<h3>4. Added CloudFront (this confused me at first)</h3>
<p>After getting the S3 version working, the next step was to add CloudFront.</p>
<p>This part was confusing in the beginning.</p>
<p>I kept thinking:</p>
<p>“If S3 is already hosting my website, why do I need CloudFront?”</p>
<p>But later, I understood it in a simple way:</p>
<ul>
<li><p>S3 = storage</p>
</li>
<li><p>CloudFront = delivery</p>
</li>
</ul>
<p>Instead of users directly accessing S3, they now access CloudFront.</p>
<p>So the flow becomes:</p>
<p>User → CloudFront → S3</p>
<p>CloudFront acts as the front layer that handles requests and delivers the website.</p>
<p>This is how most real-world setups work.</p>
<p>That’s when I started understanding that cloud architecture is about separating responsibilities.</p>
<h3>5. Understanding the root object</h3>
<p>This was one of the terms that confused me a lot at first.</p>
<p>In CloudFront, there is something called the default root object.</p>
<p>I set it to:</p>
<p>index.html</p>
<p>But what does that actually mean?</p>
<p>It simply means:</p>
<p>“When someone opens the root of the website, which file should be shown?”</p>
<p>Since users don’t type:</p>
<p>/index.html</p>
<p>we need to tell CloudFront what to load by default.</p>
<p>That’s exactly what the root object does.</p>
<p>Once I understood this, it stopped feeling like a complex term.</p>
<h3>6. Bought a custom domain</h3>
<p>After CloudFront was working, I wanted to make the website feel more real.</p>
<p>So I bought my own domain:</p>
<p>kamleshcloud.com</p>
<p>using Route 53.</p>
<p>This was a big step for me.</p>
<p>Instead of using a long AWS-generated URL, I now had something that actually looked like a real website.</p>
<p>I also realised that this domain is not just for this project.</p>
<p>I can use it in the future for:</p>
<ul>
<li><p>my portfolio</p>
</li>
<li><p>my blog</p>
</li>
<li><p>and even potential business ideas</p>
</li>
</ul>
<p>That made this project feel much more meaningful.</p>
<h3>7. Setting up HTTPS (this tested my patience)</h3>
<p>After setting up the domain, the next step was to make the website secure using HTTPS.</p>
<p>For that, I used AWS Certificate Manager (ACM).</p>
<p>I requested a certificate for my domain, and at first everything looked fine.</p>
<p>But then the status stayed stuck at:</p>
<p>👉 “Pending validation”</p>
<p>This was frustrating because I wasn’t sure what was missing.</p>
<p>After digging a bit, I understood that AWS needed proof that I actually owned the domain.</p>
<p>This is done using DNS validation.</p>
<p>Once I created the required records in Route 53, the certificate finally changed to:</p>
<p>👉 “Issued”</p>
<p>That moment felt really satisfying because now my website was secure and could be accessed using HTTPS.</p>
<h3>8. Connecting the domain (this is where it finally worked)</h3>
<p>Even after setting up the certificate, my domain still didn’t work at first.</p>
<p>When I tried opening it, I got:</p>
<p>👉 DNS_PROBE_FINISHED_NXDOMAIN</p>
<p>At that point, I realised something was still missing.</p>
<p>The domain was not properly connected to CloudFront yet.</p>
<p>To fix this, I created an alias record in Route 53 and pointed it to my CloudFront distribution.</p>
<p>After doing that, I had to wait a few minutes for DNS to propagate.</p>
<p>And then…</p>
<p>🔥 The website finally went LIVE</p>
<p>👉 <a href="https://kamleshcloud.com">https://kamleshcloud.com</a></p>
<p>That was honestly one of the best moments of this project.</p>
<h3>9. Adding WAF (security layer)</h3>
<p>The final step was adding a basic security layer using AWS WAF.</p>
<p>You can think of WAF like a security guard in front of your website.</p>
<p>Before any request reaches CloudFront, WAF checks it first.</p>
<p>So now the flow becomes:</p>
<p>User → Route 53 → WAF → CloudFront → S3</p>
<p>Even though this is a basic setup, it made the architecture feel more complete and closer to real-world systems.</p>
<h2>What I Learned</h2>
<p>This project taught me much more than I expected.</p>
<p>Some of the biggest lessons were:</p>
<ul>
<li><p>A website is just a collection of files</p>
</li>
<li><p>S3 stores the files, CloudFront delivers them</p>
</li>
<li><p>DNS is what connects everything</p>
</li>
<li><p>HTTPS requires validation, not just a button click</p>
</li>
<li><p>Cloud architecture is really about request flow</p>
</li>
</ul>
<p>Most importantly, I realised that things only start making sense when you actually build something.</p>
<h2>Problems I Faced</h2>
<p>This project was not smooth, and that’s what made it valuable.</p>
<p>Some of the issues I ran into:</p>
<ul>
<li><p>Certificate stuck on “Pending validation”</p>
</li>
<li><p>Domain not working (NXDOMAIN error)</p>
</li>
<li><p>Confusion around CloudFront concepts</p>
</li>
<li><p>Understanding root object and behavior</p>
</li>
</ul>
<p>But solving these problems is what made everything click.</p>
<h2>Final Result</h2>
<p>Here is my live website:</p>
<p>👉 <a href="https://kamleshcloud.com">https://kamleshcloud.com</a></p>
<p>And the GitHub repository:</p>
<p>👉 <a href="https://github.com/KamleshDevopsAndCloud">https://github.com/KamleshDevopsAndCloud</a></p>
<h2>Final Thought</h2>
<p>If you are starting your cloud journey, my advice is simple:</p>
<p>Don’t wait until you feel ready.</p>
<p>Just build something.</p>
<p>Break things. Fix them. Try again.</p>
<p>That’s when everything starts making sense.</p>
]]></content:encoded></item></channel></rss>