<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://blog.traviseno.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 22:27:31 GMT</lastBuildDate><atom:link href="https://blog.traviseno.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[My "Cloud Resume Challenge" Journey]]></title><description><![CDATA[Hello there! 
I recently completed my resume website as part of the Cloud Resume Challenge.
The Cloud Resume Challenge, created by Forrest Brazeal, outlines 16 steps to build a serverless website on AWS. The beauty of the challenge is that Forrest do...]]></description><link>https://blog.traviseno.com/my-cloud-resume-challenge-journey</link><guid isPermaLink="true">https://blog.traviseno.com/my-cloud-resume-challenge-journey</guid><category><![CDATA[AWS]]></category><category><![CDATA[serverless]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[cloud-resume-challenge]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Travis Eno]]></dc:creator><pubDate>Mon, 18 Jul 2022 19:02:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658101078124/GRTu5QOmr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello there! </p>
<p>I recently completed my resume website as part of the Cloud Resume Challenge.</p>
<p>The Cloud Resume Challenge, created by Forrest Brazeal, outlines 16 steps to build a serverless website on AWS. The beauty of the challenge is that Forrest does NOT specify how to complete each step. Instead, you are left to research and implement each objective on your own. The benefit of this approach is that you will be forced to learn. Jumping headfirst into the fray, you wrestle with each step, collecting battle scars and knowledge along the way.</p>
<p>I was excited to start this challenge for a few reasons. Firstly, I have been meaning to create a personal website for a while. Secondly, I recently passed the <a target="_blank" href="https://aws.amazon.com/certification/certified-sysops-admin-associate/">AWS SysOps Administrator - Associate</a> certification exam. And finally, my favorite way to learn anything new is to build something.</p>
<p>Rather than bore you with a detailed account of all 16 steps, I thought I'd highlight some of the lessons I learned along the way, including a few interesting tools. For context, the challenge can be broken down into three phases:</p>
<ol>
<li>Creating the frontend website</li>
<li>Creating the backend API</li>
<li>Automating <em>all</em> the things</li>
</ol>
<p>With that, let's get to it!</p>
<h3 id="heading-phase-1-front-end">Phase 1: Front End</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657252792827/BgT6nYbit.png" alt="FrontEnd.png" /></p>
<p>I created a simple HTML/CSS webpage and stored it in an S3 bucket. The bucket hosts the website, with a CloudFront distribution in front to provide secure communication using HTTPS. I used Route 53 to provide DNS and register my domain name. Route 53 then points traffic from my domain to the CloudFront distribution.</p>
<p><strong>Design</strong></p>
<p>Full disclosure, I am <em>not</em> a web designer.
I admire those who can execute artistic ideas with code, but I'm not there yet.
I'm the guy who heard "black goes with everything" and just ran with it.
At one point, I had an entire living room full of <em>all</em> black IKEA furniture, which looked exactly as ridiculous as you'd imagine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657948642059/2uHua0AD_.jpg" alt="mistakesweremade.jpg" class="image--center mx-auto" /></p>
<p>It's a work in progress. Anyways...</p>
<p>Despite my lack of talent, I decided to piece together bits of other websites I like into my own "design." 
Using <a target="_blank" href="https://www.w3schools.com/w3css/default.asp">W3.CSS</a> to do the heavy lifting, I cobbled together a simple layout that's also mobile-friendly.
I see a lot of detractors of W3.CSS, but I really liked it for this use case. It helped me make sense of the tangled mess known as "frontend web development." (Shots fired!)</p>
<p>It's not perfect, and I'm sure I could have just used a template.
But where's the fun in that?
Most importantly, I learned a lot in the process. Hats off to all the web designers out there; you make it look easy.</p>
<p><strong>Performance</strong></p>
<p>I love optimizing code to speed it up, so when I found www.webpagetest.org, I had to try it.
This site provides feedback on your page's performance, security, and accessibility.
Along with scores for each area, they list improvements you can try that are specific to your website. I learned a few neat tricks from those suggestions, like how to swap out the CDN version of Font Awesome and host just the .svg files with the website. It was a nice speed boost.
After several iterations, I trimmed the page loading speed from 3.1 to 1.3 seconds.
An improvement of  58%! 
Technically this isn't part of the challenge, but what's a side project without side quests?</p>
<h3 id="heading-phase-2-back-end">Phase 2: Back End</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657252837887/lBYc_WFlo.png" alt="Backend.png" /></p>
<p>I needed to build a simple API for the backend portion to keep the page views count. This involves an API Gateway that uses a Lambda function to communicate with a DynamoDB table. The API itself is called from a bit of JavaScript on the frontend webpage, which increments the count and returns the new total.</p>
<p><strong>Tradeoffs</strong></p>
<p>I chose to use a single Lambda function to update DynamoDB. You could create separate read/write functions, but then you pay for each individually. This is because DynamoDB billing is calculated in read and write "request units." By requesting a return value with the update call to DynamoDB, I get the new total and only pay for the write capacity unit. Nice!</p>
<p>After that, I encountered a few "exciting" problems while setting up CORS.
When using a SAM template to create a serverless backend, it defaults to a REST API with a "proxy integration" to the Lambda function that powers it. Additionally, the API creates a MOCK integration to process "preflight options" calls that can only allow a single domain  (<a target="_blank" href="https://cors.serverlessland.com/">As mentioned here</a>).
Those are words, but what does that mean? Simply put, you can only configure <em>one</em> outside origin that can call the API from within a browser.</p>
<p>Suppose I set up CORS with the apex domain <code>traviseno.com</code> and not the subdomain <code>www.traviseno.com</code>. In that case, it will return an error when the user enters the subdomain address <code>www.traviseno.com</code>. <a target="_blank" href="https://aws.amazon.com/blogs/networking-and-content-delivery/solving-dns-zone-apex-challenges-with-third-party-dns-providers-using-aws/">Around the web</a>, there are a variety of suggestions to work around this (Using * to open up the CORS configuration to ALL origins being the worst of them).
Instead, I created another S3 bucket and CloudFront distribution. This empty bucket responds with 301 "Moved Permanently" HTTP response codes. The users are then forwarded to the website's primary bucket that hosts the frontend files.
After re-configuring the Route 53 A record, it looked something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657411554402/50_xQ9PtB.png" alt="Untitled Diagram.png" class="image--center mx-auto" /></p>
<p>With that in place, I avoid leaving the CORS configuration completely open.</p>
<p><strong>Tests</strong></p>
<p>I wrote a simple end-to-end test to validate the API after a new deployment. It calls the API endpoint and validates a few attributes of the return data. If the tests pass, then the deployment is successful. If the tests fail for any reason, I can "fail forward" by fixing the problem and re-deploying until the tests pass. </p>
<p>This could result in the API being down for a bit as I troubleshoot and fix the issue, but I'm comfortable with that. Why? Because as it stands, the frontend website functions independently of the backend API. So all I really lose is a few view counts. To most users, this would be invisible. </p>
<p>The benefit of accepting that risk is reduced complexity. I don't have to approve each change, build a pre-production testing environment, or figure out rollbacks. If I were concerned about keeping an accurate pageview count, it would be easier to just use an existing solution, don't you think?</p>
<h3 id="heading-phase-3-automation">Phase 3: Automation</h3>
<p>Fun fact, I <em>love</em> automating operations work.
It's one of my favorite parts of working as a Site Reliability Engineer (DevOps, if you prefer). The satisfaction one gets from watching a computer execute tasks you previously had to perform manually feels magical.</p>
<p>The project calls for implementing Continuous Integration (CI) and Continuous Delivery (CD) to the frontend site and backend API. In practice, any changes I push to the frontend repository will be automatically synced to the S3 bucket. I must also define my backend API resources in "infrastructure as code" (IaC). Any changes I make to the API will be implemented by changing the IaC template file rather than manually configuring the resources.</p>
<p>I started by setting up GitHub Actions on my frontend repository, where I created a workflow that launches when new code is pushed to the main branch. GitHub Actions runners, which execute the workflow, come bundled with the AWS CLI. For this reason, the workflow uses AWS CLI commands to sync the HTML and CSS files to the S3 bucket. It also uses the CLI command to create an invalidation which removes the stale files in the CloudFront distribution.</p>
<p><strong>The Terraform Conundrum</strong></p>
<p>Moving on to converting my infrastructure into code, I dug into the more popular options available. Terraform seemed like the clear winner here, but I ran into what appears to be a common issue. Finding a safe, secure way to store state files. Running from my local machine, this isn't a problem. But running from GitHub Actions meant I needed to set up something like S3 or Terraform Cloud. I enjoyed using Terraform but didn't want to manage my own state files.</p>
<p>I decided to use AWS SAM/CloudFormation because it automatically manages your state files. When you initialize the SAM template, it sets up an S3 bucket that stores your state data and keeps it in sync as you make changes. It was also lovely having some of the security configurations between services handled for me. I created another GitHub Actions workflow for the backend repository to run the CLI commands to build and deploy this SAM template. I plan to re-visit the Terraform approach in the future, but I am happy with how the SAM template turned out.</p>
<p><strong>The Final Countdown</strong></p>
<p>The final step is to write a blog post about your experience with the challenge...
Oh wait, that's this one!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657946840807/_VYO1S_Wg.jpg" alt="successkid.jpg" class="image--center mx-auto" /></p>
<p>I think that about wraps it up!
You can see the finished product <a target="_blank" href="https://www.traviseno.com/">here</a>.</p>
<p>If you've made it this far, thank you for taking the time to read my story. I'm sure I made this challenge much harder than it had to be through perfectionism, late-night rabbit holes, and "stretch goals." Then again, what fun is taking the easy path?</p>
<p>Thanks to Forrest Brazeal for creating this challenge, which motivated me to start building things in the Cloud. You can find Forrest at https://forrestbrazeal.com/, and you can find out more about the Cloud Resume Challenge <a target="_blank" href="https://cloudresumechallenge.dev/docs/the-challenge/">here</a>.</p>
<p>You can find me on:</p>
<ul>
<li><a target="_blank" href="https://www.traviseno.com">My website</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/travis-eno">Linkedin</a></li>
</ul>
<p>Until next time, don't forget to be awesome.</p>
]]></content:encoded></item></channel></rss>