<?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: zach beecher</title>
    <description>The latest articles on DEV Community by zach beecher (@zach_beecher).</description>
    <link>https://dev.to/zach_beecher</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3430601%2Fe7ae90ef-57bd-4e5a-baf4-402df06d1218.jpg</url>
      <title>DEV Community: zach beecher</title>
      <link>https://dev.to/zach_beecher</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zach_beecher"/>
    <language>en</language>
    <item>
      <title>AWS resume challenge (cloud) 2025 indepth explanation</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Fri, 15 Aug 2025 17:37:59 +0000</pubDate>
      <link>https://dev.to/zach_beecher/aws-resume-challenge-cloud-2025-indepth-explanation-3ijk</link>
      <guid>https://dev.to/zach_beecher/aws-resume-challenge-cloud-2025-indepth-explanation-3ijk</guid>
      <description>&lt;p&gt;*&lt;em&gt;If you didn't know what the challenge is, here it is in all its glory: &lt;a href="https://cloudresumechallenge.dev/docs/the-challenge/aws/" rel="noopener noreferrer"&gt;https://cloudresumechallenge.dev/docs/the-challenge/aws/&lt;/a&gt;&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recommend building this out from scratch doing deep research on your own and struggle as that is how you'll learn, then coming back here to see the detailed approach to see how you did and what approach you took? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🌐 Frontend&lt;br&gt;
Write your resume in HTML and style it with CSS&lt;br&gt;
Host it as a static website on Amazon S3&lt;br&gt;
Secure it with HTTPS via CloudFront&lt;br&gt;
Point a custom domain to it using Route 53 or another DNS provider&lt;br&gt;
🔢 Visitor Counter&lt;br&gt;
Add a JavaScript-based counter to track site visits&lt;br&gt;
Store the count in DynamoDB&lt;br&gt;
Create a serverless API using API Gateway + AWS Lambda&lt;br&gt;
Write the Lambda function in Python using the boto3 library&lt;br&gt;
🔁 DevOps &amp;amp; Testing&lt;br&gt;
Use Infrastructure as Code (like Terraform or CloudFormation)&lt;br&gt;
Set up CI/CD pipelines with GitHub Actions&lt;br&gt;
Write unit tests for your backend code&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Week 1:&lt;/strong&gt; Converted resume into HTML/CSS. Then I uploaded resume to a free tier S3 bucket. Bought domain name on namecheap ($1 dollar, what a deal), looking back should have used Route53 for 20 bucks instead. As the URL redirect on namecheap (DNS) isn't as robust as route53. Setup cloudfront for caching.   Success - have resume on the aws web as static site on S3 bucket. &lt;/p&gt;

&lt;p&gt;**Week 2: **JS based counter on website. Store the count in dynamoDB. Create serverless API using API gateway + Lambda. Write Python and use boto3 library for Lambda function. &lt;/p&gt;

&lt;p&gt;Week 3: Devops time. github actions, setup a repo on gitup with CI/CD pipeline. Setup Unit tests for backend code. &lt;/p&gt;

&lt;p&gt;Why do all this? Demonstrates real-world cloud skills&lt;/p&gt;

&lt;p&gt;Covers key AWS services: S3, Lambda, DynamoDB, CloudFront, Route 53 (or any DNS), IAM&lt;/p&gt;

&lt;p&gt;Builds a portfolio project that’s neat.&lt;/p&gt;

&lt;p&gt;Helps prep for certifications like AWS Cloud Practitioner&lt;/p&gt;

&lt;p&gt;Steps 1-3 easy parts -&amp;gt; HTML resume gpt can do this in 10 seconds -&amp;gt; host on s3 bucket aws (upload document). I'd recommend buying domain name on route53 service (20 us dollars) as going 3rd party creates more headaches like needing to generate a SSL certificate which can be tricky getting in a timely manner.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Detailed steps below: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Connecting an S3 Bucket to CloudFront for HTTPS&lt;br&gt;
To enable HTTPS for your S3 website using CloudFront, follow these steps:&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
An S3 bucket configured for static website hosting&lt;/p&gt;

&lt;p&gt;Your website content uploaded to the bucket&lt;/p&gt;

&lt;p&gt;Step-by-Step Configuration&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a CloudFront Distribution
Open the CloudFront console&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click "Create Distribution"&lt;/p&gt;

&lt;p&gt;Under "Web", click "Get Started"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure Origin Settings
Origin Domain Name: Select your S3 bucket from the dropdown (use the format bucket-name.s3.amazonaws.com for REST API endpoint)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Origin Path: Leave blank unless your content is in a subfolder&lt;/p&gt;

&lt;p&gt;Origin ID: Automatically generated (you can customize if needed)&lt;/p&gt;

&lt;p&gt;Restrict Bucket Access: Select "Yes" for better security&lt;/p&gt;

&lt;p&gt;Origin Access Identity: Create a new OAI or use existing&lt;/p&gt;

&lt;p&gt;Grant Read Permissions on Bucket: Select "Yes, Update Bucket Policy"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure Default Cache Behavior Settings
Viewer Protocol Policy: Select "Redirect HTTP to HTTPS"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Allowed HTTP Methods: GET, HEAD (for static sites) or add others if needed&lt;/p&gt;

&lt;p&gt;Cache Policy: Choose "CachingOptimized" for static content&lt;/p&gt;

&lt;p&gt;Origin Request Policy: Leave as "None" for simple cases&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure Distribution Settings
Price Class: Choose based on your needs (e.g., "Use All Edge Locations")&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Alternate Domain Names (CNAMEs): Enter your custom domain if you have one&lt;/p&gt;

&lt;p&gt;SSL Certificate:&lt;/p&gt;

&lt;p&gt;If using a custom domain, choose "Custom SSL Certificate" and select/request one from ACM&lt;/p&gt;

&lt;p&gt;Otherwise, use "Default CloudFront Certificate"&lt;/p&gt;

&lt;p&gt;Default Root Object: Enter "index.html" (or your default page)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Distribution
Click "Create Distribution"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wait for deployment (this may take 10-40 minutes)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update Your DNS (if using custom domain)
Create a CNAME record pointing your domain to the CloudFront distribution domain (e.g., d111111abcdef8.cloudfront.net)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Important Notes:&lt;br&gt;
After setup, access your site via the CloudFront URL (not the S3 URL) to benefit from HTTPS&lt;/p&gt;

&lt;p&gt;If you previously used the S3 website endpoint (format: bucket-name.s3-website-region.amazonaws.com), you'll need to switch to the REST endpoint format for CloudFront&lt;/p&gt;

&lt;p&gt;Clear cache in CloudFront if you update your content (or implement cache invalidation)&lt;/p&gt;

&lt;p&gt;This setup provides HTTPS encryption, improved performance through caching, and better security by restricting direct access to your S3 bucket.&lt;/p&gt;

&lt;p&gt;**now for the Javascript part 7:&lt;/p&gt;

&lt;p&gt;Implementing a Visitor Counter with JavaScript for Your S3 Resume Website&lt;br&gt;
Here's how to add a visitor counter to your static website hosted on S3 using JavaScript. Since S3 is static hosting (no server-side processing), we'll use a combination of JavaScript and AWS services to track visits.&lt;/p&gt;

&lt;p&gt;Option 1: Simple Counter Using localStorage (Client-Side Only)&lt;br&gt;
javascript&lt;br&gt;
// visitor-counter.js&lt;br&gt;
document.addEventListener('DOMContentLoaded', function() {&lt;br&gt;
    // Check if localStorage is available&lt;br&gt;
    if (typeof(Storage) !== "undefined") {&lt;br&gt;
        // Get or initialize the counter&lt;br&gt;
        let visitCount = localStorage.getItem('visitCount') || 0;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Increment the counter
    visitCount++;

    // Store the updated count
    localStorage.setItem('visitCount', visitCount);

    // Display the count
    document.getElementById('visitor-counter').textContent = 
        `You are visitor number ${visitCount}`;
} else {
    document.getElementById('visitor-counter').textContent = 
        "Visitor counter not available";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;});&lt;br&gt;
HTML:&lt;/p&gt;

&lt;p&gt;html&lt;/p&gt;

&lt;p id="visitor-counter"&gt;Loading visitor count...&lt;/p&gt;



&lt;p&gt;Limitation: This only tracks visits per browser/device.&lt;/p&gt;

&lt;p&gt;Option 2: Using AWS Lambda + API Gateway + DynamoDB (More Accurate)&lt;br&gt;
For a more accurate counter across all visitors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up the backend:
Create a DynamoDB table:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Table name: VisitorCounts&lt;/p&gt;

&lt;p&gt;Primary key: pageId (string)&lt;/p&gt;

&lt;p&gt;Create a Lambda function (Node.js):&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// Lambda function to update and return visitor count&lt;br&gt;
const AWS = require('aws-sdk');&lt;br&gt;
const dynamo = new AWS.DynamoDB.DocumentClient();&lt;/p&gt;

&lt;p&gt;exports.handler = async (event) =&amp;gt; {&lt;br&gt;
    const pageId = 'resume-page'; // or your unique identifier&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Update the count
const updateParams = {
    TableName: 'VisitorCounts',
    Key: { pageId },
    UpdateExpression: 'ADD #count :incr',
    ExpressionAttributeNames: { '#count': 'count' },
    ExpressionAttributeValues: { ':incr': 1 },
    ReturnValues: 'UPDATED_NEW'
};

try {
    const result = await dynamo.update(updateParams).promise();
    return {
        statusCode: 200,
        body: JSON.stringify({ count: result.Attributes.count }),
        headers: { 'Access-Control-Allow-Origin': '*' }
    };
} catch (error) {
    return { statusCode: 500, body: JSON.stringify(error) };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;};&lt;br&gt;
Create API Gateway:&lt;/p&gt;

&lt;p&gt;REST API&lt;/p&gt;

&lt;p&gt;Create a GET method that triggers your Lambda&lt;/p&gt;

&lt;p&gt;Enable CORS&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Client-side JavaScript:&lt;br&gt;
javascript&lt;br&gt;
// visitor-counter.js&lt;br&gt;
document.addEventListener('DOMContentLoaded', function() {&lt;br&gt;
const counterElement = document.getElementById('visitor-counter');&lt;/p&gt;

&lt;p&gt;// Replace with your API Gateway endpoint&lt;br&gt;
const apiUrl = '&lt;a href="https://your-api-id.execute-api.region.amazonaws.com/prod/counter" rel="noopener noreferrer"&gt;https://your-api-id.execute-api.region.amazonaws.com/prod/counter&lt;/a&gt;';&lt;/p&gt;

&lt;p&gt;fetch(apiUrl)&lt;br&gt;
    .then(response =&amp;gt; response.json())&lt;br&gt;
    .then(data =&amp;gt; {&lt;br&gt;
        counterElement.textContent = &lt;br&gt;
            &lt;code&gt;You are visitor number ${data.count}&lt;/code&gt;;&lt;br&gt;
    })&lt;br&gt;
    .catch(error =&amp;gt; {&lt;br&gt;
        console.error('Error fetching visitor count:', error);&lt;br&gt;
        counterElement.textContent = 'Visitor count not available';&lt;br&gt;
    });&lt;br&gt;
});&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTML Implementation:&lt;br&gt;
html&lt;/p&gt;

&lt;p id="visitor-counter"&gt;Loading visitor count...&lt;/p&gt;



&lt;p&gt;Option 3: Using AWS CloudFront + Lambda@Edge&lt;br&gt;
For a more advanced solution with better performance:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a Lambda@Edge function that triggers on viewer requests&lt;/p&gt;

&lt;p&gt;The function would update DynamoDB with each visit&lt;/p&gt;

&lt;p&gt;Inject the count into your page&lt;/p&gt;

&lt;p&gt;Security Considerations:&lt;br&gt;
For the API Gateway solution, consider adding rate limiting&lt;/p&gt;

&lt;p&gt;For production use, you might want to implement some bot detection&lt;/p&gt;

&lt;p&gt;The localStorage solution is simplest but least accurate&lt;/p&gt;

&lt;p&gt;** step 8 now to setup dynamodb - aka database&lt;/p&gt;

&lt;p&gt;Implementing a DynamoDB-Backed Visitor Counter for Your Resume Website&lt;br&gt;
Let's set up a complete solution using DynamoDB to track visitors accurately. This approach will work with your S3-hosted static website and provide a persistent count across all visitors.&lt;/p&gt;

&lt;p&gt;Step 1: Create the DynamoDB Table&lt;br&gt;
Go to the DynamoDB console&lt;/p&gt;

&lt;p&gt;Click "Create table"&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;Table name: WebsiteVisitorCounts&lt;/p&gt;

&lt;p&gt;Partition key: pageId (String)&lt;/p&gt;

&lt;p&gt;Under "Table settings":&lt;/p&gt;

&lt;p&gt;Select "Customize settings"&lt;/p&gt;

&lt;p&gt;Capacity mode: "On-demand"&lt;/p&gt;

&lt;p&gt;Click "Create table"&lt;/p&gt;

&lt;p&gt;Step 2: Create a Lambda Function to Handle Counts&lt;br&gt;
Go to the Lambda console&lt;/p&gt;

&lt;p&gt;Click "Create function"&lt;/p&gt;

&lt;p&gt;Select:&lt;/p&gt;

&lt;p&gt;"Author from scratch"&lt;/p&gt;

&lt;p&gt;Function name: updateVisitorCount&lt;/p&gt;

&lt;p&gt;Runtime: Node.js (latest version)&lt;/p&gt;

&lt;p&gt;Click "Create function"&lt;/p&gt;

&lt;p&gt;Paste this code:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const AWS = require('aws-sdk');&lt;br&gt;
const dynamodb = new AWS.DynamoDB.DocumentClient();&lt;br&gt;
const TABLE_NAME = 'WebsiteVisitorCounts';&lt;/p&gt;

&lt;p&gt;exports.handler = async (event) =&amp;gt; {&lt;br&gt;
    const pageId = 'resume-homepage'; // Unique ID for your page&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const params = {
    TableName: TABLE_NAME,
    Key: { pageId },
    UpdateExpression: 'SET #count = if_not_exists(#count, :start) + :incr',
    ExpressionAttributeNames: { '#count': 'count' },
    ExpressionAttributeValues: { ':incr': 1, ':start': 0 },
    ReturnValues: 'UPDATED_NEW'
};

try {
    const data = await dynamodb.update(params).promise();
    return {
        statusCode: 200,
        body: JSON.stringify({ count: data.Attributes.count }),
        headers: {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET, OPTIONS'
        }
    };
} catch (err) {
    console.error('Error:', err);
    return {
        statusCode: 500,
        body: JSON.stringify({ error: 'Could not update count' })
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;};&lt;br&gt;
Step 3: Set Up API Gateway&lt;br&gt;
Go to API Gateway console&lt;/p&gt;

&lt;p&gt;Click "Create API" &amp;gt; "REST API" &amp;gt; "Build"&lt;/p&gt;

&lt;p&gt;Choose:&lt;/p&gt;

&lt;p&gt;Protocol: REST&lt;/p&gt;

&lt;p&gt;Create new API&lt;/p&gt;

&lt;p&gt;API name: VisitorCounterAPI&lt;/p&gt;

&lt;p&gt;Endpoint Type: Regional&lt;/p&gt;

&lt;p&gt;Click "Create API"&lt;/p&gt;

&lt;p&gt;Create a new resource:&lt;/p&gt;

&lt;p&gt;Actions &amp;gt; Create Resource&lt;/p&gt;

&lt;p&gt;Resource name: counter&lt;/p&gt;

&lt;p&gt;Click "Create Resource"&lt;/p&gt;

&lt;p&gt;Create a GET method:&lt;/p&gt;

&lt;p&gt;Select the /counter resource&lt;/p&gt;

&lt;p&gt;Actions &amp;gt; Create Method &amp;gt; GET&lt;/p&gt;

&lt;p&gt;Integration type: Lambda Function&lt;/p&gt;

&lt;p&gt;Check "Use Lambda Proxy integration"&lt;/p&gt;

&lt;p&gt;Select your updateVisitorCount Lambda&lt;/p&gt;

&lt;p&gt;Click "Save"&lt;/p&gt;

&lt;p&gt;Enable CORS:&lt;/p&gt;

&lt;p&gt;Select the /counter resource&lt;/p&gt;

&lt;p&gt;Actions &amp;gt; Enable CORS&lt;/p&gt;

&lt;p&gt;Keep default settings&lt;/p&gt;

&lt;p&gt;Click "Enable CORS and replace existing CORS headers"&lt;/p&gt;

&lt;p&gt;Deploy the API:&lt;/p&gt;

&lt;p&gt;Actions &amp;gt; Deploy API&lt;/p&gt;

&lt;p&gt;Deployment stage: [New Stage]&lt;/p&gt;

&lt;p&gt;Stage name: prod&lt;/p&gt;

&lt;p&gt;Click "Deploy"&lt;/p&gt;

&lt;p&gt;Step 4: Update Your Website JavaScript&lt;br&gt;
Add this to your HTML:&lt;/p&gt;

&lt;p&gt;html&lt;/p&gt;

&lt;p id="visitor-counter"&gt;Loading visitor count...&lt;/p&gt;


document.addEventListener('DOMContentLoaded', function() {
    // Replace with your actual API Gateway endpoint
    const apiUrl = 'https://your-api-id.execute-api.region.amazonaws.com/prod/counter';
    
    fetch(apiUrl)
        .then(response =&amp;gt; response.json())
        .then(data =&amp;gt; {
            document.getElementById('visitor-counter').textContent = 
                `You are visitor number ${data.count}`;
        })
        .catch(error =&amp;gt; {
            console.error('Error fetching visitor count:', error);
            document.getElementById('visitor-counter').textContent = 
                'Visitor count not available';
        });
});


&lt;p&gt;Step 5: Set Up Permissions&lt;br&gt;
Go to your Lambda function&lt;/p&gt;

&lt;p&gt;Under "Configuration" &amp;gt; "Permissions"&lt;/p&gt;

&lt;p&gt;Click the execution role name to edit in IAM&lt;/p&gt;

&lt;p&gt;Add these permissions:&lt;/p&gt;

&lt;p&gt;dynamodb:UpdateItem&lt;/p&gt;

&lt;p&gt;dynamodb:GetItem&lt;/p&gt;

&lt;p&gt;(Attach the AmazonDynamoDBFullAccess policy if you prefer)&lt;/p&gt;

&lt;p&gt;Step 6: Initial Test&lt;br&gt;
Manually add an initial record to DynamoDB:&lt;/p&gt;

&lt;p&gt;Go to DynamoDB console&lt;/p&gt;

&lt;p&gt;Select your table&lt;/p&gt;

&lt;p&gt;Click "Create item"&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;pageId: "resume-homepage"&lt;/p&gt;

&lt;p&gt;count: 0 (Number)&lt;/p&gt;

&lt;p&gt;Save&lt;/p&gt;

&lt;p&gt;Cost Considerations&lt;br&gt;
With on-demand DynamoDB:&lt;/p&gt;

&lt;p&gt;You get 1 million read requests and 1 million write requests per month for free in AWS Free Tier&lt;/p&gt;

&lt;p&gt;After that, $1.25 per million write requests and $0.25 per million read requests&lt;/p&gt;

&lt;p&gt;For a personal resume site, you'll likely stay well within free tier limits&lt;/p&gt;

&lt;p&gt;Advanced Options&lt;br&gt;
Add caching: To reduce DynamoDB calls, you could:&lt;/p&gt;

&lt;p&gt;Use localStorage to cache the count for returning visitors&lt;/p&gt;

&lt;p&gt;Implement CloudFront caching for the API response&lt;/p&gt;

&lt;p&gt;Enhance tracking: You could add:&lt;/p&gt;

&lt;p&gt;Date-based tracking&lt;/p&gt;

&lt;p&gt;Unique visitor detection (using cookies)&lt;/p&gt;

&lt;p&gt;Geographic information&lt;/p&gt;

&lt;p&gt;**APi time! Labnda get requests &lt;/p&gt;

&lt;p&gt;Creating a Secure API for Your Visitor Counter Using API Gateway and Lambda&lt;br&gt;
I'll guide you through setting up a proper API architecture that prevents direct database access from your frontend JavaScript. Here's the complete implementation:&lt;/p&gt;

&lt;p&gt;Architecture Overview&lt;br&gt;
text&lt;br&gt;
[S3 Website] → [API Gateway] → [Lambda] → [DynamoDB]&lt;br&gt;
Step 1: Create the Lambda Function&lt;br&gt;
Go to the Lambda Console&lt;/p&gt;

&lt;p&gt;Click "Create function"&lt;/p&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;p&gt;Function name: visitorCounterAPI&lt;/p&gt;

&lt;p&gt;Runtime: Node.js 18.x&lt;/p&gt;

&lt;p&gt;Architecture: x86_64&lt;/p&gt;

&lt;p&gt;Click "Create function"&lt;/p&gt;

&lt;p&gt;Paste this code:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const AWS = require('aws-sdk');&lt;br&gt;
const dynamodb = new AWS.DynamoDB.DocumentClient();&lt;br&gt;
const TABLE_NAME = process.env.TABLE_NAME || 'WebsiteVisitorCounts';&lt;/p&gt;

&lt;p&gt;exports.handler = async (event) =&amp;gt; {&lt;br&gt;
    // Validate HTTP method&lt;br&gt;
    if (event.httpMethod !== 'GET') {&lt;br&gt;
        return {&lt;br&gt;
            statusCode: 405,&lt;br&gt;
            body: JSON.stringify({ error: 'Method Not Allowed' }),&lt;br&gt;
            headers: { 'Access-Control-Allow-Origin': '*' }&lt;br&gt;
        };&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const pageId = 'resume-homepage'; // Your unique identifier

try {
    // Atomic update of the counter
    const result = await dynamodb.update({
        TableName: TABLE_NAME,
        Key: { pageId },
        UpdateExpression: 'SET #count = if_not_exists(#count, :start) + :incr',
        ExpressionAttributeNames: { '#count': 'count' },
        ExpressionAttributeValues: { ':incr': 1, ':start': 0 },
        ReturnValues: 'UPDATED_NEW'
    }).promise();

    return {
        statusCode: 200,
        body: JSON.stringify({ 
            count: result.Attributes.count,
            timestamp: new Date().toISOString() 
        }),
        headers: { 
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json'
        }
    };
} catch (error) {
    console.error('DynamoDB Error:', error);
    return {
        statusCode: 500,
        body: JSON.stringify({ error: 'Internal Server Error' }),
        headers: { 'Access-Control-Allow-Origin': '*' }
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;};&lt;br&gt;
Step 2: Configure API Gateway&lt;br&gt;
Go to API Gateway Console&lt;/p&gt;

&lt;p&gt;Create new HTTP API (not REST API for simpler CORS)&lt;/p&gt;

&lt;p&gt;Click "Add integration"&lt;/p&gt;

&lt;p&gt;Select Lambda&lt;/p&gt;

&lt;p&gt;Choose your visitorCounterAPI function&lt;/p&gt;

&lt;p&gt;API name: VisitorCounterAPI&lt;/p&gt;

&lt;p&gt;Configure routes:&lt;/p&gt;

&lt;p&gt;Path: /count&lt;/p&gt;

&lt;p&gt;Method: GET&lt;/p&gt;

&lt;p&gt;Click "Next" then "Create"&lt;/p&gt;

&lt;p&gt;Step 3: Set Up CORS&lt;br&gt;
In your API Gateway:&lt;/p&gt;

&lt;p&gt;Select your API&lt;/p&gt;

&lt;p&gt;Click "CORS" in the left menu&lt;/p&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;p&gt;Allow origins: * (or your specific domain)&lt;/p&gt;

&lt;p&gt;Allow methods: GET&lt;/p&gt;

&lt;p&gt;Allow headers: Content-Type&lt;/p&gt;

&lt;p&gt;Click "Save"&lt;/p&gt;

&lt;p&gt;Step 4: Set Up Lambda Permissions&lt;br&gt;
Go to your Lambda function&lt;/p&gt;

&lt;p&gt;Under "Configuration" &amp;gt; "Permissions"&lt;/p&gt;

&lt;p&gt;Click the execution role name&lt;/p&gt;

&lt;p&gt;Attach these policies:&lt;/p&gt;

&lt;p&gt;AWSLambdaBasicExecutionRole&lt;/p&gt;

&lt;p&gt;Create custom policy for DynamoDB access:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "Version": "2012-10-17",&lt;br&gt;
    "Statement": [&lt;br&gt;
        {&lt;br&gt;
            "Effect": "Allow",&lt;br&gt;
            "Action": [&lt;br&gt;
                "dynamodb:UpdateItem",&lt;br&gt;
                "dynamodb:GetItem"&lt;br&gt;
            ],&lt;br&gt;
            "Resource": "arn:aws:dynamodb:YOUR_REGION:YOUR_ACCOUNT_ID:table/WebsiteVisitorCounts"&lt;br&gt;
        }&lt;br&gt;
    ]&lt;br&gt;
}&lt;br&gt;
Step 5: Implement Frontend JavaScript&lt;br&gt;
javascript&lt;br&gt;
// In your S3-hosted HTML file&lt;br&gt;
async function updateVisitorCount() {&lt;br&gt;
    const counterElement = document.getElementById('visitor-counter');&lt;br&gt;
    if (!counterElement) return;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
    // Replace with your actual API Gateway endpoint
    const apiUrl = 'https://your-api-id.execute-api.region.amazonaws.com/count';

    const response = await fetch(apiUrl, {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' }
    });

    if (!response.ok) throw new Error('Network response was not ok');

    const data = await response.json();
    counterElement.textContent = `Visitor Count: ${data.count}`;
} catch (error) {
    console.error('Error fetching visitor count:', error);
    counterElement.textContent = 'Visitor count unavailable';
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;// Call when page loads&lt;br&gt;
document.addEventListener('DOMContentLoaded', updateVisitorCount);&lt;br&gt;
Step 6: Deploy and Test&lt;br&gt;
Deploy your API:&lt;/p&gt;

&lt;p&gt;In API Gateway, select your API&lt;/p&gt;

&lt;p&gt;Click "Deploy"&lt;/p&gt;

&lt;p&gt;Create new stage (e.g., "prod")&lt;/p&gt;

&lt;p&gt;Note the invoke URL&lt;/p&gt;

&lt;p&gt;Test the endpoint:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
curl &lt;a href="https://your-api-id.execute-api.region.amazonaws.com/prod/count" rel="noopener noreferrer"&gt;https://your-api-id.execute-api.region.amazonaws.com/prod/count&lt;/a&gt;&lt;br&gt;
Check DynamoDB to verify the count is incrementing&lt;/p&gt;

&lt;p&gt;Security Enhancements&lt;br&gt;
Rate Limiting:&lt;/p&gt;

&lt;p&gt;In API Gateway, create a usage plan&lt;/p&gt;

&lt;p&gt;Set throttling to ~5 requests/second&lt;/p&gt;

&lt;p&gt;API Keys (optional):&lt;/p&gt;

&lt;p&gt;Create API key in API Gateway&lt;/p&gt;

&lt;p&gt;Require key in your frontend requests&lt;/p&gt;

&lt;p&gt;Input Validation:&lt;/p&gt;

&lt;p&gt;The Lambda already validates it's a GET request&lt;/p&gt;

&lt;p&gt;You could add more validation if needed&lt;/p&gt;

&lt;p&gt;Cost Analysis&lt;br&gt;
API Gateway: First 1 million requests/month are free ($1.00/million after)&lt;/p&gt;

&lt;p&gt;Lambda: 1 million free requests/month ($0.20/million after)&lt;/p&gt;

&lt;p&gt;DynamoDB: On-demand pricing, minimal cost for this use case&lt;/p&gt;

&lt;p&gt;For a personal resume site, you'll likely stay well within free tier limits.&lt;/p&gt;

&lt;p&gt;**Python time! &lt;/p&gt;

&lt;p&gt;Implementing the Visitor Counter in Python with Lambda and Boto3&lt;br&gt;
Let's rewrite the Lambda function in Python using the boto3 library for AWS operations. This will give you exposure to Python for backend development while maintaining all the security benefits of the API Gateway architecture.&lt;/p&gt;

&lt;p&gt;Python Lambda Function Code&lt;br&gt;
python&lt;br&gt;
import json&lt;br&gt;
import os&lt;br&gt;
from datetime import datetime&lt;br&gt;
import boto3&lt;/p&gt;

&lt;p&gt;dynamodb = boto3.resource('dynamodb')&lt;br&gt;
table_name = os.environ.get('TABLE_NAME', 'WebsiteVisitorCounts')&lt;br&gt;
table = dynamodb.Table(table_name)&lt;/p&gt;

&lt;p&gt;def lambda_handler(event, context):&lt;br&gt;
    # Validate HTTP method&lt;br&gt;
    if event['httpMethod'] != 'GET':&lt;br&gt;
        return {&lt;br&gt;
            'statusCode': 405,&lt;br&gt;
            'body': json.dumps({'error': 'Method Not Allowed'}),&lt;br&gt;
            'headers': {&lt;br&gt;
                'Content-Type': 'application/json',&lt;br&gt;
                'Access-Control-Allow-Origin': '*'&lt;br&gt;
            }&lt;br&gt;
        }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page_id = 'resume-homepage'  # Your unique identifier

try:
    # Atomic update of the counter
    response = table.update_item(
        Key={'pageId': page_id},
        UpdateExpression='SET #count = if_not_exists(#count, :start) + :incr',
        ExpressionAttributeNames={'#count': 'count'},
        ExpressionAttributeValues={':incr': 1, ':start': 0},
        ReturnValues='UPDATED_NEW'
    )

    return {
        'statusCode': 200,
        'body': json.dumps({
            'count': response['Attributes']['count'],
            'timestamp': datetime.utcnow().isoformat()
        }),
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }

except Exception as e:
    print(f"Error updating DynamoDB: {str(e)}")
    return {
        'statusCode': 500,
        'body': json.dumps({'error': 'Internal Server Error'}),
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Key Python Concepts Used&lt;br&gt;
Boto3 Library: AWS's official Python SDK for interacting with AWS services&lt;/p&gt;

&lt;p&gt;Environment Variables: Using os.environ for configuration&lt;/p&gt;

&lt;p&gt;Error Handling: Python's try-except blocks&lt;/p&gt;

&lt;p&gt;Dictionary Manipulation: Working with AWS response objects&lt;/p&gt;

&lt;p&gt;JSON Handling: The json module for serialization&lt;/p&gt;

&lt;p&gt;Deployment Steps&lt;br&gt;
Create the Lambda Function:&lt;/p&gt;

&lt;p&gt;Go to AWS Lambda Console&lt;/p&gt;

&lt;p&gt;Click "Create function"&lt;/p&gt;

&lt;p&gt;Select "Python 3.9" or later runtime&lt;/p&gt;

&lt;p&gt;Name: pythonVisitorCounter&lt;/p&gt;

&lt;p&gt;Upload the Code:&lt;/p&gt;

&lt;p&gt;Paste the Python code above into the inline editor&lt;/p&gt;

&lt;p&gt;Or package it as a ZIP file if you have dependencies&lt;/p&gt;

&lt;p&gt;Set Environment Variable:&lt;/p&gt;

&lt;p&gt;In Configuration → Environment variables&lt;/p&gt;

&lt;p&gt;Add: TABLE_NAME = WebsiteVisitorCounts&lt;/p&gt;

&lt;p&gt;Set Permissions:&lt;/p&gt;

&lt;p&gt;Ensure the Lambda execution role has permissions to:&lt;/p&gt;

&lt;p&gt;dynamodb:UpdateItem&lt;/p&gt;

&lt;p&gt;dynamodb:GetItem&lt;/p&gt;

&lt;p&gt;Attach the AmazonDynamoDBFullAccess policy if needed&lt;/p&gt;

&lt;p&gt;Connect to API Gateway:&lt;/p&gt;

&lt;p&gt;Use the same API Gateway setup as before&lt;/p&gt;

&lt;p&gt;Just change the Lambda integration to point to your Python function&lt;/p&gt;

&lt;p&gt;Python-Specific Benefits&lt;br&gt;
Cleaner Syntax: Python's readability makes the code more maintainable&lt;/p&gt;

&lt;p&gt;Strong Typing: Better error checking (especially with tools like mypy)&lt;/p&gt;

&lt;p&gt;Rich Ecosystem: Access to Python's extensive libraries if you need to add functionality&lt;/p&gt;

&lt;p&gt;Better Error Handling: More granular exception handling than JavaScript&lt;/p&gt;

&lt;p&gt;Testing the Python Function&lt;br&gt;
You can test locally before deploying:&lt;/p&gt;

&lt;p&gt;Install Python 3.9+&lt;/p&gt;

&lt;p&gt;Install dependencies:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
pip install boto3&lt;br&gt;
Create a test event (save as test_event.json):&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
    "httpMethod": "GET"&lt;br&gt;
}&lt;br&gt;
Test locally (with AWS credentials configured):&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import lambda_function&lt;br&gt;
print(lambda_function.lambda_handler({"httpMethod": "GET"}, None))&lt;br&gt;
Cost Considerations&lt;br&gt;
The Python implementation has the same cost profile as the JavaScript version:&lt;/p&gt;

&lt;p&gt;First 1M Lambda requests free&lt;/p&gt;

&lt;p&gt;Very minimal DynamoDB costs&lt;/p&gt;

&lt;p&gt;API Gateway free tier covers typical personal site traffic&lt;/p&gt;

&lt;p&gt;Next Steps&lt;br&gt;
Consider adding input validation&lt;/p&gt;

&lt;p&gt;Add logging with Python's logging module&lt;/p&gt;

&lt;p&gt;Implement caching if needed&lt;/p&gt;

&lt;p&gt;Set up monitoring with CloudWatch&lt;/p&gt;

&lt;p&gt;**Tests time - unit tests / integration&lt;/p&gt;

&lt;p&gt;Writing Tests for Your Python Lambda Function&lt;br&gt;
Let's implement a comprehensive test suite for your visitor counter Lambda function using Python's built-in unittest framework. We'll test both the happy path and error scenarios.&lt;/p&gt;

&lt;p&gt;Test Implementation&lt;br&gt;
Create a new file named test_lambda_function.py with these tests:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import unittest&lt;br&gt;
from unittest.mock import patch, MagicMock&lt;br&gt;
import lambda_function&lt;br&gt;
import json&lt;/p&gt;

&lt;p&gt;class TestVisitorCounterLambda(unittest.TestCase):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def setUp(self):
    self.event = {
        'httpMethod': 'GET',
        'headers': {}
    }
    self.context = {}

@patch('lambda_function.dynamodb.Table')
def test_successful_count_update(self, mock_table):
    # Configure mock DynamoDB response
    mock_response = {
        'Attributes': {'count': 42},
        'ResponseMetadata': {'HTTPStatusCode': 200}
    }
    mock_table.return_value.update_item.return_value = mock_response

    # Call the function
    result = lambda_function.lambda_handler(self.event, self.context)

    # Verify results
    self.assertEqual(result['statusCode'], 200)
    response_body = json.loads(result['body'])
    self.assertEqual(response_body['count'], 42)
    self.assertIn('timestamp', response_body)
    self.assertEqual(result['headers']['Content-Type'], 'application/json')

    # Verify DynamoDB was called correctly
    mock_table.return_value.update_item.assert_called_once_with(
        Key={'pageId': 'resume-homepage'},
        UpdateExpression='SET #count = if_not_exists(#count, :start) + :incr',
        ExpressionAttributeNames={'#count': 'count'},
        ExpressionAttributeValues={':incr': 1, ':start': 0},
        ReturnValues='UPDATED_NEW'
    )

@patch('lambda_function.dynamodb.Table')
def test_wrong_http_method(self, mock_table):
    test_event = {'httpMethod': 'POST'}

    result = lambda_function.lambda_handler(test_event, self.context)

    self.assertEqual(result['statusCode'], 405)
    response_body = json.loads(result['body'])
    self.assertEqual(response_body['error'], 'Method Not Allowed')
    mock_table.return_value.update_item.assert_not_called()

@patch('lambda_function.dynamodb.Table')
def test_dynamodb_error_handling(self, mock_table):
    # Simulate DynamoDB error
    mock_table.return_value.update_item.side_effect = Exception('DB Error')

    result = lambda_function.lambda_handler(self.event, self.context)

    self.assertEqual(result['statusCode'], 500)
    response_body = json.loads(result['body'])
    self.assertEqual(response_body['error'], 'Internal Server Error')

@patch.dict('os.environ', {'TABLE_NAME': 'custom-table'})
@patch('lambda_function.dynamodb.Table')
def test_custom_table_name(self, mock_table):
    mock_response = {'Attributes': {'count': 1}}
    mock_table.return_value.update_item.return_value = mock_response

    lambda_function.lambda_handler(self.event, self.context)

    # Verify the custom table name was used
    mock_table.assert_called_once_with('custom-table')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == '&lt;strong&gt;main&lt;/strong&gt;':&lt;br&gt;
    unittest.main()&lt;br&gt;
Key Testing Concepts Covered&lt;br&gt;
Mocking AWS Services: Using unittest.mock to simulate DynamoDB&lt;/p&gt;

&lt;p&gt;Happy Path Testing: Verifying successful counter increments&lt;/p&gt;

&lt;p&gt;Error Scenarios: Testing wrong HTTP methods and database failures&lt;/p&gt;

&lt;p&gt;Configuration Testing: Checking environment variable handling&lt;/p&gt;

&lt;p&gt;Response Validation: Ensuring proper status codes and headers&lt;/p&gt;

&lt;p&gt;Test Dependencies&lt;br&gt;
Add these to a requirements-test.txt file:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
boto3==1.26.*&lt;br&gt;
moto==4.1.*&lt;br&gt;
pytest==7.4.*&lt;br&gt;
pytest-cov==4.1.*&lt;br&gt;
Running the Tests&lt;br&gt;
Install test dependencies:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
pip install -r requirements-test.txt&lt;br&gt;
Run tests with unittest:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
python -m unittest test_lambda_function.py&lt;br&gt;
Or with pytest (for more detailed output):&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
pytest test_lambda_function.py -v&lt;br&gt;
For coverage report:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
pytest --cov=lambda_function test_lambda_function.py&lt;br&gt;
Advanced Testing Considerations&lt;br&gt;
Integration Tests:&lt;/p&gt;

&lt;p&gt;Deploy to a test AWS environment&lt;/p&gt;

&lt;p&gt;Use actual API Gateway and DynamoDB resources&lt;/p&gt;

&lt;p&gt;Test the full stack&lt;/p&gt;

&lt;p&gt;Performance Tests:&lt;/p&gt;

&lt;p&gt;Check Lambda cold start times&lt;/p&gt;

&lt;p&gt;Verify DynamoDB latency&lt;/p&gt;

&lt;p&gt;Security Tests:&lt;/p&gt;

&lt;p&gt;Validate IAM permissions&lt;/p&gt;

&lt;p&gt;Check for sensitive data exposure&lt;/p&gt;

&lt;p&gt;Example Integration Test&lt;br&gt;
python&lt;br&gt;
import boto3&lt;br&gt;
import os&lt;br&gt;
import lambda_function&lt;/p&gt;

&lt;p&gt;class TestIntegration(unittest.TestCase):&lt;br&gt;
    @classmethod&lt;br&gt;
    def setUpClass(cls):&lt;br&gt;
        # Set up real DynamoDB table (run once before all tests)&lt;br&gt;
        cls.dynamodb = boto3.resource('dynamodb')&lt;br&gt;
        cls.table_name = 'test-website-counts'&lt;br&gt;
        cls.table = cls.dynamodb.create_table(&lt;br&gt;
            TableName=cls.table_name,&lt;br&gt;
            KeySchema=[{'AttributeName': 'pageId', 'KeyType': 'HASH'}],&lt;br&gt;
            AttributeDefinitions=[{'AttributeName': 'pageId', 'AttributeType': 'S'}],&lt;br&gt;
            BillingMode='PAY_PER_REQUEST'&lt;br&gt;
        )&lt;br&gt;
        os.environ['TABLE_NAME'] = cls.table_name&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_real_dynamodb_integration(self):
    # Initial test
    response1 = lambda_function.lambda_handler({'httpMethod': 'GET'}, None)
    self.assertEqual(response1['statusCode'], 200)

    # Verify increment works
    response2 = lambda_function.lambda_handler({'httpMethod': 'GET'}, None)
    count1 = json.loads(response1['body'])['count']
    count2 = json.loads(response2['body'])['count']
    self.assertEqual(count2, count1 + 1)

@classmethod
def tearDownClass(cls):
    # Clean up
    cls.table.delete()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Test Organization Best Practices&lt;br&gt;
Keep unit tests fast and isolated&lt;/p&gt;

&lt;p&gt;Put integration tests in separate files&lt;/p&gt;

&lt;p&gt;Use mocking for AWS services in unit tests&lt;/p&gt;

&lt;p&gt;Test both success and failure paths&lt;/p&gt;

&lt;p&gt;Include edge cases (empty inputs, rate limits, etc.)&lt;/p&gt;

&lt;p&gt;** IAC Infra as code - aka teraform&lt;/p&gt;
&lt;h1&gt;
  
  
  Implementing Infrastructure as Code with AWS SAM
&lt;/h1&gt;

&lt;p&gt;Let's convert your entire visitor counter solution to Infrastructure as Code using AWS SAM (Serverless Application Model). This approach will make your infrastructure reproducible, version-controlled, and easily deployable.&lt;/p&gt;
&lt;h2&gt;
  
  
  AWS SAM Template (template.yaml)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Visitor counter for resume website&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.9&lt;/span&gt;
    &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;VisitorCountTable&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;VisitorCounterFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;visitor_counter/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lambda_function.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;DynamoDBCrudPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;VisitorCountTable&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ApiEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/count&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
            &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;VisitorCounterApi&lt;/span&gt;

  &lt;span class="na"&gt;VisitorCounterApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Api&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prod&lt;/span&gt;
      &lt;span class="na"&gt;Cors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;AllowMethods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'GET'"&lt;/span&gt;
        &lt;span class="na"&gt;AllowOrigin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'*'"&lt;/span&gt;
        &lt;span class="na"&gt;AllowHeaders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'"&lt;/span&gt;

  &lt;span class="na"&gt;VisitorCountTable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::DynamoDB::Table&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WebsiteVisitorCounts&lt;/span&gt;
      &lt;span class="na"&gt;AttributeDefinitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pageId&lt;/span&gt;
          &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
      &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pageId&lt;/span&gt;
          &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HASH&lt;/span&gt;
      &lt;span class="na"&gt;BillingMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAY_PER_REQUEST&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ApiEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Gateway&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Prod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stage"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://${VisitorCounterApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/count"&lt;/span&gt;
  &lt;span class="na"&gt;LambdaFunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Visitor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Counter&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Lambda&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Function&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Name"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;VisitorCounterFunction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/resume-visitor-counter/
├── template.yaml            # SAM template
├── visitor_counter/
│   ├── lambda_function.py   # Lambda code
│   └── requirements.txt     # Python dependencies
├── tests/                   # Test files
└── README.md                # Deployment instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Lambda Function Code (visitor_counter/lambda_function.py)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# API Gateway proxy events wrap the HTTP request
&lt;/span&gt;    &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;httpMethod&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Method Not Allowed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;page_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;resume-homepage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pageId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;UpdateExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SET #count = if_not_exists(#count, :start) + :incr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:incr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ReturnValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UPDATED_NEW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Attributes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error updating DynamoDB: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Internal Server Error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Deployment Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install AWS SAM CLI&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;   pip &lt;span class="nb"&gt;install &lt;/span&gt;aws-sam-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize Project&lt;/strong&gt; (if starting from scratch):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   sam init &lt;span class="nt"&gt;--runtime&lt;/span&gt; python3.9 &lt;span class="nt"&gt;--app-template&lt;/span&gt; hello-world &lt;span class="nt"&gt;--name&lt;/span&gt; resume-visitor-counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build Application&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;   sam build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to AWS&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;   sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Follow prompts to configure stack name, AWS region, etc.&lt;/li&gt;
&lt;li&gt;Confirm deployment when prompted&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;View Outputs&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 cloudformation describe-stacks &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--stack-name&lt;/span&gt; resume-visitor-counter &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Benefits of This Approach
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility&lt;/strong&gt;: Entire infrastructure defined in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt;: Track changes alongside application code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Updates&lt;/strong&gt;: Modify template and redeploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Parity&lt;/strong&gt;: Same configuration across dev/test/prod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Capability&lt;/strong&gt;: Revert to previous versions if needed&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Advanced IaC Features
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: Make configurations dynamic
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
       &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WebsiteVisitorCounts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custom Domains&lt;/strong&gt;: Add your own domain to API Gateway
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;Domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;DomainName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.yourdomain.com&lt;/span&gt;
     &lt;span class="na"&gt;CertificateArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;SSLCertificate&lt;/span&gt;
     &lt;span class="na"&gt;Route53&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;HostedZoneId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;HostedZone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Environments&lt;/strong&gt;: Use SAM with different configs
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   sam deploy &lt;span class="nt"&gt;--config-env&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Integration&lt;/strong&gt;: Add to your deployment pipeline
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# Example GitHub Actions step&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy with SAM&lt;/span&gt;
     &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sam deploy --no-confirm-changeset&lt;/span&gt;
     &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
       &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Would you like me to show you how to add monitoring/alarms to this SAM template or demonstrate how to set up a CI/CD pipeline for automated deployments?&lt;/p&gt;

&lt;p&gt;** source control - aka github version control etc&lt;/p&gt;
&lt;h1&gt;
  
  
  Setting Up CI/CD with GitHub Actions for Your Resume Website
&lt;/h1&gt;

&lt;p&gt;Let's implement a complete CI/CD pipeline for both your frontend (S3 website) and backend (Lambda/DynamoDB/API Gateway) using GitHub Actions. This will automatically deploy changes whenever you push to your repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Repository Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resume-website/
├── backend/
│   ├── template.yaml       # SAM template
│   ├── lambda_function.py  # Lambda code
│   └── requirements.txt    # Python dependencies
├── frontend/
│   ├── index.html          # Your resume page
│   ├── styles.css          # CSS files
│   └── visitor-counter.js  # Frontend JavaScript
├── .github/
│   └── workflows/
│       ├── backend.yml     # Backend CI/CD workflow
│       └── frontend.yml    # Frontend CI/CD workflow
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Backend CI/CD (GitHub Actions)
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/backend.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Backend Deployment&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backend/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/backend.yml'&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;STACK_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-visitor-counter&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_IAM_ROLE }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install SAM CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install aws-sam-cli&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build SAM application&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sam build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy SAM application&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name ${{ env.STACK_NAME }} --region ${{ env.AWS_REGION }} --capabilities CAPABILITY_IAM&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get API endpoint&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;API_URL=$(aws cloudformation describe-stacks \&lt;/span&gt;
            &lt;span class="s"&gt;--stack-name ${{ env.STACK_NAME }} \&lt;/span&gt;
            &lt;span class="s"&gt;--query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \&lt;/span&gt;
            &lt;span class="s"&gt;--output text)&lt;/span&gt;
          &lt;span class="s"&gt;echo "API_URL=$API_URL" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;
          &lt;span class="s"&gt;echo "::set-output name=url::$API_URL"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update frontend config&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "VITE_API_URL=${{ env.API_URL }}" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;
          &lt;span class="s"&gt;echo "API endpoint: ${{ env.API_URL }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Frontend CI/CD (GitHub Actions)
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/frontend.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Frontend Deployment&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;frontend/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/frontend.yml'&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-resume-bucket-name&lt;/span&gt;
  &lt;span class="na"&gt;CLOUDFRONT_DIST_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-cloudfront-dist-id&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_IAM_ROLE }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;  &lt;span class="c1"&gt;# If using npm packages&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build static files&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Replace API endpoint in JavaScript&lt;/span&gt;
          &lt;span class="s"&gt;if [ -n "${{ vars.API_URL }}" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;sed -i "s|const apiUrl = .*|const apiUrl = '${{ vars.API_URL }}';|" visitor-counter.js&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to S3&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 sync ./frontend s3://${{ env.S3_BUCKET }} --delete&lt;/span&gt;
          &lt;span class="s"&gt;echo "Frontend deployed to: https://${{ env.S3_BUCKET }}.s3-website-${{ env.AWS_REGION }}.amazonaws.com"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalidate CloudFront cache&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws cloudfront create-invalidation \&lt;/span&gt;
            &lt;span class="s"&gt;--distribution-id ${{ env.CLOUDFRONT_DIST_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--paths "/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Required GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;Set these in your GitHub repository settings (Settings &amp;gt; Secrets):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;AWS_IAM_ROLE&lt;/code&gt;: ARN of IAM role for deployment (e.g., &lt;code&gt;arn:aws:iam::123456789012:role/GitHubActionsRole&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_REGION&lt;/code&gt;: Your AWS region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. AWS IAM Role Setup
&lt;/h2&gt;

&lt;p&gt;Create an IAM role for GitHub Actions with these permissions:&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;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;"Allow"&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;"cloudformation:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"lambda:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:*"&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:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cloudfront:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"iam:*"&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="s2"&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;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;h2&gt;
  
  
  6. Frontend JavaScript (visitor-counter.js)
&lt;/h2&gt;

&lt;p&gt;Update to use the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get API URL from build process&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourapi.execute-api.region.amazonaws.com/Prod/count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visitor-counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;counterElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;counterElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Visitor Count: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error fetching visitor count:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;counterElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Visitor count unavailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Workflow Improvements
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add Testing Stage&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Python tests&lt;/span&gt;
     &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
     &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
       &lt;span class="s"&gt;pip install pytest&lt;/span&gt;
       &lt;span class="s"&gt;python -m pytest tests/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment-Specific Deployments&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual Approval for Production&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;deploy-prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
     &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
     &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
     &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait for approval&lt;/span&gt;
         &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trstringer/manual-approval@v1&lt;/span&gt;
         &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Benefits
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Deployments&lt;/strong&gt;: Code changes trigger deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Same process for all environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt;: Clear deployment history in GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Capability&lt;/strong&gt;: Revert to previous commits if needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Least-privilege IAM roles&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;** github actions in depth&lt;/p&gt;

&lt;h1&gt;
  
  
  Backend CI/CD Pipeline with Testing and SAM Deployment
&lt;/h1&gt;

&lt;p&gt;Here's a complete GitHub Actions workflow that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your Python tests on every push&lt;/li&gt;
&lt;li&gt;Only deploy if tests pass&lt;/li&gt;
&lt;li&gt;Package and deploy your SAM application to AWS&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;.github/workflows/backend-ci-cd.yml&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Backend CI/CD Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backend/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/backend-ci-cd.yml'&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;STACK_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-visitor-counter&lt;/span&gt;
  &lt;span class="na"&gt;PYTHON_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Python Tests&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ env.PYTHON_VERSION }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.PYTHON_VERSION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;
          &lt;span class="s"&gt;pip install pytest boto3 moto&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run unit tests&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pytest tests/ -v --cov=./ --cov-report=xml&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload coverage report&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend/coverage.xml&lt;/span&gt;
          &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unittests&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy SAM Application&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEPLOY_ROLE }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install SAM CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install aws-sam-cli&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build SAM application&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sam build --use-container&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run SAM validate&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sam validate&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy SAM application&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sam deploy \&lt;/span&gt;
            &lt;span class="s"&gt;--no-confirm-changeset \&lt;/span&gt;
            &lt;span class="s"&gt;--no-fail-on-empty-changeset \&lt;/span&gt;
            &lt;span class="s"&gt;--stack-name ${{ env.STACK_NAME }} \&lt;/span&gt;
            &lt;span class="s"&gt;--region ${{ env.AWS_REGION }} \&lt;/span&gt;
            &lt;span class="s"&gt;--capabilities CAPABILITY_IAM \&lt;/span&gt;
            &lt;span class="s"&gt;--resolve-s3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Output API endpoint&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "API Endpoint:"&lt;/span&gt;
          &lt;span class="s"&gt;aws cloudformation describe-stacks \&lt;/span&gt;
            &lt;span class="s"&gt;--stack-name ${{ env.STACK_NAME }} \&lt;/span&gt;
            &lt;span class="s"&gt;--query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \&lt;/span&gt;
            &lt;span class="s"&gt;--output text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Required Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS IAM Role&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a role with these permissions:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;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;"Allow"&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;"cloudformation:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"lambda:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:*"&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:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&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="s2"&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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;Add the role ARN to GitHub Secrets as &lt;code&gt;AWS_DEPLOY_ROLE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend Directory Structure&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   backend/
   ├── template.yaml
   ├── lambda_function.py
   ├── requirements.txt
   └── tests/
       ├── test_lambda.py
       └── __init__.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;requirements.txt&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   pytest==7.4.0
   pytest-cov==4.1.0
   moto==4.1.5
   boto3==1.26.118
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test Isolation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests run in a separate job before deployment&lt;/li&gt;
&lt;li&gt;Deployment only proceeds if tests pass (&lt;code&gt;needs: test&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses AWS IAM Role for credentials (not access keys)&lt;/li&gt;
&lt;li&gt;Minimal required permissions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs &lt;code&gt;sam validate&lt;/code&gt; to check template syntax&lt;/li&gt;
&lt;li&gt;Uses containerized builds for consistency&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outputs API endpoint after deployment&lt;/li&gt;
&lt;li&gt;Generates code coverage reports&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimizations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caches Python dependencies between runs&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--no-fail-on-empty-changeset&lt;/code&gt; to avoid failures when no changes detected&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advanced Options
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment-Specific Deployments&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
       &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
           &lt;span class="na"&gt;stack_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-dev&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
           &lt;span class="na"&gt;stack_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-staging&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
           &lt;span class="na"&gt;stack_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual Approval for Production&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;deploy-prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
     &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
     &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
     &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait for approval&lt;/span&gt;
         &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trstringer/manual-approval@v1&lt;/span&gt;
         &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
         &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;integration-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
     &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
     &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run integration tests&lt;/span&gt;
         &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
           &lt;span class="s"&gt;pip install requests&lt;/span&gt;
           &lt;span class="s"&gt;python -m pytest integration_tests/ -v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**CI CD pipelines. &lt;/p&gt;

&lt;h1&gt;
  
  
  Frontend CI/CD Pipeline for S3 and CloudFront
&lt;/h1&gt;

&lt;p&gt;Here's a secure GitHub Actions workflow that will automatically deploy your frontend changes to S3 and invalidate the CloudFront cache without exposing any credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;.github/workflows/frontend-ci-cd.yml&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Frontend CI/CD Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!.github/workflows/backend-*.yml'&lt;/span&gt;  &lt;span class="c1"&gt;# Exclude backend workflows&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-resume-bucket-name&lt;/span&gt;
  &lt;span class="na"&gt;CLOUDFRONT_DIST_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-distribution-id&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to S3 and Invalidate CloudFront&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_FRONTEND_DEPLOY_ROLE }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies (if using npm)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exists('package.json')&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build project (if needed)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exists('package.json')&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sync files to S3&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 sync . s3://${{ env.S3_BUCKET }} \&lt;/span&gt;
            &lt;span class="s"&gt;--delete \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude '.git/*' \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude '.github/*' \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude '*.yml' \&lt;/span&gt;
            &lt;span class="s"&gt;--cache-control "max-age=31536000,public"  # Cache static assets&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create CloudFront invalidation&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws cloudfront create-invalidation \&lt;/span&gt;
            &lt;span class="s"&gt;--distribution-id ${{ env.CLOUDFRONT_DIST_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--paths "/*"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Output website URL&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Website deployed to: https://$(aws cloudfront get-distribution \&lt;/span&gt;
            &lt;span class="s"&gt;--id ${{ env.CLOUDFRONT_DIST_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--query 'Distribution.DomainName' \&lt;/span&gt;
            &lt;span class="s"&gt;--output text)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Required Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a dedicated IAM role&lt;/strong&gt; for frontend deployments with these permissions:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;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;"Allow"&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:PutObject"&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:GetObject"&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:ListBucket"&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="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:::your-resume-bucket-name"&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:::your-resume-bucket-name/*"&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;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;"Allow"&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;"cloudfront:CreateInvalidation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="s2"&gt;"cloudfront:GetDistribution"&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="s2"&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;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;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add these GitHub Secrets&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS_FRONTEND_DEPLOY_ROLE&lt;/code&gt;: ARN of your IAM role&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLOUDFRONT_DIST_ID&lt;/code&gt;: Your CloudFront distribution ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S3_BUCKET&lt;/code&gt;: Your S3 bucket name&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   frontend-repo/
   ├── index.html
   ├── styles/
   │   └── main.css
   ├── scripts/
   │   └── visitor-counter.js
   └── .github/
       └── workflows/
           └── frontend-ci-cd.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never store credentials in code&lt;/strong&gt; - Use GitHub Secrets and IAM roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege principle&lt;/strong&gt; - Only grant required S3/CloudFront permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclude sensitive files&lt;/strong&gt; - The workflow excludes &lt;code&gt;.git/&lt;/code&gt; and &lt;code&gt;.github/&lt;/code&gt; from sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use OIDC&lt;/strong&gt; - GitHub's OpenID Connect for temporary credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advanced Configuration Options
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment-specific deployments&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
       &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
           &lt;span class="na"&gt;s3_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-staging&lt;/span&gt;
           &lt;span class="na"&gt;cloudfront_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ABC123&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
           &lt;span class="na"&gt;s3_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume-prod&lt;/span&gt;
           &lt;span class="na"&gt;cloudfront_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;XYZ789&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cache control headers&lt;/strong&gt; for different file types:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload HTML files&lt;/span&gt;
     &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
       &lt;span class="s"&gt;aws s3 sync . s3://${{ env.S3_BUCKET }} \&lt;/span&gt;
         &lt;span class="s"&gt;--exclude "*" \&lt;/span&gt;
         &lt;span class="s"&gt;--include "*.html" \&lt;/span&gt;
         &lt;span class="s"&gt;--cache-control "max-age=3600,public"&lt;/span&gt;

   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload assets&lt;/span&gt;
     &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
       &lt;span class="s"&gt;aws s3 sync ./assets s3://${{ env.S3_BUCKET }}/assets \&lt;/span&gt;
         &lt;span class="s"&gt;--cache-control "max-age=31536000,public"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Preview deployments&lt;/strong&gt; for pull requests:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
   &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;deploy-preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
       &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to preview bucket&lt;/span&gt;
           &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync . s3://resume-preview-${{ github.event.number }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cloud</category>
      <category>awschallenge</category>
    </item>
    <item>
      <title>Job apply strategies</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Fri, 15 Aug 2025 15:52:46 +0000</pubDate>
      <link>https://dev.to/zach_beecher/job-apply-strategies-3l7p</link>
      <guid>https://dev.to/zach_beecher/job-apply-strategies-3l7p</guid>
      <description>&lt;p&gt;Phased Approach: Breaking it into preparation, daily execution, application crafting, and long-term engagement makes it digestible and repeatable.&lt;/p&gt;

&lt;p&gt;Specificity: You’re not just saying “optimize LinkedIn”, tools, and even suggested headlines. That’s gold.&lt;/p&gt;

&lt;p&gt;Realistic Volume: 30–50 applications/week is ambitious but achievable with batching and automation.&lt;/p&gt;

&lt;p&gt;AI with Integrity: You’re using AI to assist, not replace authenticity. That’s the right mindset.&lt;/p&gt;

&lt;p&gt;Networking Strategy: You’re not afraid to reach out, and you understand the power of warm intros and referrals.&lt;/p&gt;

&lt;p&gt;💡 Suggested Enhancements&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automate Job Tracking
Instead of manually saving PDFs and updating spreadsheets, consider a script or tool that:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Scrapes job descriptions and saves them with structured filenames&lt;/p&gt;

&lt;p&gt;Logs applications into a CSV or Notion database automatically&lt;/p&gt;

&lt;p&gt;Flags duplicate applications or expired listings&lt;/p&gt;

&lt;p&gt;I can help you build this in Bash or Python if you're interested.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Portfolio Integration
Mention your GitHub, blog, or portfolio site in your resume and LinkedIn. Better yet:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add a “Projects” section to LinkedIn with links&lt;/p&gt;

&lt;p&gt;Use GitHub README files to explain project goals, tech stack, and outcomes&lt;/p&gt;

&lt;p&gt;Create a simple static site (e.g., with AWS S3 + CloudFront) to showcase your resume and projects&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interview Prep Layer
Add a Phase 5: Interview Readiness&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Maintain a doc with common behavioral questions and STAR-format answers&lt;/p&gt;

&lt;p&gt;Practice technical questions daily (e.g., LeetCode, Cloud Guru labs)&lt;/p&gt;

&lt;p&gt;Record yourself answering questions to refine delivery&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Weekly Review Ritual
Every Sunday, review:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How many applications you submitted&lt;/p&gt;

&lt;p&gt;Which ones got responses&lt;/p&gt;

&lt;p&gt;What patterns you’re noticing (e.g., certain titles or companies responding more)&lt;/p&gt;

&lt;p&gt;What you learned or built that week&lt;/p&gt;

&lt;p&gt;This keeps you intentional and adaptive.&lt;/p&gt;

&lt;p&gt;🧠 Final Thought&lt;br&gt;
You’re not just applying for jobs—you’re building a system that compounds. Every tailored resume, every outreach message, every saved job description is a brick in the foundation of your next opportunity.&lt;/p&gt;

&lt;p&gt;a Notion template, a Bash automation suite, or even a personalized job search dashboard. Just say the word.&lt;/p&gt;

&lt;p&gt;notion template&lt;br&gt;
Let’s build you a Notion template that mirrors your strategic job search plan—organized, actionable, and easy to maintain. This will help you track applications, prep for interviews, and stay focused on learning and networking.&lt;/p&gt;

&lt;p&gt;🧱 Notion Template Structure&lt;br&gt;
🗂️ Main Dashboard: Job Search HQ&lt;br&gt;
A central page with links to all subpages and a weekly progress tracker.&lt;/p&gt;

&lt;p&gt;📋 1. Job Applications Tracker (Database View)&lt;br&gt;
Column Name Type    Purpose&lt;br&gt;
Company Text    Name of the company&lt;br&gt;
Job Title   Text    Specific role applied for&lt;br&gt;
Date Applied    Date    When you submitted the application&lt;br&gt;
Status  Select  Applied, Interviewing, Rejected, Offer&lt;br&gt;
Job Description PDF File    Upload saved job description&lt;br&gt;
Resume Version Used Text    Which tailored resume you used&lt;br&gt;
Cover Letter Used   Text    Which cover letter version&lt;br&gt;
Contacted Recruiter?    Checkbox    Did you reach out?&lt;br&gt;
Notes   Text    Interview prep, follow-up, etc.&lt;br&gt;
Views:&lt;/p&gt;

&lt;p&gt;Kanban by Status&lt;/p&gt;

&lt;p&gt;Calendar by Date Applied&lt;/p&gt;

&lt;p&gt;Table view for sorting/filtering&lt;/p&gt;

&lt;p&gt;🧠 2. Interview Prep Vault&lt;br&gt;
Behavioral Questions (toggle list or database)&lt;/p&gt;

&lt;p&gt;STAR-format answers&lt;/p&gt;

&lt;p&gt;Common prompts like “Tell me about a time you failed”&lt;/p&gt;

&lt;p&gt;Technical Questions&lt;/p&gt;

&lt;p&gt;Categorized by topic (e.g., AWS, Bash, Python)&lt;/p&gt;

&lt;p&gt;Include links to practice problems or your own notes&lt;/p&gt;

&lt;p&gt;Company-Specific Prep&lt;/p&gt;

&lt;p&gt;Subpages for each company with tailored prep notes&lt;/p&gt;

&lt;p&gt;📚 3. Learning &amp;amp; Projects Log&lt;br&gt;
Column Name Type    Purpose&lt;br&gt;
Topic   Text    e.g., Terraform, IAM Policies&lt;br&gt;
Type    Select  Course, Blog, Project, Cert&lt;br&gt;
Date Started    Date    When you began&lt;br&gt;
Status  Select  In Progress, Completed&lt;br&gt;
Notes   Text    What you learned, links, etc.&lt;br&gt;
💬 4. Networking Tracker&lt;br&gt;
Column Name Type    Purpose&lt;br&gt;
Name    Text    Person you contacted&lt;br&gt;
Role    Text    Recruiter, Engineer, etc.&lt;br&gt;
Company Text    Where they work&lt;br&gt;
Date Contacted  Date    When you reached out&lt;br&gt;
Follow-Up Date  Date    When to follow up&lt;br&gt;
Notes   Text    What you discussed, outcome&lt;br&gt;
📈 5. Weekly Review Journal&lt;br&gt;
Template with prompts:&lt;/p&gt;

&lt;p&gt;How many jobs did I apply to?&lt;/p&gt;

&lt;p&gt;What responses did I get?&lt;/p&gt;

&lt;p&gt;What did I learn this week?&lt;/p&gt;

&lt;p&gt;What will I improve next week?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cloud eng/ dev Daily job search guide from a recruiter</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Fri, 15 Aug 2025 15:37:35 +0000</pubDate>
      <link>https://dev.to/zach_beecher/cloud-eng-dev-daily-job-search-guide-from-a-recruiter-3476</link>
      <guid>https://dev.to/zach_beecher/cloud-eng-dev-daily-job-search-guide-from-a-recruiter-3476</guid>
      <description>&lt;p&gt;Daily Job Search Strategy&lt;br&gt;
Step    Action  Notes&lt;br&gt;
1️⃣ Apply for 30 jobs/day   Use batching: 10 in morning, 10 midday, 10 evening. Use saved searches to speed things up.&lt;br&gt;
2️⃣ Use Google strategically    Try "site:careers.company.com job title" or "intitle:job job title location" to find hidden gems.&lt;br&gt;
3️⃣ Save job descriptions as PDFs   Use Print → Save as PDF or browser extensions. Organize by folder: Applied/CompanyName/JobTitle.pdf.&lt;br&gt;
4️⃣ Be specific with job titles Search for exact roles like “Cloud Support Engineer” or “DevOps Intern” rather than generic terms.&lt;br&gt;
5️⃣ Use LinkedIn’s “&amp;lt;10 applicants” filter    Prioritize these for higher visibility. Combine with “Easy Apply” for speed.&lt;br&gt;
6️⃣ Internships &amp;amp; volunteering  Look for open-source projects, nonprofit tech roles, or startup gigs. Experience is experience.&lt;br&gt;
7️⃣ Network intentionally   Message alumni, attend virtual meetups, comment on posts. Ask for advice, not jobs.&lt;br&gt;
8️⃣ Tailor your resume  Use keywords from the job description. Highlight relevant projects and skills.&lt;br&gt;
9️⃣ Write a compelling cover letter Focus on how you solve problems, not just what you’ve done. Keep it concise and specific.&lt;br&gt;
🔟    Optimize LinkedIn   Use a strong headline, detailed experience, and a custom URL. Ask for endorsements.&lt;br&gt;
1️⃣1️⃣  Commit to learning  Take short courses, build projects, or write blog posts. Show growth and curiosity.&lt;/p&gt;

&lt;p&gt;Setup google alerts with scripts running on topics like new job postings on your favorite companies. Be organized, keep a spreadsheet and do followups 1-2 weeks after apply. Find a recruiter at said company and message them.&lt;/p&gt;

&lt;p&gt;Use a spreadsheet or Notion board with columns like:&lt;/p&gt;

&lt;p&gt;Company&lt;/p&gt;

&lt;p&gt;Job Title&lt;/p&gt;

&lt;p&gt;Date Applied&lt;/p&gt;

&lt;p&gt;Status (Applied, Interviewing, Rejected)&lt;/p&gt;

&lt;p&gt;PDF Link&lt;/p&gt;

&lt;p&gt;Notes (e.g., who you networked with)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Bash script to generate 25 files, a resource, a guide, challenge yourself</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Fri, 15 Aug 2025 15:33:11 +0000</pubDate>
      <link>https://dev.to/zach_beecher/bash-script-to-generate-25-files-a-resource-a-guide-challenge-yourself-1b1m</link>
      <guid>https://dev.to/zach_beecher/bash-script-to-generate-25-files-a-resource-a-guide-challenge-yourself-1b1m</guid>
      <description>&lt;p&gt;**The Challenge&lt;br&gt;
**Write a Bash script based on the following requirements:&lt;/p&gt;

&lt;p&gt;Creates 25 empty (0 KB) files. (Hint: Use the touch command.)&lt;/p&gt;

&lt;p&gt;The file names should be , , , and so on.&lt;/p&gt;

&lt;p&gt;Design the script so that each time you run it, it creates the next batch of 25 files with increasing numbers starting with the last or maximum number that already exists.&lt;br&gt;
Do not hard code these numbers. You need to generate them by using automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the script. Display a long list of the directory and its contents to validate that the script created the expected files.
&lt;/h2&gt;

&lt;p&gt;At first glance I'm thinking I need a way to check the system for current number of files, so I start searching: &lt;/p&gt;

&lt;p&gt;I'll need a dynamic loop, checking system files for numbers so I search for lookup methods, grep comes up and find comes up as well: &lt;/p&gt;

&lt;p&gt;[Link(&lt;a href="https://stackoverflow.com/questions/24655436/how-can-i-find-a-file-directory-that-could-be-anywhere-on-linux-command-line)" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/24655436/how-can-i-find-a-file-directory-that-could-be-anywhere-on-linux-command-line)&lt;/a&gt;] To get rid of permission errors (and such), you can redirect stderr to nowhere&lt;/p&gt;

&lt;p&gt;find / -name "something" 2&amp;gt;/dev/null&lt;/p&gt;

&lt;p&gt;I've heard of this null thing before, and it's pretty neat. I found an interesting reason to use this, the 2&amp;gt; is a bash syntax to tell the system to redirect stderr (2) so all errors will be ignored, and the carrot &amp;gt; redirects stdout. I was doing some bandit over the wire hacking, and often times to get around the permissions errors, you chmod 400 the file if you have permission if not copy file and make a tmp directory and paste into that directory and work on it there. &lt;/p&gt;

&lt;p&gt;Here's the final code (we can walk through it as a team): &lt;/p&gt;

&lt;p&gt;`#!/bin/bash&lt;/p&gt;

&lt;p&gt;yourName="zach"&lt;br&gt;
logFile="file_creation.log"&lt;/p&gt;

&lt;h1&gt;
  
  
  Get list of existing files matching the pattern
&lt;/h1&gt;

&lt;p&gt;existingFiles=$(ls ${yourName}[0-9]* 2&amp;gt;/dev/null)&lt;/p&gt;

&lt;h1&gt;
  
  
  Extract the highest number from existing files
&lt;/h1&gt;

&lt;p&gt;if [ -z "$existingFiles" ]; then&lt;br&gt;
  maxNum=0&lt;br&gt;
else&lt;br&gt;
  maxNum=$(echo "$existingFiles" | grep -oP "${yourName}\K[0-9]+" | sort -n | tail -1)&lt;br&gt;
fi&lt;/p&gt;

&lt;h1&gt;
  
  
  Timestamp for this batch
&lt;/h1&gt;

&lt;p&gt;timestamp=$(date '+%Y-%m-%d %H:%M:%S')&lt;br&gt;
echo "[$timestamp] Creating files starting from ${yourName}$((maxNum + 1)) to ${yourName}$((maxNum + 25))" &amp;gt;&amp;gt; "$logFile"&lt;/p&gt;

&lt;h1&gt;
  
  
  Create 25 new files and log each one
&lt;/h1&gt;

&lt;p&gt;for ((i=1; i&amp;lt;=25; i++)); do&lt;br&gt;
  newNum=$((maxNum + i))&lt;br&gt;
  fileName="${yourName}${newNum}"&lt;br&gt;
  touch "$fileName"&lt;br&gt;
  echo "[$timestamp] Created $fileName" &amp;gt;&amp;gt; "$logFile"&lt;br&gt;
done&lt;/p&gt;

&lt;h1&gt;
  
  
  Show the results
&lt;/h1&gt;

&lt;p&gt;echo "Created files:"&lt;br&gt;
ls -l ${yourName}[0-9]* | tail -25&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;h1&gt;
  
  
  !/bin/bash -&amp;gt; tells system to use Bash to interpret the code
&lt;/h1&gt;

&lt;p&gt;yourName="zach"&lt;br&gt;
logFile="file_creation.log"&lt;/p&gt;

&lt;p&gt;--&amp;gt; setting up basic static variables: &lt;/p&gt;

&lt;p&gt;existingFiles=$(ls ${yourName}[0-9]* 2&amp;gt;/dev/null)&lt;br&gt;
--&amp;gt; 2&amp;gt;/dev/null suppresses errors if no files exist yet. Lists all files match pattern.&lt;/p&gt;

&lt;p&gt;if [ -z "$existingFiles" ]; then&lt;br&gt;
  maxNum=0&lt;br&gt;
else&lt;br&gt;
  maxNum=$(echo "$existingFiles" | grep -oP "${yourName}\K[0-9]+" | sort -n | tail -1)&lt;br&gt;
fi&lt;/p&gt;

&lt;p&gt;--&amp;gt; checks if any matching files exist, if none exist start numbering from 0, If files do exist:&lt;/p&gt;

&lt;p&gt;grep -oP "${yourName}\K[0-9]+" extracts just the number part from filenames.&lt;/p&gt;

&lt;p&gt;sort -n sorts numerically.&lt;/p&gt;

&lt;p&gt;tail -1 grabs the highest number so we can start from there.&lt;/p&gt;

&lt;p&gt;timestamp=$(date '+%Y-%m-%d %H:%M:%S')&lt;br&gt;
--&amp;gt; captures date saves for logging&lt;/p&gt;

&lt;p&gt;echo "[$timestamp] Creating files starting from ${yourName}$((maxNum + 1)) to ${yourName}$((maxNum + 25))" &amp;gt;&amp;gt; "$logFile"&lt;br&gt;
--&amp;gt; logs batch file_creation.log&lt;/p&gt;

&lt;p&gt;for ((i=1; i&amp;lt;=25; i++)); do&lt;br&gt;
  newNum=$((maxNum + i))&lt;br&gt;
  fileName="${yourName}${newNum}"&lt;br&gt;
  touch "$fileName"&lt;br&gt;
  echo "[$timestamp] Created $fileName" &amp;gt;&amp;gt; "$logFile"&lt;br&gt;
done&lt;br&gt;
--&amp;gt; meat of the operation here, 25 loops calculates next num +1 - creates files with touch, logs them with time stamp&lt;/p&gt;

&lt;p&gt;echo "Created files:"&lt;br&gt;
ls -l ${yourName}[0-9]* | tail -25&lt;br&gt;
--&amp;gt; displays last 25 files (can use head as well to see the first 25 but doesn't add much value. &lt;/p&gt;

&lt;p&gt;Now that's all of the code. It's a lot to take in as a beginner bash scripter. One thing to take note of is the programmatic approach to solve this, you can't hard code the for loop and just have it work. It needs to do a few things so good to research grep, find, sort, tail functions in bash. &lt;/p&gt;

&lt;p&gt;Wish you all the best,&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AWS VPC and EC2 basic setup (manual)</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Tue, 12 Aug 2025 20:34:36 +0000</pubDate>
      <link>https://dev.to/zach_beecher/aws-vpc-and-ec2-basic-setup-manual-59f4</link>
      <guid>https://dev.to/zach_beecher/aws-vpc-and-ec2-basic-setup-manual-59f4</guid>
      <description>&lt;p&gt;Learning cloud I wanted a place I can quickly find a step by step guide to launch VPC and EC2: &lt;/p&gt;

&lt;p&gt;Quick AWS Management Console steps (visual, good for first-time use)&lt;/p&gt;

&lt;p&gt;1 — Overview&lt;br&gt;
A custom VPC (10.0.0.0/16).&lt;br&gt;
A public subnet (10.0.1.0/24) with auto-assign public IPs.&lt;br&gt;
An Internet Gateway (IGW) and a route for 0.0.0.0/0 → IGW.&lt;br&gt;
A security group allowing SSH (22) from your IP and HTTP (80) from anywhere (optional).&lt;br&gt;
A key pair to SSH.&lt;br&gt;
One EC2 instance (Amazon Linux 2 example) in the public subnet.&lt;br&gt;
2 — Console steps (fastest visually)&lt;br&gt;
Sign in to the AWS Console and pick a region (top-right).&lt;br&gt;
Go to VPC service → Your VPCs → Create VPC:&lt;br&gt;
Name: my-vpc&lt;br&gt;
IPv4 CIDR block: 10.0.0.0/16&lt;br&gt;
Create.&lt;br&gt;
Enable DNS hostnames (if not auto-enabled): select VPC → Actions → Edit DNS hostnames → Enable.&lt;br&gt;
Create subnet: Subnets → Create subnet:&lt;br&gt;
VPC: my-vpc&lt;br&gt;
Name: public-subnet-1&lt;br&gt;
IPv4 CIDR: 10.0.1.0/24&lt;br&gt;
AZ: pick one (e.g., us-east-1a)&lt;br&gt;
Create.&lt;br&gt;
Then, with subnet selected: Actions → Modify auto-assign IP settings → enable Auto-assign public IPv4 address.&lt;br&gt;
Internet Gateway: Internet Gateways → Create internet gateway → name my-igw → Create, then Attach to my-vpc.&lt;br&gt;
Route table: Route Tables → create a route table for my-vpc (name public-rt). Edit routes: add route 0.0.0.0/0 → target: my-igw. Then Subnet Associations → Associate public-subnet-1.&lt;br&gt;
Security group: EC2 → Network &amp;amp; Security → Security Groups → Create security group:&lt;br&gt;
Name: my-sg, VPC: my-vpc&lt;br&gt;
Inbound rules: SSH (TCP 22) — Source: Your IP (use “My IP” option); HTTP (TCP 80) — Source: 0.0.0.0/0 (if you want web).&lt;br&gt;
Create.&lt;br&gt;
Key pair: EC2 → Key Pairs → Create key pair → give name my-key → download .pem and store securely; chmod 400 my-key.pem.&lt;br&gt;
Launch EC2:&lt;br&gt;
EC2 → Instances → Launch instances.&lt;br&gt;
Choose an AMI (e.g., Amazon Linux 2 or Ubuntu — note username differs).&lt;br&gt;
Instance type: t3.micro or t2.micro (free-tier eligible if your account qualifies).&lt;br&gt;
Under Network settings: VPC: my-vpc, Subnet: public-subnet-1, Auto-assign Public IP: enabled.&lt;br&gt;
Select Security group: my-sg.&lt;br&gt;
Key pair: my-key.&lt;br&gt;
Launch.&lt;br&gt;
After instance status is running, grab the Public IP and SSH:&lt;br&gt;
Amazon Linux 2 → ssh -i my-key.pem ec2-user@PUBLIC_IP&lt;br&gt;
Ubuntu → ssh -i my-key.pem ubuntu@PUBLIC_IP&lt;/p&gt;

&lt;p&gt;Under the user settings on Ec2 add this bash script to pull in a website template if you want to show your friends.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#!/bin/bash&lt;br&gt;
sudo yum update -y&lt;br&gt;
sudo yum upgrade -y&lt;br&gt;
sudo yum install httpd -y&lt;br&gt;
sudo yum install unzip -y&lt;br&gt;
sudo wget --no-check-certificate 'https://drive.google.com/uc?export=download&amp;amp;id=1wO2DwQ5KgiBeP9dg51H6hw95et89WlUo' -O tempsite.zip&lt;br&gt;
sudo unzip tempsite.zip -d /var/www/html&lt;br&gt;
sudo mv /var/www/html/tempsite/* /var/www/html/&lt;br&gt;
sudo systemctl enable httpd&lt;br&gt;
sudo systemctl start httpd&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo5265mtss8ajqa3x1f2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo5265mtss8ajqa3x1f2.webp" alt=" " width="800" height="600"&gt;&lt;/a&gt; courtesy of: &lt;a href="https://syang.substack.com/p/think-of-aws-vpc-like-a-parking-lot" rel="noopener noreferrer"&gt;https://syang.substack.com/p/think-of-aws-vpc-like-a-parking-lot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OFC There's a faster way to launch VPC and Ec2 but this is breaking down each step and it's important to know what subnets are doing (10.0.0.0/16 (~65k IPs, -5 for amazon), internet gateway, Routes, Ec2 runs on VPC so if you know Ec2 is having issues, check the routes, subnets, do they have access to Internet gateway (auto resolved hosts or assigned Public IP) - go through the check list in order. &lt;/p&gt;

&lt;p&gt;Fascinating stuff. Can't wait to learn more &lt;/p&gt;

</description>
      <category>cloud</category>
    </item>
    <item>
      <title>Bash commands for AWS Cloud engineers</title>
      <dc:creator>zach beecher</dc:creator>
      <pubDate>Tue, 12 Aug 2025 19:48:08 +0000</pubDate>
      <link>https://dev.to/zach_beecher/bash-commands-for-aws-cloud-engineers-351k</link>
      <guid>https://dev.to/zach_beecher/bash-commands-for-aws-cloud-engineers-351k</guid>
      <description>&lt;p&gt;Learning AWS cloud I stumbled onto Bash commands which got me thinking - which ones are key to a cloud engineer's toolbox? &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Wanna quickly edit EC2 instances and metadata? &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
`#!/bin/bash&lt;br&gt;
echo "Fetching EC2 instances and their tags..."&lt;/p&gt;

&lt;p&gt;aws ec2 describe-instances \&lt;br&gt;
  --query 'Reservations[&lt;em&gt;].Instances[&lt;/em&gt;].[InstanceId,Tags]' \&lt;br&gt;
  --output table`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;are wildcards so it'll pull down all reserved instances &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;Wanna see S3 bucket backups? &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
`#!/bin/bash&lt;br&gt;
SOURCE_BUCKET="my-source-bucket"&lt;br&gt;
DEST_BUCKET="my-backup-bucket"&lt;br&gt;
DATE=$(date +%Y-%m-%d)&lt;/p&gt;

&lt;p&gt;echo "Backing up $SOURCE_BUCKET to $DEST_BUCKET/$DATE..."&lt;/p&gt;

&lt;p&gt;aws s3 sync s3://$SOURCE_BUCKET s3://$DEST_BUCKET/$DATE --storage-class STANDARD_IA&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Daily backup and version / archive storage&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Wanna rotate IAM access keys? &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
`#!/bin/bash&lt;br&gt;
USER_NAME="your-iam-user"&lt;/p&gt;

&lt;p&gt;echo "Creating new access key..."&lt;br&gt;
NEW_KEY=$(aws iam create-access-key --user-name $USER_NAME)&lt;/p&gt;

&lt;p&gt;echo "Disabling old access keys..."&lt;br&gt;
OLD_KEYS=$(aws iam list-access-keys --user-name $USER_NAME --query 'AccessKeyMetadata[*].AccessKeyId' --output text)&lt;/p&gt;

&lt;p&gt;for KEY in $OLD_KEYS; do&lt;br&gt;
  aws iam update-access-key --user-name $USER_NAME --access-key-id $KEY --status Inactive&lt;br&gt;
done&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Rotate keys daily to ensure security is hygenic&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Wanna check the CPU utillization of EC2? &lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;`#!/bin/bash&lt;br&gt;
INSTANCE_ID="i-xxxxxxxxxxxxxxxxx"&lt;br&gt;
REGION="us-west-2"&lt;/p&gt;

&lt;p&gt;aws cloudwatch get-metric-statistics \&lt;br&gt;
  --namespace AWS/EC2 \&lt;br&gt;
  --metric-name CPUUtilization \&lt;br&gt;
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \&lt;br&gt;
  --start-time $(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ) \&lt;br&gt;
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \&lt;br&gt;
  --period 300 \&lt;br&gt;
  --statistics Average \&lt;br&gt;
  --region $REGION \&lt;br&gt;
  --output table&lt;br&gt;
`&lt;br&gt;
Wanna kill unused EC2s by tag (save dat money)&lt;/p&gt;

&lt;p&gt;`#!/bin/bash&lt;br&gt;
TAG_KEY="Environment"&lt;br&gt;
TAG_VALUE="Dev"&lt;/p&gt;

&lt;p&gt;echo "Finding EC2 instances with tag $TAG_KEY=$TAG_VALUE..."&lt;/p&gt;

&lt;p&gt;INSTANCE_IDS=$(aws ec2 describe-instances \&lt;br&gt;
  --filters "Name=tag:$TAG_KEY,Values=$TAG_VALUE" "Name=instance-state-name,Values=running" \&lt;br&gt;
  --query "Reservations[&lt;em&gt;].Instances[&lt;/em&gt;].InstanceId" \&lt;br&gt;
  --output text)&lt;/p&gt;

&lt;p&gt;echo "Terminating instances: $INSTANCE_IDS"&lt;br&gt;
aws ec2 terminate-instances --instance-ids $INSTANCE_IDS&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Cleans up dev env automagically &lt;/p&gt;

&lt;p&gt;Let me know which Bash scripts you reccomend, or use as your daily drivers? &lt;/p&gt;

</description>
      <category>cloud</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
