<?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>Forem: Justin Wheeler</title>
    <description>The latest articles on Forem by Justin Wheeler (@wheeleruniverse).</description>
    <link>https://forem.com/wheeleruniverse</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%2F472468%2Ff653e7a7-e83a-414a-bc7f-b6fd8f033cba.jpg</url>
      <title>Forem: Justin Wheeler</title>
      <link>https://forem.com/wheeleruniverse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/wheeleruniverse"/>
    <language>en</language>
    <item>
      <title>Part 2: From $68/month to $5/month - The "Lite" Deployment Strategy</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Sun, 02 Nov 2025 19:07:10 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/part-2-from-68month-to-5month-the-lite-deployment-strategy-1lah</link>
      <guid>https://forem.com/wheeleruniverse/part-2-from-68month-to-5month-the-lite-deployment-strategy-1lah</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;My Cloud Portfolio Challenge implementation costs $68 per month on Digital Ocean. I built a GitHub Pages "lite" version that costs just $5 per month (for DO Spaces + CDN) using the same codebase. Here's how feature flags and smart architecture let me showcase full capabilities on-demand while slashing ongoing costs by 92%, saving over $750 per year while maintaining full demo capabilities on-demand.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is part of my #CloudGuruChallenge series. It's a follow-up to &lt;a href="https://blog.wheeleruniverse.com/read/cgc-lb-and-cdn" rel="noopener noreferrer"&gt;"Better Late Than Never: Tackling the 2022 Cloud Portfolio Challenge"&lt;/a&gt; - showing how I reduced costs from $68/month to $5/month using the same codebase.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: When Reality Hits Your Wallet
&lt;/h2&gt;

&lt;p&gt;Remember in &lt;a href="https://blog.wheeleruniverse.com/read/cgc-lb-and-cdn" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; when I mentioned those DigitalOcean promotional credits? Yeah, they expired. And that feeling when you realize your portfolio project is costing you &lt;strong&gt;$68 per month&lt;/strong&gt; to keep running? That's... not ideal.&lt;/p&gt;

&lt;p&gt;Let's break down what I was paying for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load Balancer&lt;/strong&gt;: $12 per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 Droplets&lt;/strong&gt; (full-stack servers): $18 per month each = $36 per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valkey Database&lt;/strong&gt;: $15 per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DO Spaces + CDN&lt;/strong&gt;: $5 per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total&lt;/strong&gt;: $68 per month for a portfolio project that &lt;em&gt;might&lt;/em&gt; get viewed once a week during interview seasons.&lt;/p&gt;

&lt;p&gt;That's when I had a thought: &lt;em&gt;What if I could keep the impressive architecture for demos but run at minimal cost 99% of the time?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: GitHub Pages "Lite" Mode
&lt;/h2&gt;

&lt;p&gt;Enter the dual-deployment strategy. Same codebase, two completely different deployment targets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Full Deployment&lt;/strong&gt; (Digital Ocean): All the bells and whistles - AI generation, live voting, cross-session tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lite Deployment&lt;/strong&gt; (GitHub Pages): Static site with gracefully degraded features - local voting, pre-generated images&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The magic? &lt;strong&gt;Feature flags&lt;/strong&gt; that configure everything at build time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture Decision
&lt;/h3&gt;

&lt;p&gt;Instead of maintaining two separate codebases (nightmare fuel), I built a feature flag system that lets Next.js adapt based on &lt;code&gt;NEXT_PUBLIC_DEPLOYMENT_MODE&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Mode&lt;/strong&gt; (&lt;code&gt;frontend/.env.full&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_DEPLOYMENT_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;full
&lt;span class="nv"&gt;NEXT_PUBLIC_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.wheeleraiduel.online
&lt;span class="nv"&gt;NEXT_PUBLIC_USE_STATIC_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lite Mode&lt;/strong&gt; (&lt;code&gt;frontend/.env.lite&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_DEPLOYMENT_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lite
&lt;span class="nv"&gt;NEXT_PUBLIC_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_USE_STATIC_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend reads these flags and swaps out implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/app/services/dataService.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useStaticData&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;staticDataService&lt;/span&gt;  &lt;span class="c1"&gt;// Reads from JSON files&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiDataService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Calls backend API&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same React components, different data sources. Clean, testable, maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cost-Conscious Matters
&lt;/h2&gt;

&lt;p&gt;Here's the thing about cloud deployments: they're running 24/7 whether you're using them or not. My project gets meaningful traffic maybe 2-3 days a month during demo calls or when I share it on LinkedIn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Old strategy&lt;/strong&gt;: Pay $68 per month regardless of usage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New strategy&lt;/strong&gt;: Pay $5 per month baseline + demo costs only when needed&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The only ongoing cost&lt;/strong&gt;: $5 per month for DO Spaces + CDN (stores all images)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Pages hosting&lt;/strong&gt;: $0 per month (completely free)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full infrastructure&lt;/strong&gt;: Only spin up for demos (~$2-3 per day, prorated hourly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a &lt;strong&gt;92% monthly cost reduction&lt;/strong&gt; ($68 → $5) while maintaining the ability to showcase full capabilities when it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why keep DO Spaces?&lt;/strong&gt; Both deployments need the images anyway. Spaces serves as the single source of truth with built-in CDN, so it's worth the $5 whether I'm running full or lite mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual math&lt;/strong&gt;: Base cost of $60/year ($5 × 12 months) plus occasional demo costs. Even with 10-15 demo days per year, you're looking at ~$90-120/year instead of $816/year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;cheap-deploy.yml&lt;/code&gt; workflow is beautifully simple:&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;Deploy to GitHub Pages (Lite)&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;workflow_dispatch&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;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;Build Lite Frontend&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@v4&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;Setup Node.js&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-node@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;node-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;18'&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;Generate static data from DO Spaces&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 "📡 Fetching real image pairs from DO Spaces..."&lt;/span&gt;
        &lt;span class="s"&gt;node .github/scripts/generate-static-data.js&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 frontend 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 ci&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 frontend in lite mode&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 run build:lite&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 artifact&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/upload-pages-artifact@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;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend/out&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 GitHub Pages&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pages&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deployment.outputs.page_url }}&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&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 GitHub Pages&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;deployment&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/deploy-pages@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key insight: &lt;strong&gt;The static data is generated automatically from DO Spaces&lt;/strong&gt;. I don't manually maintain two sets of images. The build script fetches metadata from my DO Spaces bucket (which I keep running at $5 per month for the CDN anyway) and generates the &lt;code&gt;image-pairs.json&lt;/code&gt; file at build time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demo-Ready Strategy
&lt;/h2&gt;

&lt;p&gt;Here's my actual workflow when I have an interview or want to showcase the full project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before the demo&lt;/strong&gt; (20 minutes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Deploy full infrastructure&lt;/span&gt;
Actions → pricy-deploy.yml → Run workflow &lt;span class="o"&gt;(&lt;/span&gt;droplet_count: 2&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Wait ~8 minutes for deployment&lt;/span&gt;

&lt;span class="c"&gt;# 3. Switch DNS to Digital Ocean&lt;/span&gt;
Actions → dns-cutover.yml → Run workflow &lt;span class="o"&gt;(&lt;/span&gt;target: digital-ocean&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 4. Wait 5-10 minutes for DNS propagation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After the demo&lt;/strong&gt; (5 minutes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Switch DNS back to GitHub Pages&lt;/span&gt;
Actions → dns-cutover.yml → Run workflow &lt;span class="o"&gt;(&lt;/span&gt;target: github-pages&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Tear down all DO infrastructure&lt;/span&gt;
Actions → pricy-teardown.yml → Run workflow &lt;span class="o"&gt;(&lt;/span&gt;confirm: DELETE PRICY&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two quick workflows, and you're back to the $5/month baseline. The whole demo cycle costs about $2-3 (hourly prorated billing), and I can repeat this as many times as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Lose (and What You Don't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Features removed in lite mode&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ AI image generation&lt;/li&gt;
&lt;li&gt;❌ Cross-session vote tracking&lt;/li&gt;
&lt;li&gt;❌ Live statistics across users&lt;/li&gt;
&lt;li&gt;❌ Backend API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Features preserved in lite mode&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Same UI/UX&lt;/li&gt;
&lt;li&gt;✅ Local voting (localStorage)&lt;/li&gt;
&lt;li&gt;✅ Real images from DO Spaces CDN&lt;/li&gt;
&lt;li&gt;✅ Mobile-optimized swipe interface&lt;/li&gt;
&lt;li&gt;✅ Winners tracking (per browser)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a portfolio project, the lite version is perfectly sufficient 95% of the time. Nobody &lt;em&gt;really&lt;/em&gt; needs to generate new AI images when browsing my portfolio at 2am. They just need to see that the project works and looks professional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production-Grade Practices (The Real Portfolio Value)
&lt;/h2&gt;

&lt;p&gt;Here's what this dual-deployment strategy demonstrates to recruiters:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Feature Flags &amp;amp; Configuration Management
&lt;/h3&gt;

&lt;p&gt;Build-time feature flags allow a single codebase to serve multiple deployment targets. This is how real companies handle staging/production differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Blue/Green Deployment
&lt;/h3&gt;

&lt;p&gt;Both deployments run simultaneously. I can test the lite version independently before cutting over DNS. Zero-downtime switching between environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost Optimization
&lt;/h3&gt;

&lt;p&gt;Understanding cloud economics and implementing strategies to minimize costs while maintaining capabilities shows business acumen, not just technical skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Infrastructure as Code
&lt;/h3&gt;

&lt;p&gt;The full deployment uses Pulumi. The lite deployment uses GitHub Actions. Both are reproducible, version-controlled, and automated.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Graceful Degradation
&lt;/h3&gt;

&lt;p&gt;The frontend adapts intelligently to available backend services. This is critical for resilient production systems.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Full (DO)&lt;/th&gt;
&lt;th&gt;Lite (GH Pages + DO Spaces)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$68&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly Savings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;$63 (92% reduction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~20 min&lt;/td&gt;
&lt;td&gt;~3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;~70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annual Base Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$816 per year&lt;/td&gt;
&lt;td&gt;$60 per year*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Demo Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;+$2-3 per demo day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;*Plus demo costs if/when you spin up full infrastructure&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Was It Worth It?
&lt;/h2&gt;

&lt;p&gt;Absolutely. I now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ A $5 per month portfolio deployment running 24/7&lt;/li&gt;
&lt;li&gt;✅ Full feature showcase available on-demand&lt;/li&gt;
&lt;li&gt;✅ Real-world experience with feature flags&lt;/li&gt;
&lt;li&gt;✅ Blue/green deployment practice&lt;/li&gt;
&lt;li&gt;✅ Cost optimization case study for interviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I'm saving &lt;strong&gt;$63 per month (92% reduction)&lt;/strong&gt; while maintaining the ability to demonstrate the full project during interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Your Turn
&lt;/h2&gt;

&lt;p&gt;Think this approach could work for your cloud portfolio project? The patterns here apply to any dual-deployment scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full vs. lite&lt;/li&gt;
&lt;li&gt;Production vs. preview&lt;/li&gt;
&lt;li&gt;US region vs. EU region&lt;/li&gt;
&lt;li&gt;Premium features vs. free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 2022 Cloud Portfolio Challenge is still out there, still relevant in 2025. And now you have a cost-effective strategy for keeping it running without breaking the bank.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connect &amp;amp; Build
&lt;/h2&gt;

&lt;p&gt;Want to discuss cloud architecture, cost optimization strategies, or your own portfolio projects? Find me on &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; - I'm always happy to connect with fellow builders.&lt;/p&gt;

&lt;p&gt;Or better yet: &lt;strong&gt;Attempt the challenge yourself&lt;/strong&gt;. Build something, deploy it two ways, and show recruiters you understand both technical excellence &lt;em&gt;and&lt;/em&gt; business value. That's the real portfolio flex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/wheeleruniverse/cgc-lb-and-cdn" rel="noopener noreferrer"&gt;github.com/wheeleruniverse/cgc-lb-and-cdn&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of my #CloudGuruChallenge series. It's a follow-up to &lt;a href="https://blog.wheeleruniverse.com/read/cgc-lb-and-cdn" rel="noopener noreferrer"&gt;"Better Late Than Never: Tackling the 2022 Cloud Portfolio Challenge"&lt;/a&gt; - showing how I reduced costs from $68/month to $5/month using the same codebase.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>costoptimization</category>
      <category>devops</category>
      <category>githubactions</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>Better Late Than Never: Tackling the 2022 Cloud Portfolio Challenge in 2025 (With a Modern Twist)</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Mon, 20 Oct 2025 19:26:28 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/better-late-than-never-tackling-the-2022-cloud-portfolio-challenge-in-2025-with-a-modern-twist-3gde</link>
      <guid>https://forem.com/wheeleruniverse/better-late-than-never-tackling-the-2022-cloud-portfolio-challenge-in-2025-with-a-modern-twist-3gde</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt; &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Support my work:&lt;/strong&gt; &lt;a href="https://buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy me a coffee ☕&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You know that feeling when you add something to your to-do list with every intention of knocking it out quickly... and then three years pass? Yeah, me too.&lt;/p&gt;

&lt;p&gt;Back in June 2022, &lt;a href="https://www.linkedin.com/in/lklint/" rel="noopener noreferrer"&gt;Lars Klint&lt;/a&gt; posted the &lt;a href="https://www.pluralsight.com/resources/blog/cloud/cloud-portfolio-challenge-load-balancing-and-content-delivery-network" rel="noopener noreferrer"&gt;Cloud Portfolio Challenge: Load Balancing and Content Delivery Network&lt;/a&gt; on Pluralsight. The challenge was straightforward: build an image delivery service that returns images matching search criteria, while learning about load balancing, CDN, compute, and storage fundamentals.&lt;/p&gt;

&lt;p&gt;I bookmarked it. Added it to my list. And promptly let life happen.&lt;/p&gt;

&lt;p&gt;Fast forward to October 2025, and I finally dusted off that bookmark. But here's the thing—&lt;strong&gt;I wouldn't have been able to build what I built if I had actually met that 2022 deadline&lt;/strong&gt;. The tech stack I used simply didn't exist back then, or wasn't mature enough to use effectively.&lt;/p&gt;

&lt;p&gt;So this is my "better late than never" submission, powered by 2025's tech stack, and honestly? I'm glad I waited.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Original Challenge (And How I Completely Reimagined It)
&lt;/h2&gt;

&lt;p&gt;The original challenge asked participants to build an image delivery service with four key cloud components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute&lt;/strong&gt; - Running the application logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt; - Storing and serving images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load Balancing&lt;/strong&gt; - Distributing traffic across instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN&lt;/strong&gt; - Delivering content globally with low latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple enough, right? Well, I decided to add a twist: &lt;strong&gt;What if the images were AI-generated from a curated set of prompts, and users could vote on which images they prefer in head-to-head battles?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enter: &lt;strong&gt;AI Image Battle Arena&lt;/strong&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%2F3yw2gqum1mftuvyg9m3y.png" 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%2F3yw2gqum1mftuvyg9m3y.png" alt="AI Image Battle Home" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of serving pre-existing images, my application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asynchronously generates images using multiple AI providers (Freepik, Google Imagen, Leonardo AI) via scheduled cron jobs&lt;/li&gt;
&lt;li&gt;Presents two random images side-by-side for comparison (both from the same provider to ensure fair comparison)&lt;/li&gt;
&lt;li&gt;Lets users vote on which image they prefer via swipe gestures&lt;/li&gt;
&lt;li&gt;Tracks statistics and winners in a Valkey (Redis-compatible) database&lt;/li&gt;
&lt;li&gt;Serves everything through a CDN with load-balanced full-stack droplets&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's like Hot or Not, but for AI-generated art. Each comparison uses images from a single provider to keep things fair—I didn't want the user experience depending on multiple AI APIs being available simultaneously. This makes the system more resilient with ADK managing provider selection behind the scenes.&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%2Fbv3mlhsbgbkw1cbcotvj.png" 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%2Fbv3mlhsbgbkw1cbcotvj.png" alt="AI Image Battle Left" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Digital Ocean? (A Love Letter to Simplicity)
&lt;/h2&gt;

&lt;p&gt;Let me be upfront: I'm a &lt;strong&gt;major cloud provider person&lt;/strong&gt;. I'm an AWS Community Builder, AWS User Group Leader, and AWS Gold Jacket holder. I've worked extensively with AWS, Azure, and GCP for years—and I genuinely love these platforms. They power the world's largest applications, and their breadth of services is unmatched.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Sidebar: Yes, even with today's AWS outage on October 20, 2025—because let's be real, all cloud providers have bad days. The big three have earned their reliability reputations.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But I kept hearing from other developers in the community: &lt;em&gt;"You should try Digital Ocean. It's so much simpler. The developer experience is amazing."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And you know what? &lt;strong&gt;They were right.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After years with the hyperscalers, Digital Ocean felt like a breath of fresh air. The UI is clean, intuitive, and doesn't make you feel like you need a map just to find what you're looking for. Everything is straightforward—no endless service catalogs, no decision paralysis about which of the 17 database options to choose.&lt;/p&gt;

&lt;p&gt;Some highlights from my DO experience:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Managed Databases (Valkey)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Setting up a Valkey cluster (Redis-compatible) via Pulumi was incredibly straightforward. The API is intuitive—just specify size, region, and VPC attachment. No complex subnet CIDR calculations or security group wizardry. It just works.&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%2Fckvz20c3hgfsxjjxky83.png" 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%2Fckvz20c3hgfsxjjxky83.png" alt="Digital Ocean Console showing project resources" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Spaces + CDN&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Digital Ocean Spaces (S3-compatible object storage) comes with built-in CDN. Not "you need to set up CloudFront and configure origins and behaviors"—it's just... included. Upload your images, they're automatically CDN-distributed. Chef's kiss. 👌&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%2F6fwqnh0pnih03qbiwq8g.png" 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%2F6fwqnh0pnih03qbiwq8g.png" alt="DO Spaces Root" width="515" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;DO Metrics Agent&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here's a cool feature I didn't expect: Digital Ocean offers an enhanced metrics agent you can install on droplets that measures things like &lt;strong&gt;memory usage&lt;/strong&gt;—metrics that aren't included in the free tier on other providers. I configured it in my UserData script, and suddenly I had deep observability into my droplet performance without additional cost.&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%2Fjaj42878e0w85zy37doi.png" 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%2Fjaj42878e0w85zy37doi.png" alt="DO Metrics Agent" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Credits and Support&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;DO gave me $200 in promotional credits (60-day expiration) to test things out. And when I had questions about Droplet limits? Their support team responded quickly with helpful, non-robotic answers.&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%2Fkzo5zg494aq99tvkkt4b.png" 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%2Fkzo5zg494aq99tvkkt4b.png" alt="Digital Ocean promotional credits 1" width="800" height="150"&gt;&lt;/a&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%2Faybng2e0gm5ofc7x7m5i.png" 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%2Faybng2e0gm5ofc7x7m5i.png" alt="Digital Ocean promotional credits 2" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To be clear&lt;/strong&gt;: AWS, Azure, and GCP are incredible platforms that I'll continue using for enterprise work. But for side projects, learning, and rapid prototyping? Digital Ocean's simplicity is genuinely refreshing. Different tools for different jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Domain Management with Namecheap + DO Nameservers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For this project, I grabbed a domain from Namecheap: &lt;code&gt;wheeleraiduel.online&lt;/code&gt; for just &lt;strong&gt;$0.98/year&lt;/strong&gt;. Since this is a side project I don't plan to keep live beyond a year, why spend $12+ on a .com?&lt;/p&gt;

&lt;p&gt;The setup is beautifully simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register domain on Namecheap (~$1)&lt;/li&gt;
&lt;li&gt;Point Namecheap to Digital Ocean's nameservers&lt;/li&gt;
&lt;li&gt;Manage all DNS records directly in the DO console&lt;/li&gt;
&lt;li&gt;Let's Encrypt SSL certificates auto-provision via Pulumi&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This hybrid approach gives me Namecheap's pricing with DO's DNS management UX. Best of both worlds for a temporary project.&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%2F914ndthjh9ksujq5wuf7.png" 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%2F914ndthjh9ksujq5wuf7.png" alt="Namecheap domain pointing to Digital Ocean nameservers" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Random Prompts? (Safety First)
&lt;/h2&gt;

&lt;p&gt;You might notice the application generates images from a curated set of prompts rather than taking user input. This was a deliberate design choice for three reasons:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Async Generation Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Since images are generated via scheduled cron jobs (not on-demand), there's no user session to capture input from. The generation happens in the background, building up a library of images that the frontend randomly serves.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;User Input Requires Special Care&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Accepting user input means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input validation and sanitization&lt;/li&gt;
&lt;li&gt;Rate limiting to prevent abuse&lt;/li&gt;
&lt;li&gt;Moderation to filter inappropriate prompts&lt;/li&gt;
&lt;li&gt;Storage and management of user data&lt;/li&gt;
&lt;li&gt;Potential GDPR/privacy concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a side project focused on cloud architecture and AI orchestration? That's scope creep I didn't need.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Prompt Injection Is Real&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Generative AI is susceptible to prompt injection attacks where malicious users craft inputs to bypass safety filters or generate harmful content. By using a curated set of prompts (generated by Claude and Gemini during development), I completely eliminate this attack vector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example curated prompts&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Robot holding a red skateboard"&lt;/li&gt;
&lt;li&gt;"Astronaut riding a bicycle on the moon"&lt;/li&gt;
&lt;li&gt;"Cat wearing sunglasses at a coffee shop"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safe, fun, and focused on the technical infrastructure—not content moderation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack That Made This Possible
&lt;/h2&gt;

&lt;p&gt;Here's where 2025 tech really shines:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Backend: Go + Gin Framework&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I built the API server in Go using the Gin web framework. Why Go? Fast, statically typed, great concurrency support, and perfect for cloud-native applications. The backend handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image generation orchestration&lt;/li&gt;
&lt;li&gt;Provider fallback logic&lt;/li&gt;
&lt;li&gt;Vote tracking and statistics&lt;/li&gt;
&lt;li&gt;Health checks for the load balancer&lt;/li&gt;
&lt;/ul&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%2Fgseur7v3ffl2is8aubfq.png" 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%2Fgseur7v3ffl2is8aubfq.png" alt="DO Spaces Images" width="620" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend: Next.js 14&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The image comparison interface is built with Next.js, featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile-first responsive design with swipe gestures&lt;/li&gt;
&lt;li&gt;Framer Motion animations with spring physics&lt;/li&gt;
&lt;li&gt;Real-time vote feedback&lt;/li&gt;
&lt;li&gt;Server-side rendering for SEO&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Infrastructure as Code: Pulumi (with Go)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I've used Terraform extensively, and I've worked with CloudFormation and AWS CDK. But for this project, I went with &lt;strong&gt;Pulumi&lt;/strong&gt;—and I'm genuinely impressed.&lt;/p&gt;

&lt;p&gt;Pulumi lets you write infrastructure code in real programming languages (I used Go for consistency with my backend). No HCL to learn, no YAML templating gymnastics—just actual code with real loops, conditionals, and type safety.&lt;/p&gt;

&lt;p&gt;Here's what my infrastructure deploys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Simplified example from hosting/main.go&lt;/span&gt;
&lt;span class="n"&gt;droplets&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;digitalocean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Droplet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropletCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;dropletCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;droplet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;digitalocean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDroplet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logicalName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;digitalocean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DropletArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ubuntu-22-04-x64"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s-2vcpu-2gb"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nyc3"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;VpcUuid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;UserData&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;getFullStackUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;droplets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;droplet&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each droplet runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend Go API server (port 8080)&lt;/li&gt;
&lt;li&gt;Frontend Next.js app (port 3000)&lt;/li&gt;
&lt;li&gt;Nginx reverse proxy (port 80)&lt;/li&gt;
&lt;li&gt;Automated log uploads to Spaces (hourly, gzip compressed)&lt;/li&gt;
&lt;li&gt;DigitalOcean metrics agent&lt;/li&gt;
&lt;/ul&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%2F8fncwk0rc8dgzslt0u3w.png" 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%2F8fncwk0rc8dgzslt0u3w.png" alt="DO Spaces Logs" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Pulumi console gives you real-time visibility into deployments:&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%2Fby9jmgd9fhoubc59jipq.png" 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%2Fby9jmgd9fhoubc59jipq.png" alt="Pulumi console showing infrastructure updates" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why Pulumi over Terraform? A few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: Compiler catches errors before deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loops &amp;amp; Logic&lt;/strong&gt;: Native language constructs instead of &lt;code&gt;count&lt;/code&gt; hacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Language&lt;/strong&gt;: Same language as my backend (Go)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Error Messages&lt;/strong&gt;: Actually tells you what's wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulumi might not have Terraform's community size yet, but for greenfield projects, it's a compelling choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Google Agent Development Kit (ADK)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here's the real 2025 magic: &lt;strong&gt;Google's Agent Development Kit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I learned about ADK from my connection &lt;a href="https://www.linkedin.com/in/enevoldk/" rel="noopener noreferrer"&gt;Kelby Enevold&lt;/a&gt;, who told me how cool it was. ADK is Google's framework for building AI agents that can orchestrate tasks, use tools, and handle complex workflows.&lt;/p&gt;

&lt;p&gt;In my application, ADK powers the "orchestrator agent" that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Randomly selects an AI image provider&lt;/li&gt;
&lt;li&gt;Calls the provider's API to generate an image&lt;/li&gt;
&lt;li&gt;Detects quota limits, rate limits, or errors&lt;/li&gt;
&lt;li&gt;Automatically falls back to a different provider&lt;/li&gt;
&lt;li&gt;Handles retries and error scenarios&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern—intelligent provider selection with automatic fallback—would have been manual spaghetti code without ADK. Instead, it's a clean agent-based architecture that "just works."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This literally wouldn't have been possible in 2022.&lt;/strong&gt; Google ADK was &lt;a href="https://developers.googleblog.com/en/agent-development-kit-easy-to-build-multi-agent-applications/" rel="noopener noreferrer"&gt;announced at Google Cloud Next 2025&lt;/a&gt;, with the stable Python v1.0.0 release happening in 2024. It's built on the same foundation powering Google's own products like Agentspace and their Customer Engagement Suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hot Storage vs. Cold Storage: Why Valkey Is Optional
&lt;/h2&gt;

&lt;p&gt;One architectural decision I'm particularly proud of: &lt;strong&gt;Digital Ocean Spaces is my single source of truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's how the data architecture works:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cold Storage: DO Spaces (Source of Truth)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every generated image is stored in DO Spaces with rich metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image file&lt;/strong&gt;: The actual PNG/JPEG&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object metadata&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt;: Which AI service generated it (freepik, google-imagen, leonardo-ai)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prompt&lt;/code&gt;: The text prompt used for generation&lt;/li&gt;
&lt;li&gt;Other generation details&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This metadata lives directly on the S3-compatible object storage. No separate database required.&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%2F4maqnsf9ozd19fjxcy70.png" 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%2F4maqnsf9ozd19fjxcy70.png" alt="DO Spaces Metadata" width="782" height="823"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Hot Storage: Valkey (Performance Cache)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Valkey stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vote counts and statistics&lt;/li&gt;
&lt;li&gt;Side win tracking (left vs. right)&lt;/li&gt;
&lt;li&gt;Winning image references&lt;/li&gt;
&lt;li&gt;Real-time leaderboard data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here's the key insight: &lt;strong&gt;Valkey is purely for performance&lt;/strong&gt;. It's a cache, not the source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The &lt;code&gt;recreate_valkey&lt;/code&gt; Flag&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In my GitHub Actions deploy workflow, there's a boolean parameter called &lt;code&gt;recreate_valkey&lt;/code&gt;. When set to &lt;code&gt;true&lt;/code&gt;, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scans all objects in DO Spaces&lt;/li&gt;
&lt;li&gt;Reads metadata from each image&lt;/li&gt;
&lt;li&gt;Rebuilds Valkey indexes from scratch&lt;/li&gt;
&lt;li&gt;Repopulates provider statistics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the entire Valkey cluster to save costs&lt;/li&gt;
&lt;li&gt;Recover from Valkey data corruption&lt;/li&gt;
&lt;li&gt;Rebuild after accidental data loss&lt;/li&gt;
&lt;li&gt;Migrate to a different caching solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The images, prompts, and generation history are never lost.&lt;/strong&gt; They live permanently in Spaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Many applications tightly couple their database and storage layers. If the database fails, critical metadata is gone forever. By storing metadata with the objects themselves, I've created a resilient architecture where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valkey failure = temporary performance degradation&lt;/strong&gt;, not data loss&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spaces backup = complete system backup&lt;/strong&gt;, including all metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost optimization = optional caching layer&lt;/strong&gt; when budget is tight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the "lite" version I'm planning in Part 2, I could potentially run without Valkey entirely—serving slightly slower but still functional. That's architectural flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Build Process: Research First, Deploy Last
&lt;/h2&gt;

&lt;p&gt;People sometimes ask me: "How do you even start large side projects like this?"&lt;/p&gt;

&lt;p&gt;My answer: &lt;strong&gt;Start small. Start with research.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how I structured this project:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Research (&lt;code&gt;research/&lt;/code&gt; directory)
&lt;/h3&gt;

&lt;p&gt;Before writing a single line of business logic, I created small Proof of Concepts for each AI provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;research/
├── freepik/          # Test Freepik API integration
├── google-imagen/    # Test Google Imagen API
├── leonardo-ai/      # Test Leonardo AI API
└── craiyon/          # Test Craiyon (spoiler: broken)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each research folder had its own README documenting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to authenticate&lt;/li&gt;
&lt;li&gt;How to make requests&lt;/li&gt;
&lt;li&gt;Response formats&lt;/li&gt;
&lt;li&gt;Pricing/quota limits&lt;/li&gt;
&lt;li&gt;Gotchas and workarounds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This meant when I started building the actual backend, I wasn't debugging API integration issues—I already knew exactly how each provider worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key lesson&lt;/strong&gt;: Spend time in research mode. It pays dividends later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Backend (&lt;code&gt;backend/&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Once I understood the providers, I built the Go API server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider abstraction layer&lt;/li&gt;
&lt;li&gt;ADK orchestrator integration&lt;/li&gt;
&lt;li&gt;Valkey vote tracking&lt;/li&gt;
&lt;li&gt;Image storage to DO Spaces&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3: Frontend (&lt;code&gt;frontend/&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Next.js app with mobile-optimized voting interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swipe left/right to vote&lt;/li&gt;
&lt;li&gt;Real-time animations&lt;/li&gt;
&lt;li&gt;Statistics display&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 4: Infrastructure (&lt;code&gt;hosting/&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Last&lt;/strong&gt; step: Deploy to the cloud.&lt;/p&gt;

&lt;p&gt;Why last? Because cloud providers bill you from the moment you provision resources. By building locally first, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoided weeks of unnecessary billing&lt;/li&gt;
&lt;li&gt;Used my DO credits efficiently (60-day expiration)&lt;/li&gt;
&lt;li&gt;Deployed a complete, tested application—not a half-baked experiment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This order—&lt;strong&gt;research → backend → frontend → hosting&lt;/strong&gt;—is how I approach every side project. It keeps costs down and reduces cloud debugging headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions: The Full Deployment Pipeline
&lt;/h2&gt;

&lt;p&gt;Once infrastructure code was ready, I built three GitHub Actions workflows to manage everything:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Deploy Workflow&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;One-click deployment via GitHub Actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provisions all infrastructure (load balancer, droplets, database, CDN)&lt;/li&gt;
&lt;li&gt;Configurable droplet count (2-10 instances)&lt;/li&gt;
&lt;li&gt;Auto-deploys applications via UserData script&lt;/li&gt;
&lt;li&gt;Sets up monitoring and log shipping&lt;/li&gt;
&lt;/ul&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%2F8za6luya4gandp99yog7.png" 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%2F8za6luya4gandp99yog7.png" alt="GitHub repository secrets configuration" width="798" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Teardown Workflow&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Safe infrastructure destruction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires typing "DESTROY" to confirm (safety first!)&lt;/li&gt;
&lt;li&gt;Cleans up auto-created DNS records&lt;/li&gt;
&lt;li&gt;Removes all resources to stop billing&lt;/li&gt;
&lt;li&gt;Saves ~$68/month when not in use&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Refresh Workflow&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;State synchronization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Syncs Pulumi state with actual cloud resources&lt;/li&gt;
&lt;li&gt;Useful after manual changes in DO console&lt;/li&gt;
&lt;li&gt;Detects and resolves state drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full transparency&lt;/strong&gt;: This automation is a game-changer. Push code, watch it deploy, tear it down when done. No manual server configuration, no SSH debugging sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned (And What Surprised Me)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Simplicity Has Value&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AWS's breadth of services is powerful, but DO's focused simplicity meant I spent more time building features and less time reading documentation about VPC peering topologies.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;IaC Language Matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using Go for both backend and infrastructure code created a cohesive developer experience. Context switching between languages is mentally taxing—Pulumi eliminated that.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;AI Agents Are Production-Ready&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Google ADK isn't just a toy—it handles real production workflows with fallback logic, error handling, and reliability. This is the future of orchestration.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;UserData Is Underrated&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;My droplets auto-deploy everything via UserData scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install dependencies (Go, Node.js, nginx)&lt;/li&gt;
&lt;li&gt;Clone and build applications&lt;/li&gt;
&lt;li&gt;Configure systemd services&lt;/li&gt;
&lt;li&gt;Set up cron jobs for log uploads&lt;/li&gt;
&lt;li&gt;Install monitoring agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No manual SSH configuration. No Ansible playbooks. Just boot and go.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Research Time Is Never Wasted&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Those small POCs in the &lt;code&gt;research/&lt;/code&gt; folder saved me countless debugging hours later. Investing in understanding your dependencies upfront is time well spent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge Requirement Checklist ✅
&lt;/h2&gt;

&lt;p&gt;So, did I actually complete the original challenge requirements?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Compute&lt;/strong&gt;: Full-stack droplets running Go + Next.js + nginx&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Storage&lt;/strong&gt;: DO Spaces for images and logs&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Load Balancing&lt;/strong&gt;: DO Load Balancer distributing traffic across instances&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;CDN&lt;/strong&gt;: Built-in CDN via DO Spaces&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Image Delivery&lt;/strong&gt;: Returns images (AI-generated) matching search criteria&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Learning Outcome&lt;/strong&gt;: Deep understanding of how these components work together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bonus achievements&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Infrastructure as Code (Pulumi)&lt;/li&gt;
&lt;li&gt;✅ AI orchestration (Google ADK)&lt;/li&gt;
&lt;li&gt;✅ Automated deployment (GitHub Actions)&lt;/li&gt;
&lt;li&gt;✅ Production-grade monitoring (DO Metrics Agent)&lt;/li&gt;
&lt;li&gt;✅ Security hardening (non-root service user, VPC-isolated database)&lt;/li&gt;
&lt;li&gt;✅ Cost optimization (automated teardown, log compression)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts: Better Late Than Never
&lt;/h2&gt;

&lt;p&gt;Did I miss the 2022 deadline? Absolutely.&lt;/p&gt;

&lt;p&gt;Do I regret waiting? Not even a little.&lt;/p&gt;

&lt;p&gt;This project showcases technology that didn't exist three years ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google ADK for AI orchestration (released 2024)&lt;/li&gt;
&lt;li&gt;Modern generative AI APIs (Imagen 3.0, Leonardo AI)&lt;/li&gt;
&lt;li&gt;Pulumi's matured Go SDK&lt;/li&gt;
&lt;li&gt;Next.js 14's App Router&lt;/li&gt;
&lt;li&gt;Digital Ocean's enhanced features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sometimes the best time to tackle a challenge is when you have the right tools for the job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're sitting on a dusty to-do item from years ago, consider this your sign: maybe now is actually the perfect time. The tools have gotten better. Your skills have improved. And that "overdue" project might turn into your best work yet.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/lklint/" rel="noopener noreferrer"&gt;Lars Klint&lt;/a&gt; for the original challenge, &lt;a href="https://www.linkedin.com/in/enevoldk/" rel="noopener noreferrer"&gt;Kelby Enevold&lt;/a&gt; for introducing me to Google ADK, and the Digital Ocean team for making cloud infrastructure genuinely enjoyable to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next? Stay Tuned for Part 2 📅
&lt;/h2&gt;

&lt;p&gt;As my DO promotional credits approach expiration near the end of the year, I'm planning to deploy a &lt;strong&gt;"lite" version&lt;/strong&gt; of this project—a cost-optimized configuration designed to avoid bill shock while keeping the core functionality intact.&lt;/p&gt;

&lt;p&gt;Part 2 will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost optimization strategies for production&lt;/li&gt;
&lt;li&gt;Scaling down gracefully without losing features&lt;/li&gt;
&lt;li&gt;Balancing cloud costs vs. capabilities&lt;/li&gt;
&lt;li&gt;Real-world lessons from running AI infrastructure on a budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me on &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to catch Part 2 when it drops!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Build This Yourself?
&lt;/h2&gt;

&lt;p&gt;The entire project is open source on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/wheeleruniverse/cgc-lb-and-cdn" rel="noopener noreferrer"&gt;wheeleruniverse/cgc-lb-and-cdn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://wheeleraiduel.online" rel="noopener noreferrer"&gt;wheeleraiduel.online&lt;/a&gt; (when deployed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The README includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete setup instructions&lt;/li&gt;
&lt;li&gt;Architecture diagrams&lt;/li&gt;
&lt;li&gt;API documentation&lt;/li&gt;
&lt;li&gt;Cost breakdowns&lt;/li&gt;
&lt;li&gt;Deployment guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give it a star if you found this interesting, and feel free to fork it for your own experiments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What cloud challenges are sitting on your dusty to-do list? Drop them in the comments—let's hold each other accountable! 👇&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Enjoyed this post? &lt;a href="https://buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy me a coffee ☕&lt;/a&gt; to support more cloud adventures!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>digitalocean</category>
      <category>pulumi</category>
      <category>go</category>
    </item>
    <item>
      <title>Conquering the Multi-Cloud Resume Challenge: My Journey to a Serverless Showcase</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Wed, 23 Jul 2025 19:20:18 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/conquering-the-multi-cloud-resume-challenge-my-journey-to-a-serverless-showcase-5a6c</link>
      <guid>https://forem.com/wheeleruniverse/conquering-the-multi-cloud-resume-challenge-my-journey-to-a-serverless-showcase-5a6c</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Challenge: Beyond a Single Cloud&lt;/li&gt;
&lt;li&gt;
My Multicloud Resume: A Deep Dive

&lt;ul&gt;
&lt;li&gt;The Architecture: Think Globally, Deploy Remotely&lt;/li&gt;
&lt;li&gt;Cloud Provider Implementation: A Complexity Comparison&lt;/li&gt;
&lt;li&gt;The Stack: Tried, Tested, and True&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Technical Deep Dive: Cloud-Agnostic Business Logic

&lt;ul&gt;
&lt;li&gt;Database Connector Complexity Analysis&lt;/li&gt;
&lt;li&gt;Multi-Cloud Environment Configuration&lt;/li&gt;
&lt;li&gt;CI/CD: The Automated Dream&lt;/li&gt;
&lt;li&gt;Features that Impress&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Performance/Cost Analysis: The Real-World Impact

&lt;ul&gt;
&lt;li&gt;Cold Start Performance Analysis&lt;/li&gt;
&lt;li&gt;Deployment Speed Comparison&lt;/li&gt;
&lt;li&gt;Cost Analysis: Monthly Operating Expenses&lt;/li&gt;
&lt;li&gt;Performance Optimization Insights&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Gotchas and Pain Points: The Multi-Cloud Reality Check

&lt;ul&gt;
&lt;li&gt;Azure's SKU-Based Pricing: The Feature Wall&lt;/li&gt;
&lt;li&gt;HTTP to HTTPS Redirect: Azure's Hidden Cost&lt;/li&gt;
&lt;li&gt;Database Consistency Models: The Hidden Complexity&lt;/li&gt;
&lt;li&gt;Container Registry Complexity&lt;/li&gt;
&lt;li&gt;Authentication and Secrets Management&lt;/li&gt;
&lt;li&gt;Monitoring and Debugging Differences&lt;/li&gt;
&lt;li&gt;SKU Upgrade Pressure: The Azure Tax&lt;/li&gt;
&lt;li&gt;Lessons Learned: Multi-Cloud Gotchas&lt;/li&gt;
&lt;li&gt;The Bottom Line&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What I Learned (and Why You Should Try It!)&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Hey there, fellow cloud enthusiasts! You know that feeling when a new challenge lands in your lap, and it just &lt;em&gt;clicks&lt;/em&gt; with everything you've been working towards? That's exactly how I felt when I stumbled upon the &lt;a href="https://blog.wheeleruniverse.com/meta-resume-challenge" rel="noopener noreferrer"&gt;Meta Resume Challenge&lt;/a&gt;. It wasn't just another cloud project; it was an opportunity to truly flex those multi-cloud muscles and build something that screams "enterprise-level cloud development!"&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Beyond a Single Cloud
&lt;/h2&gt;

&lt;p&gt;Forrest Brazeal's original Cloud Resume Challenge was a game-changer, pushing us to build a serverless resume on a single cloud. But what if you could take that concept and multiply it by three? That's the essence of the Meta Resume Challenge: constructing a comprehensive, serverless resume application deployed across &lt;strong&gt;AWS&lt;/strong&gt;, &lt;strong&gt;Azure&lt;/strong&gt;, and &lt;strong&gt;Google Cloud Platform&lt;/strong&gt;, each with complete isolation.&lt;/p&gt;

&lt;p&gt;This wasn't just about putting a resume online; it was about demonstrating proficiency in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three-tier architecture:&lt;/strong&gt; Frontend, backend, and database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless everywhere:&lt;/strong&gt; Functions, Lambda, Cloud Run – you name it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code (IaC):&lt;/strong&gt; Because who wants to click around consoles anymore?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipelines:&lt;/strong&gt; Automating the build, test, and deployment dance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-agnostic skills:&lt;/strong&gt; Building solutions that aren't locked into a single provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Multicloud Resume: A Deep Dive
&lt;/h2&gt;

&lt;p&gt;My approach to the Meta Resume Challenge is encapsulated in my &lt;a href="https://github.com/wheeleruniverse/multicloud-resume" rel="noopener noreferrer"&gt;Multicloud Resume GitHub repository&lt;/a&gt;. I aimed for a robust, production-ready application that showcases not just what I know, but how I build.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture: Think Globally, Deploy Remotely
&lt;/h3&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%2Fyg3zspy528iugy41fg3l.png" 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%2Fyg3zspy528iugy41fg3l.png" alt="architecture-diagram" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At its core, my solution is a classic three-tier serverless application. But here's the kicker: each tier is strategically deployed across the three major cloud providers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend (Angular) ──► API Gateway ──► Serverless Functions ──► Cloud Databases
     │                          │                     │                       │
Static Web                Load Balancer         Business Logic          Data Storage
(CDN + Storage)                               (Spring Boot)           (NoSQL DBs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how the components are distributed across the clouds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;AWS&lt;/th&gt;
&lt;th&gt;Azure&lt;/th&gt;
&lt;th&gt;GCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CloudFront + S3&lt;/td&gt;
&lt;td&gt;Static Web Apps&lt;/td&gt;
&lt;td&gt;Cloud Storage + CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API Gateway + Lambda&lt;/td&gt;
&lt;td&gt;Function App&lt;/td&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;Cosmos DB&lt;/td&gt;
&lt;td&gt;Firestore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3 Buckets&lt;/td&gt;
&lt;td&gt;Blob Storage&lt;/td&gt;
&lt;td&gt;Cloud Storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CDN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CloudFront&lt;/td&gt;
&lt;td&gt;Front Door&lt;/td&gt;
&lt;td&gt;Cloud CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cloud Provider Implementation: A Complexity Comparison
&lt;/h3&gt;

&lt;p&gt;After examining the serverless implementations across all three providers, here's what I discovered about their technical approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🥇 Simplest: Google Cloud Platform&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GCP's Cloud Run approach is the most developer-friendly—it's just a standard Spring Boot application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="nc"&gt;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No special handlers, no cloud-specific annotations, just &lt;code&gt;SpringApplication.run()&lt;/code&gt;. This makes local development and testing seamless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🥈 Most AWS-Specific: Amazon Web Services&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Lambda requires the &lt;code&gt;SpringBootLambdaContainerHandler&lt;/code&gt; pattern, adding an abstraction layer between your Spring Boot app and Lambda runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LambdaHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RequestStreamHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;SpringBootLambdaContainerHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AwsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AwsProxyResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;HANDLER&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;HANDLER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpringBootLambdaContainerHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAwsProxyHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContainerInitializationException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"StreamHandler ContainerInitializationException"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OutputStream&lt;/span&gt; &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;HANDLER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proxyStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While more complex than GCP, it's still relatively clean and provides good integration with the AWS ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🥉 Most Verbose: Microsoft Azure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Azure Functions demands the most ceremony—individual &lt;code&gt;@FunctionName&lt;/code&gt; annotations for each endpoint, cloud-specific types, and manual response building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@FunctionName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"visitorCount"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HttpResponseMessage&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@HttpTrigger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;authLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ANONYMOUS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"visitor/count"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="nc"&gt;HttpRequestMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received %d as the visitor count"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createResponseBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CountDto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asHttpResponse&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this verbosity provides fine-grained control over function behavior and excellent integration with Azure's monitoring and logging.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stack: Tried, Tested, and True
&lt;/h3&gt;

&lt;p&gt;I leveraged a technology stack that's both modern and enterprise-friendly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Java 11 with Spring Boot 2.5.4, Spring Cloud Function for serverless compatibility, and robust testing with JUnit 5 and Mockito.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Angular 11 with TypeScript, NgRx for state management, and Angular Material for a sleek UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; Terraform for all IaC needs, with GitHub Actions orchestrating the entire CI/CD pipeline. Docker plays a key role for containerization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  State Management with NgRx
&lt;/h4&gt;

&lt;p&gt;The frontend leverages NgRx for sophisticated state management, handling complex async operations like visitor tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;count$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;ofType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VisitorCountAction&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;VisitorActionType&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="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visitorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VisitorCountSuccessAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;catchError&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ErrorAction&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="p"&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="nx"&gt;incrementSuccess$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ofType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VisitorIncrementSuccessAction&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;VisitorActionType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IncrementSuccess&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookieService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snackBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New Visitor Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snackBarConfig&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;p&gt;This reactive approach ensures consistent user experience regardless of which cloud provider is serving the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Deep Dive: Cloud-Agnostic Business Logic
&lt;/h2&gt;

&lt;p&gt;One of the most elegant aspects of this architecture is how Spring Cloud Function enables identical business logic across all three clouds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VisitorService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;CountRepository&lt;/span&gt; &lt;span class="n"&gt;visitorRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;visitorCount&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitorRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;  
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitorIncrement&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;visitorRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitorLoad&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;validateCount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;visitorRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This functional approach means the same business logic runs identically whether it's invoked by AWS Lambda, Azure Functions, or GCP Cloud Run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Connector Complexity Analysis
&lt;/h3&gt;

&lt;p&gt;The database connectivity reveals interesting differences in each cloud's SDK design philosophy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS DynamoDB (Simplest):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DynamoDbClient&lt;/span&gt; &lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="o"&gt;(){&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DynamoDbClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialsProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DefaultCredentialsProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure Cosmos DB (Medium Complexity):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;CosmosClient&lt;/span&gt; &lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CosmosClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consistencyLevel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsistencyLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EVENTUAL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosmosHost&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosmosAuth&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GCP Firestore (Most Complex):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;FirestoreOptions&lt;/span&gt; &lt;span class="nf"&gt;getOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FirestoreOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefaultInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProjectId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCredentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getCredentials&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// Requires custom credential handling&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Credentials&lt;/span&gt; &lt;span class="nf"&gt;getCredentials&lt;/span&gt;&lt;span class="o"&gt;(){&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;credentialsStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ByteArrayInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;())){&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;GoogleCredentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentialsStream&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InternalServerErrorException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"getCredentials() Failed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Cloud Environment Configuration
&lt;/h3&gt;

&lt;p&gt;The Angular frontend uses environment-specific configurations to seamlessly switch between cloud providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// environment.aws.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.aws.wheelercloudguru.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wheelercloudguru-iac.s3.amazonaws.com/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// environment.azure.ts  &lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wheelercloudguru.azurewebsites.net/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;microsoft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wheelercloudguruiac.blob.core.windows.net/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// environment.gcp.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.gcp.wheelercloudguru.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://storage.googleapis.com/wheelercloudguru-iac/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project structure is clean and modular, allowing for clear separation of concerns, from core business logic to cloud-specific implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multicloud-resume/
├── app/                      # Backend applications
│   ├── core/                 # Shared business logic and models
│   ├── aws/                  # AWS Lambda implementation
│   ├── azure/                # Azure Functions implementation
│   └── gcp/                  # Google Cloud Run implementation
├── web/                      # Angular frontend application
├── iac/                      # Infrastructure as Code
│   ├── terraform/            # Terraform configurations
│   ├── data/                 # Resume data (JSON/Excel)
│   └── diagrams/             # Architecture diagrams
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD: The Automated Dream
&lt;/h3&gt;

&lt;p&gt;My favorite part? The comprehensive CI/CD pipeline built with GitHub Actions. Each cloud platform has its dedicated workflow, ensuring that every code push triggers automated builds, tests, and deployments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app-core.yml&lt;/code&gt;: Builds and tests the shared core module.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app-aws.yml&lt;/code&gt;: Deploys AWS Lambda functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app-azure.yml&lt;/code&gt;: Deploys Azure Functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app-gcp.yml&lt;/code&gt;: Deploys GCP Cloud Run services.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web.yml&lt;/code&gt;: Builds and deploys the frontend to all platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup ensures rapid, reliable deployments and keeps everything in sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features that Impress
&lt;/h3&gt;

&lt;p&gt;Beyond the core requirements, I added some features that really make the resume shine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Visitor Counter:&lt;/strong&gt; A fun touch that tracks visitors across all platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design:&lt;/strong&gt; Optimized for every device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimization:&lt;/strong&gt; CDN, caching, and compression for a lightning-fast experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive Testing:&lt;/strong&gt; Unit, integration, and end-to-end tests for both backend and frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance/Cost Analysis: The Real-World Impact
&lt;/h2&gt;

&lt;p&gt;One of the most valuable aspects of this multi-cloud implementation was gaining concrete insights into performance characteristics and cost implications across providers. Here's what the data revealed:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cold Start Performance Analysis
&lt;/h3&gt;

&lt;p&gt;Through extensive testing and monitoring, I discovered significant differences in serverless cold start times:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Cold Start Time&lt;/th&gt;
&lt;th&gt;Warm Response&lt;/th&gt;
&lt;th&gt;Memory Config&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~2.8 seconds&lt;/td&gt;
&lt;td&gt;~150ms&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4.2 seconds&lt;/td&gt;
&lt;td&gt;~200ms&lt;/td&gt;
&lt;td&gt;Dynamic (Y1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GCP Cloud Run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1.5 seconds&lt;/td&gt;
&lt;td&gt;~120ms&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GCP Cloud Run&lt;/strong&gt; emerged as the performance winner, benefiting from its containerized approach and faster initialization. The standard Spring Boot deployment model eliminates the overhead of serverless-specific frameworks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt; showed consistent mid-range performance, with the SpringBootLambdaContainerHandler adding some initialization overhead but providing reliable execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt; had the longest cold starts, likely due to the Java runtime initialization and the multiple function bindings required for the individual endpoint approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Speed Comparison
&lt;/h3&gt;

&lt;p&gt;The deployment characteristics varied significantly across platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AWS: Container-based deployment via ECR&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
  &lt;span class="nx"&gt;image_uri&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;memory_size&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Azure: Dynamic consumption plan&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_plan"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FunctionApp"&lt;/span&gt;
  &lt;span class="nx"&gt;sku&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Y1"&lt;/span&gt;
    &lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Dynamic"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GCP: Direct container deployment&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_service"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deployment Speed Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GCP Cloud Run&lt;/strong&gt;: ~2-3 minutes (fastest)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: ~4-5 minutes (container build + deploy)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Functions&lt;/strong&gt;: ~6-8 minutes (dynamic scaling setup)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost Analysis: Monthly Operating Expenses
&lt;/h3&gt;

&lt;p&gt;Based on my Terraform configurations and actual usage patterns, here's the cost breakdown for a low-traffic resume site (~1,000 monthly visitors):&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Infrastructure Costs:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# DynamoDB: Provisioned mode&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_dynamodb_table"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;billing_mode&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PROVISIONED"&lt;/span&gt;
  &lt;span class="nx"&gt;read_capacity&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="c1"&gt;# ~$0.65/month per table&lt;/span&gt;
  &lt;span class="nx"&gt;write_capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="c1"&gt;# ~$0.65/month per table&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# CloudFront: PriceClass_100 (US/Europe)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;price_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PriceClass_100"&lt;/span&gt;  &lt;span class="c1"&gt;# ~$1.00/month for low traffic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AWS Total: ~$12-15/month&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda: $0 (within free tier)&lt;/li&gt;
&lt;li&gt;DynamoDB: ~$8/month (6 tables × $1.30)&lt;/li&gt;
&lt;li&gt;CloudFront: ~$1/month
&lt;/li&gt;
&lt;li&gt;S3 Storage: ~$1/month&lt;/li&gt;
&lt;li&gt;API Gateway: ~$2/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Azure Infrastructure Costs:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cosmos DB: Free tier enabled&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_cosmosdb_account"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enable_free_tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EnableServerless"&lt;/span&gt;  &lt;span class="c1"&gt;# Pay-per-request&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;p&gt;&lt;strong&gt;Azure Total: ~$5-8/month&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Functions: $0 (within free tier)&lt;/li&gt;
&lt;li&gt;Cosmos DB: $0 (free tier covers usage)&lt;/li&gt;
&lt;li&gt;CDN: ~$2/month&lt;/li&gt;
&lt;li&gt;Storage: ~$3/month&lt;/li&gt;
&lt;li&gt;Application Insights: ~$2/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  GCP Infrastructure Costs:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cloud Run: Pay-per-request, generous free tier&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_service"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# No minimum provisioning required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Firestore: Native mode, free tier&lt;/span&gt;
&lt;span class="c1"&gt;# App Engine app required for Firestore&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_app_engine_application"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;database_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CLOUD_FIRESTORE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GCP Total: ~$3-5/month&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Run: $0 (within free tier)&lt;/li&gt;
&lt;li&gt;Firestore: $0 (free tier sufficient)&lt;/li&gt;
&lt;li&gt;Cloud Storage: ~$1/month&lt;/li&gt;
&lt;li&gt;CDN: ~$2/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Optimization Insights
&lt;/h3&gt;

&lt;p&gt;The Terraform configurations reveal different optimization strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Optimization:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Aggressive caching with CloudFront&lt;/span&gt;
&lt;span class="nx"&gt;default_cache_behavior&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;compress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;cached_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;viewer_protocol_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"redirect-to-https"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure Optimization:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content-specific compression rules&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_cdn_endpoint"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content_types_to_compress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"application/javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"text/css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"text/html"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;is_compression_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GCP Optimization:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Backend bucket with CDN&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_backend_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enable_cdn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Simple but effective&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Most Cost-Effective&lt;/strong&gt;: GCP wins with superior free tiers and serverless-first pricing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Performance&lt;/strong&gt;: GCP Cloud Run's container approach provides fastest cold starts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Most Enterprise Features&lt;/strong&gt;: Azure offers the richest monitoring and debugging capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Most AWS-Native&lt;/strong&gt;: Lambda integrates best with other AWS services but at higher cost&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a production multi-cloud strategy, I'd recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development/Testing&lt;/strong&gt;: GCP (lowest cost, fastest iteration)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production&lt;/strong&gt;: AWS (ecosystem maturity, enterprise support)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics/Monitoring&lt;/strong&gt;: Azure (superior Application Insights integration)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotchas and Pain Points: The Multi-Cloud Reality Check
&lt;/h2&gt;

&lt;p&gt;While building across three cloud providers was incredibly educational, it wasn't without its frustrations. Here are the real-world pain points that caught me off guard and could save you significant time and headaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure's SKU-Based Pricing: The Feature Wall
&lt;/h3&gt;

&lt;p&gt;The most significant pain point was Azure's rigid SKU-based pricing model, which creates artificial feature limitations that simply don't exist on AWS or GCP.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Custom Domain Dilemma
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Azure: Consumption plan limitations&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_plan"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FunctionApp"&lt;/span&gt;
  &lt;span class="nx"&gt;sku&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Y1"&lt;/span&gt;      &lt;span class="c1"&gt;# Consumption tier&lt;/span&gt;
    &lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Dynamic"&lt;/span&gt; &lt;span class="c1"&gt;# Locked into this for serverless&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Result: NO custom domain support on consumption plan!&lt;/span&gt;
&lt;span class="c1"&gt;# You're forced to use: https://myapp.azurewebsites.net/api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: Azure's consumption plan (Y1 SKU) doesn't support custom domains, period. To get &lt;code&gt;https://api.azure.mysite.com&lt;/code&gt;, you need to upgrade to a Premium plan (~$150/month minimum), completely defeating the serverless cost model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS/GCP Reality Check&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AWS: Custom domains included with API Gateway (free tier eligible)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_api"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Works with custom domains out of the box&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# GCP: Custom domains work seamlessly with Cloud Run  &lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_domain_mapping"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api.gcp.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fqdn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Just works, no SKU restrictions&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both AWS and GCP offer custom domain support as a standard feature, not a premium upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP to HTTPS Redirect: Azure's Hidden Cost
&lt;/h3&gt;

&lt;p&gt;Another Azure gotcha was the HTTP to HTTPS redirect functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Azure: Requires separate CDN endpoint for HTTPS redirect&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_cdn_endpoint"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;delivery_rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HttpToHttpsRedirect"&lt;/span&gt;
    &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nx"&gt;request_scheme_condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;match_values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HTTP"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;operator&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Equal"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;url_redirect_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;protocol&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Https"&lt;/span&gt;
      &lt;span class="nx"&gt;redirect_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PermanentRedirect"&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="c1"&gt;# Additional monthly CDN costs just for HTTPS redirect!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Meanwhile on AWS/GCP&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AWS CloudFront: HTTPS redirect built-in, free&lt;/span&gt;
&lt;span class="nx"&gt;viewer_protocol_policy&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"redirect-to-https"&lt;/span&gt; &lt;span class="c1"&gt;# That's it!&lt;/span&gt;

&lt;span class="c1"&gt;# GCP: Automatic HTTPS redirect, no extra config needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database Consistency Models: The Hidden Complexity
&lt;/h3&gt;

&lt;p&gt;Each cloud's NoSQL offering has different default consistency models that aren't immediately obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Azure: Must explicitly configure consistency&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_cosmosdb_account"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;consistency_policy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;consistency_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Eventual"&lt;/span&gt; &lt;span class="c1"&gt;# Required explicit choice&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# AWS: Eventual consistency by default, no config needed&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_dynamodb_table"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Consistency is handled automatically&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# GCP: Strong consistency by default&lt;/span&gt;
&lt;span class="c1"&gt;# Firestore handles this transparently&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Gotcha&lt;/strong&gt;: I spent hours debugging what I thought were data sync issues, only to discover it was Azure Cosmos DB's default consistency settings interacting poorly with my test data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Registry Complexity
&lt;/h3&gt;

&lt;p&gt;The container registry setup revealed significant differences in approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AWS: Simple ECR setup&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecr_repository"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
  &lt;span class="nx"&gt;image_scanning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scan_on_push&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Azure: No built-in container registry with Functions&lt;/span&gt;
&lt;span class="c1"&gt;# Must use Azure Container Registry (ACR) separately&lt;/span&gt;
&lt;span class="c1"&gt;# or Docker Hub - additional complexity and cost&lt;/span&gt;

&lt;span class="c1"&gt;# GCP: Container Registry built into the platform&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_container_registry"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location_multi_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Pain&lt;/strong&gt;: Azure Functions don't have seamless container registry integration like AWS Lambda or GCP Cloud Run, forcing you into more complex deployment patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication and Secrets Management
&lt;/h3&gt;

&lt;p&gt;Managing service account credentials across platforms was surprisingly inconsistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AWS: Seamless IAM integration&lt;/span&gt;
&lt;span class="nc"&gt;DynamoDbClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialsProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DefaultCredentialsProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// Just works&lt;/span&gt;

&lt;span class="c1"&gt;// Azure: Requires manual key management  &lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CosmosClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosmosHost&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosmosAuth&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Must manage this key manually&lt;/span&gt;

&lt;span class="c1"&gt;// GCP: Service account JSON (complex but flexible)&lt;/span&gt;
&lt;span class="nc"&gt;GoogleCredentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentialsStream&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Reality&lt;/strong&gt;: AWS has the most seamless credential management, GCP offers the most security options, and Azure sits uncomfortably in the middle with manual key handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring and Debugging Differences
&lt;/h3&gt;

&lt;p&gt;The debugging experience varied dramatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS&lt;/strong&gt;: CloudWatch logs are verbose but scattered across services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure&lt;/strong&gt;: Application Insights is excellent but requires separate configuration and has retention limits on free tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP&lt;/strong&gt;: Cloud Logging is clean and consolidated but sometimes lacks detail&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SKU Upgrade Pressure: The Azure Tax
&lt;/h3&gt;

&lt;p&gt;The most frustrating aspect of Azure was the constant pressure to upgrade SKUs for basic functionality:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;AWS&lt;/th&gt;
&lt;th&gt;GCP&lt;/th&gt;
&lt;th&gt;Azure Consumption&lt;/th&gt;
&lt;th&gt;Azure Premium&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom Domains&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;❌ Not Available&lt;/td&gt;
&lt;td&gt;💰 $150+/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS Redirect&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;❌ Requires CDN&lt;/td&gt;
&lt;td&gt;💰 Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VNet Integration&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;❌ Not Available&lt;/td&gt;
&lt;td&gt;💰 Premium only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Slots&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;❌ Not Available&lt;/td&gt;
&lt;td&gt;💰 Standard+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Lessons Learned: Multi-Cloud Gotchas
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the Fine Print&lt;/strong&gt;: Azure's consumption plan is "serverless" in name only - critical features are locked behind premium SKUs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Early&lt;/strong&gt;: What works in development might hit SKU limitations in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget for Surprises&lt;/strong&gt;: Azure's "free" tier often requires paid add-ons for basic functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS/GCP Alignment&lt;/strong&gt;: These two providers have similar feature parity in their free/pay-as-you-go tiers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation Gaps&lt;/strong&gt;: Azure docs often don't clearly explain SKU limitations upfront&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Bottom Line
&lt;/h3&gt;

&lt;p&gt;While Azure offers excellent enterprise features and integration with Microsoft ecosystems, their consumption-based pricing model feels more like a trial version than a true serverless offering. For small projects and startups, the SKU-based feature gates can be deal-breakers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation&lt;/strong&gt;: If you're building a proof-of-concept or low-traffic application, start with GCP or AWS. Save Azure for scenarios where you're already committed to Microsoft tooling and have budget for premium SKUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned (and Why You Should Try It!)
&lt;/h2&gt;

&lt;p&gt;This challenge was more than just a coding exercise; it was a deep dive into the nuances of multi-cloud development. I gained invaluable insights into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The subtle (and not-so-subtle) differences between cloud providers for similar services.&lt;/li&gt;
&lt;li&gt;The power of Terraform in managing complex, multi-cloud infrastructure.&lt;/li&gt;
&lt;li&gt;The art of designing a truly cloud-agnostic application from a single codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP had the simplest deployment model&lt;/strong&gt; with standard Spring Boot, while &lt;strong&gt;Azure required the most cloud-specific code&lt;/strong&gt; but provided the finest control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're looking to level up your cloud game and prove your mettle in the multi-cloud world, I highly recommend taking on the &lt;a href="https://blog.wheeleruniverse.com/meta-resume-challenge" rel="noopener noreferrer"&gt;Meta Resume Challenge&lt;/a&gt;. It's a fantastic way to solidify your skills and build a portfolio piece that truly stands out.&lt;/p&gt;

&lt;p&gt;Feel free to check out my &lt;a href="https://github.com/wheeleruniverse/multicloud-resume" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; to see the full implementation, and don't hesitate to reach out on &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; if you have any questions or want to collaborate!&lt;/p&gt;

&lt;p&gt;Happy building! ☁️&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>From Dusty Requirements to Live Website: How Claude Code Built My Blog Aggregator in a Single Day</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Tue, 08 Jul 2025 18:23:11 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/from-dusty-requirements-to-live-website-how-claude-code-built-my-blog-aggregator-in-a-single-day-3jbo</link>
      <guid>https://forem.com/wheeleruniverse/from-dusty-requirements-to-live-website-how-claude-code-built-my-blog-aggregator-in-a-single-day-3jbo</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;After sitting on a requirements document for over a year, I used Claude Code to build and deploy a complete blog aggregation website in just one day. The site aggregates my posts from multiple platforms under one roof with custom URLs, filtering, and a clean Vue 3 interface. &lt;a href="https://blog.wheeleruniverse.com/" rel="noopener noreferrer"&gt;Check it out here&lt;/a&gt; or dive into the &lt;a href="https://github.com/wheeleruniverse/blog" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The Problem That Wouldn't Go Away&lt;/li&gt;
&lt;li&gt;Enter Claude Code&lt;/li&gt;
&lt;li&gt;The Art of Prompt Massaging&lt;/li&gt;
&lt;li&gt;What Claude Code Built&lt;/li&gt;
&lt;li&gt;Why Netlify Made Perfect Sense&lt;/li&gt;
&lt;li&gt;The Claude Code Experience&lt;/li&gt;
&lt;li&gt;Lessons Learned&lt;/li&gt;
&lt;li&gt;Resources and Downloads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem That Wouldn't Go Away
&lt;/h2&gt;

&lt;p&gt;I've been blogging across multiple platforms for years now, and it's become a bit of a mess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bravo LT&lt;/strong&gt; (my employer): Company blog posts on Wix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to&lt;/strong&gt;: Personal posts under my own profile &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Community Builders&lt;/strong&gt;: Technical content under the AWS Builders organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RedMonk&lt;/strong&gt;: Participated in their developer certification series&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each platform has its own limitations that drive me crazy. Dev.to appends random characters to URLs making them impossible to guess. I can't customize the slugs for sharing. Some posts are scattered under different organizational spaces. It's chaos.&lt;/p&gt;

&lt;p&gt;Back in July 2024, I sat down and wrote a comprehensive requirements document. I knew exactly what I wanted: a single aggregation site that would pull all my content together with custom URLs, filtering, search, and a clean interface. I even specified the tech stack I wanted to learn - Vue 3, TypeScript, Tailwind CSS, and Vite.&lt;/p&gt;

&lt;p&gt;Then life happened. Personal obligations, work projects, and the general chaos of existence meant this project kept getting pushed to the back burner. I even reached out to three junior developers about taking it on for a fair rate, but got no takers.&lt;/p&gt;

&lt;p&gt;The requirements document just sat there in my Google Drive, collecting digital dust for over a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Claude Code
&lt;/h2&gt;

&lt;p&gt;With 2025 winding down and my current project ending, I knew I needed to get serious about client acquisition. Having a professional blog aggregator would be a key part of my online presence. The recent improvements in AI capabilities made me wonder - could I finally tackle this myself?&lt;/p&gt;

&lt;p&gt;That's when I discovered Claude Code, Anthropic's agentic command line tool for developers. Unlike traditional AI coding assistants that give you snippets to copy-paste, Claude Code can actually build entire projects from start to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Art of Prompt Massaging
&lt;/h2&gt;

&lt;p&gt;Here's where things got interesting. Claude Code couldn't directly process my year-old requirements document - it was too verbose and scattered. I needed to translate my original specs into something more digestible.&lt;/p&gt;

&lt;p&gt;I went back to the browser-based Claude and had several iterations of conversation to transform my requirements document into a focused prompt specifically designed for Claude Code. This process taught me something crucial about working with AI tools: &lt;strong&gt;prompt engineering is an art form&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The original document was eight pages of scattered thoughts, edge cases, and technical musings. The refined prompt was a concise, structured set of instructions that clearly communicated the tech stack, core features, data structure, and design requirements.&lt;/p&gt;

&lt;p&gt;This back-and-forth process of refinement reminded me of working with any developer - you need to be clear, specific, and organized in your communication. The difference is that AI tools are incredibly literal, so precision matters even more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Code Built
&lt;/h2&gt;

&lt;p&gt;The final result exceeded my expectations. Claude Code delivered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single view listing of all blog posts&lt;/li&gt;
&lt;li&gt;JSON-driven content management (no code changes needed for new posts)&lt;/li&gt;
&lt;li&gt;Custom URL slugs with seamless redirects&lt;/li&gt;
&lt;li&gt;Advanced filtering by date range and source platform&lt;/li&gt;
&lt;li&gt;Real-time search functionality&lt;/li&gt;
&lt;li&gt;Dark/light mode toggle with preference persistence&lt;/li&gt;
&lt;li&gt;Fully responsive design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Technical Implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue 3 with Composition API&lt;/li&gt;
&lt;li&gt;Full TypeScript implementation&lt;/li&gt;
&lt;li&gt;Tailwind CSS for styling&lt;/li&gt;
&lt;li&gt;Vite for build tooling&lt;/li&gt;
&lt;li&gt;ESLint and Prettier configuration&lt;/li&gt;
&lt;li&gt;Modern component architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site aggregates content from all my platforms - Bravo LT, Dev.to (both personal and AWS Community Builders), and even my RedMonk video appearance. Each post redirects to the original source while maintaining clean, branded URLs on my domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Netlify Made Perfect Sense
&lt;/h2&gt;

&lt;p&gt;For hosting, I chose Netlify as a simple, straightforward option. If you're not familiar with Netlify, it's a platform that specializes in static site hosting with some fantastic developer-friendly features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Netlify Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant deployment&lt;/strong&gt; from Git repositories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic builds&lt;/strong&gt; triggered by code commits
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in CDN&lt;/strong&gt; for fast global content delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom domain support&lt;/strong&gt; with free SSL certificates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch previews&lt;/strong&gt; for testing changes before going live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deployment process was beautifully simple: connect the GitHub repository, configure the build settings (&lt;code&gt;npm run build&lt;/code&gt; with output directory &lt;code&gt;dist&lt;/code&gt;), and boom - live website. Any time I push changes to the main branch, Netlify automatically rebuilds and deploys the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Claude Code Experience
&lt;/h2&gt;

&lt;p&gt;Using Claude Code was genuinely impressive. It's currently in research preview and represents a new category of AI tooling - true agentic development assistance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes Claude Code special:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous execution&lt;/strong&gt;: It doesn't just suggest code, it writes complete files and manages the entire project structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context awareness&lt;/strong&gt;: It maintains understanding of the project goals throughout the development process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end delivery&lt;/strong&gt;: From initial scaffolding to final deployment-ready code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool took my refined prompt and built a production-ready application with proper error handling, loading states, and comprehensive TypeScript typing throughout. It created a logical file structure, implemented all the required components, and even included configuration files for the build tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About Claude Code:&lt;/strong&gt;&lt;br&gt;
Claude Code is Anthropic's command line tool that lets developers delegate coding tasks directly from their terminal. It's currently available in research preview and represents a significant step forward in AI-assisted development. You can find more information about Claude Code on &lt;a href="https://www.anthropic.com" rel="noopener noreferrer"&gt;Anthropic's blog&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;This experience taught me several valuable lessons about modern development and AI assistance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt Engineering is Critical&lt;/strong&gt;&lt;br&gt;
The difference between my original requirements document and the refined Claude Code prompt was night and day. Taking time to structure your requirements clearly pays massive dividends in output quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Tools Excel at Implementation&lt;/strong&gt;&lt;br&gt;
Once I had clear specifications, Claude Code handled all the tedious implementation details flawlessly. It's like having a senior developer who never gets tired and follows instructions precisely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Power of Modern Tooling&lt;/strong&gt;&lt;br&gt;
The combination of Vue 3, TypeScript, Tailwind, and Vite created a development experience that felt both powerful and approachable. Claude Code leveraged these tools effectively to create clean, maintainable code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment Should Be Simple&lt;/strong&gt;&lt;br&gt;
Netlify's straightforward deployment process meant I could focus on building rather than wrestling with hosting configuration. Sometimes the simple solution is the right solution.&lt;/p&gt;

&lt;p&gt;The most surprising part? This entire project - from dusty requirements to live website - took exactly one day. A year of procrastination solved in eight hours of focused AI-assisted development.&lt;/p&gt;

&lt;p&gt;If you're sitting on a project that keeps getting pushed to the back burner, maybe it's time to see what Claude Code can do for you. The technology has advanced to the point where the biggest barrier isn't technical complexity - it's taking the time to clearly define what you want to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources and Downloads
&lt;/h2&gt;

&lt;p&gt;Want to try this yourself or see exactly how it was done? I'm making the key artifacts available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📋 &lt;a href="https://github.com/wheeleruniverse/blog/blob/main/docs/original-requirements.pdf" rel="noopener noreferrer"&gt;Original Requirements Document&lt;/a&gt; - The year-old Google Doc that started it all&lt;/li&gt;
&lt;li&gt;🤖 &lt;a href="https://github.com/wheeleruniverse/blog/blob/main/docs/claude-code-prompt.md" rel="noopener noreferrer"&gt;Claude Code Prompt&lt;/a&gt; - The refined prompt that actually worked!&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://blog.wheeleruniverse.com/" rel="noopener noreferrer"&gt;Live Website&lt;/a&gt; - See the final result in action&lt;/li&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/wheeleruniverse/blog" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; - Complete source code and project structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These documents show the evolution from scattered requirements to focused AI instructions. The contrast between the two approaches really highlights the importance of prompt engineering when working with AI development tools.&lt;/p&gt;

&lt;p&gt;Check out the live site and feel free to explore the source code. I'd love to hear your thoughts on the implementation or your own experiences with AI-assisted development.&lt;/p&gt;




&lt;p&gt;As always, if you liked this content, maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Alternatively, you can find all my links on &lt;a href="//linktr.ee/wheeleruniverse"&gt;Linktree&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>vue</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Crushed My AWS Certification Renewals Back-to-Back (and Why It Was a Bad Idea)</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Thu, 09 May 2024 02:11:47 +0000</pubDate>
      <link>https://forem.com/aws-builders/how-i-crushed-my-aws-certification-renewals-back-to-back-and-why-it-was-a-bad-idea-56fh</link>
      <guid>https://forem.com/aws-builders/how-i-crushed-my-aws-certification-renewals-back-to-back-and-why-it-was-a-bad-idea-56fh</guid>
      <description>&lt;p&gt;I renewed all 12 of my active AWS certifications in 22 days! Most of the exams were back-to-back (based on testing center availability), and I even took two exams in a single day. The entire experience was insanely stressful, yet incredible. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;In January, I was compiling a list of my goals this year, determined to be more accomplished than I was last year. I set some extremely ambitious goals for myself, to say the least. As if they are too ambitious, I guess we will wait and see. Since I had some AWS certifications expiring this year and some next year, I wanted to get them all renewed so I could focus on other priorities next year (Kubernetes, here I come)! The obsessive tendencies in me thought it would be nice if I could align the dates in some logical way rather than the random mess they were previously in. Although I had originally planned on finishing my AWS certifications by the end of Q1, plans changed. Aside from everyday life, there were other complications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule
&lt;/h2&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%2Fbl7b75l9v89sglhkvg8n.png" 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%2Fbl7b75l9v89sglhkvg8n.png" alt="Cert Schedule" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4/08 Data Analytics Specialty&lt;/li&gt;
&lt;li&gt;4/22 DevOps Engineer Professional&lt;/li&gt;
&lt;li&gt;4/23 Solutions Architect Professional&lt;/li&gt;
&lt;li&gt;4/24 Advanced Networking Specialty&lt;/li&gt;
&lt;li&gt;4/25 Machine Learning Specialty&lt;/li&gt;
&lt;li&gt;4/27 Security Specialty&lt;/li&gt;
&lt;li&gt;4/29 Database Specialty&lt;/li&gt;
&lt;li&gt;4/29 SAP on AWS Specialty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you didn't already know that professional-level exams automatically renew some lower-level certifications, now you do.&lt;/p&gt;

&lt;p&gt;DevOps Engineer Professional renews&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer Associate&lt;/li&gt;
&lt;li&gt;SysOps Administrator Associate&lt;/li&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solutions Architect Professional renews&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solutions Architect Associate&lt;/li&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Retiring Exams
&lt;/h2&gt;

&lt;p&gt;AWS had announced they were retiring three of their exams, which I already wrote about in &lt;a href="https://dev.to/aws-builders/navigating-aws-certifications-in-2024-hap"&gt;Navigating AWS Certifications in 2024&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Analytics Specialty (retiring April 8th)&lt;/li&gt;
&lt;li&gt;Database Specialty (retiring April 29th)&lt;/li&gt;
&lt;li&gt;SAP on AWS Specialty (retiring April 29th)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Obviously, I contemplated skipping these exams; after all, they are retiring. Why didn't I? What's the point? Well, like I already said in &lt;a href="https://www.linkedin.com/posts/wheeleruniverse_aws-awscertified-awsdata-activity-7183473344866877440-o2cP" rel="noopener noreferrer"&gt;this LinkedIn post&lt;/a&gt; in regards to the Data Analytics Specialty exam, "I don't agree with Amazon Web Services decision to retire this exam". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/what-is/big-data/" rel="noopener noreferrer"&gt;Big Data&lt;/a&gt; is more important now than ever before. Perhaps the Data Engineer Associate is meant to replace this exam. Although I think we may need a Data Engineer Professional if we expect the same level of depth. &lt;/p&gt;

&lt;p&gt;I feel that the Database Specialty is equally valuable. With all of the &lt;a href="https://aws.amazon.com/products/databases/" rel="noopener noreferrer"&gt;purpose-built databases&lt;/a&gt; that AWS has to offer, it is invaluable to build a strong understanding to ensure the best database for the job can be chosen.&lt;/p&gt;

&lt;p&gt;My feelings for the SAP on AWS Specialty are not the same; I'm not terribly sad to see it go, yet I still wanted to renew it. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I had previously planned on taking this exam before I learned about the retirement. If I had skipped it, part of me would have always wondered if I could've actually passed it again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I wanted to test my SAP knowledge, as I am particularly weak in this domain. Even simple SAP configurations on AWS can easily cost &lt;a href="https://d0.awsstatic.com/enterprise-marketing/SAP/sap-on-aws-pricing-guide.pdf" rel="noopener noreferrer"&gt;thousands of dollars per month&lt;/a&gt;, which makes it impossible to experiment on my own.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;p&gt;I've talked about my study methods &lt;a href="https://dev.to/wheeleruniverse/fully-aws-certified-the-5-most-common-questions-i-received-1pk8#q2"&gt;before&lt;/a&gt; and this time was pretty similar, to be honest. Except this time, I only had to focus on the new stuff since I've already passed these tests before.&lt;/p&gt;

&lt;p&gt;These are the websites that I used the most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pluralsight.com/cloud-guru" rel="noopener noreferrer"&gt;A Cloud Guru (Paid)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudacademy.com/profile/9701311f-8e5b-4b21-bb10-6ffc30932c6b" rel="noopener noreferrer"&gt;Cloud Academy (Paid)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mlexam.com/" rel="noopener noreferrer"&gt;ML Exam (Free)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pluralsight.com/" rel="noopener noreferrer"&gt;Pluralsight (Paid)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practice Tests
&lt;/h3&gt;

&lt;p&gt;I started with practice tests to gauge my knowledge before hitting the books. This helped me focus on the newer services and concepts I wasn't familiar with. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did you know that API Gateway can directly call some other AWS services without Lambda? It's called &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/direct-integrations.html" rel="noopener noreferrer"&gt;Direct integrations&lt;/a&gt;, and it's definitely something I learned through this experience. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The scores varied wildly between A Cloud Guru and CloudAcademy, which was interesting. How can I get a 50% on CloudAcademy and a 90% on A Cloud Guru for the same exam? 🤔 &lt;/p&gt;

&lt;h3&gt;
  
  
  Flash Cards
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://quizlet.com/" rel="noopener noreferrer"&gt;Quizlet&lt;/a&gt; to create online flashcards. The AI functionality that has been added to Quizlet recently is really great. I'll highlight two examples.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Magic Notes&lt;/strong&gt; uses AI to generate flash cards automatically from a large source of text. I used this a couple of times to paste AWS FAQs or documentation. &lt;/li&gt;
&lt;/ul&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%2Fda3vjdsclg56xr08v6ti.png" 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%2Fda3vjdsclg56xr08v6ti.png" alt="Quizlet Magic Notes" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Q-Chat&lt;/strong&gt; uses AI to interact with your own flash cards using natural language. With this, I don't have to worry about exactly how my flash cards are typed anymore. &lt;/li&gt;
&lt;/ul&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%2Fx7y0tj9qv2vej74n2gep.png" 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%2Fx7y0tj9qv2vej74n2gep.png" alt="Quizlet Q Chat" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the middle of this AI storm, it seems like Quizlet is doing AI the right way!&lt;/p&gt;

&lt;h3&gt;
  
  
  Dedicated Study Time
&lt;/h3&gt;

&lt;p&gt;For a while now, I have always dedicated time every day to studying something. This year, I just focused the allocated time strictly on AWS. Nearing my exams, I would often trade time I would've spent on entertainment like movies or shows for studying instead to ensure I was as prepared as I could be. Do you know how hard it is to study for eight exams at the same time? In my mind, it was similar to "finals week" in college.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dedicated Testing Time
&lt;/h3&gt;

&lt;p&gt;Typically, when I take a single exam, I will take a half day and be back to work. Although, this was much more taxing, I used a week of PTO so I could focus on my exams exclusively. I prefer testing in person, which resulted in commuting to testing centers that were about 35 miles away. Still, not all of my tests were in person. I did three exams using the online option called &lt;a href="https://home.pearsonvue.com/Test-takers/OnVUE-online-proctoring.aspx" rel="noopener noreferrer"&gt;OnVUE&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expenses
&lt;/h3&gt;

&lt;p&gt;The costs can be high for these types of exams. I had access to a bunch of benefits through my company and my engagement in AWS communities.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;AWS Community Builders&lt;/strong&gt; and &lt;strong&gt;AWS User Group Leaders&lt;/strong&gt; both provided me with a 100% voucher.&lt;/p&gt;

&lt;p&gt;Next, I was able to use multiple 50% vouchers that I had stockpiled from earning all of the certifications previously. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS provides &lt;a href="https://aws.amazon.com/certification/benefits/" rel="noopener noreferrer"&gt;benefits&lt;/a&gt; to certified individuals. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, &lt;a href="https://bravolt.com/" rel="noopener noreferrer"&gt;Bravo LT&lt;/a&gt; reimbursed me for every exam I passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Words of Caution 🚧
&lt;/h2&gt;

&lt;p&gt;I will never do this again. If you are thinking of attempting something similar, I hope I can change your mind. Overall, I felt like everything turned out okay, but it could've been far worse.&lt;/p&gt;

&lt;p&gt;My mental health suffered from this endeavor in a couple ways. I experienced anxiety, stress, and &lt;a href="https://www.betterup.com/blog/what-is-imposter-syndrome-and-how-to-avoid-it" rel="noopener noreferrer"&gt;Imposter Syndrome&lt;/a&gt;. I lost enjoyment in my life for a time because I was sacrificing all of my spare time to studying.&lt;/p&gt;

&lt;p&gt;My physical health was impacted as well. I found myself skipping meals to finish practice tests or additional lessons. Furthermore, my quality of sleep was affected.&lt;/p&gt;

&lt;p&gt;When planning, I want to ask that you don't overwhelm yourself. Give yourself plenty of time to accomplish your goals without rushing.&lt;/p&gt;




&lt;p&gt;As always, if you liked this content, maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/wheeleruniverse/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
    </item>
    <item>
      <title>Lessons Learned from Learning Everyday for 4 Years on Pluralsight</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Wed, 28 Feb 2024 12:41:16 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/lessons-learned-from-learning-everyday-for-4-years-on-pluralsight-2d47</link>
      <guid>https://forem.com/wheeleruniverse/lessons-learned-from-learning-everyday-for-4-years-on-pluralsight-2d47</guid>
      <description>&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;Disclaimer mode activated! While my enthusiasm for Pluralsight (and A Cloud Guru) could fuel a rocket launch, I have zero affiliation with either company (present or past). I'm just a passionate learner, that's all!&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Discovery&lt;/li&gt;
&lt;li&gt;The Power of Learning Everyday ⚡&lt;/li&gt;
&lt;li&gt;
Why Pluralsight?

&lt;ul&gt;
&lt;li&gt;Learning Streak 🔥&lt;/li&gt;
&lt;li&gt;Certification Prep 📝&lt;/li&gt;
&lt;li&gt;Impressive Catalog 📚&lt;/li&gt;
&lt;li&gt;Official Training&lt;/li&gt;
&lt;li&gt;Skill IQ&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Favorite Authors 🧑‍🏫

&lt;ul&gt;
&lt;li&gt;A Cloud Guru&lt;/li&gt;
&lt;li&gt;Pluralsight&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Discovery 🔭
&lt;/h2&gt;

&lt;p&gt;Personally, I owe the start of my learning journey to Verizon and my amazing manager at the time, &lt;a href="https://www.linkedin.com/in/thomas-wise-333578/" rel="noopener noreferrer"&gt;Thomas Wise&lt;/a&gt;. Verizon had many programs that enabled me to grow. They provided me with an &lt;a href="https://www.pluralsight.com/cloud-guru" rel="noopener noreferrer"&gt;A Cloud Guru&lt;/a&gt; subscription when I started pursuing AWS certifications in 2018. After a while, they switched to &lt;a href="https://www.pluralsight.com/" rel="noopener noreferrer"&gt;Pluralsight&lt;/a&gt;. I truly don't know why the change was made. Perhaps it was an extended library. 🤷🏻&lt;/p&gt;

&lt;p&gt;I chose to maintain my A Cloud Guru subscription on my own and divided my time across the platforms. At the time, I would study cloud technology on A Cloud Guru and literally everything else on Pluralsight. In 2021, I made the tough decision to leave Verizon. Afterwards, I decided to maintain both platforms independently going forward since I knew that I had grown immensely from both platforms. &lt;/p&gt;

&lt;p&gt;It's kind of funny that shortly after I found out that &lt;a href="https://www.startupdaily.net/topic/asx/us-tech-giant-pluralsight-completes-2-billion-acquisition-of-a-cloud-guru/" rel="noopener noreferrer"&gt;Pluralsight acquired A Cloud Guru&lt;/a&gt;, which was great news for me since my two favorite learning platforms were merging. The future was indeed bright! &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; The Power of Learning Everyday ⚡
&lt;/h2&gt;

&lt;p&gt;It has been proven time and time again that it is better to learn a little every day than it is to cram a lot of "learning" into one drawn-out session. The brain can retain information a lot better when it is spread out over time. You may not know just how much learning you can do in just a few minutes a day. I recently read a great blog titled &lt;a href="https://stevekeating.me/2021/04/25/learning-a-little-leads-to-learning-a-lot/" rel="noopener noreferrer"&gt;"Learning a Little Leads to Learning a Lot" by Steve Keating&lt;/a&gt; that you could quickly read. He states that most people are really busy, but rarely productive. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"One of the most productive things people can do is set aside a few minutes EVERY day to learn something new."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Furthermore, &lt;a href="https://www.learnerbly.com/" rel="noopener noreferrer"&gt;Learnerbly&lt;/a&gt; covers many benefits of learning in their blog, &lt;a href="https://www.learnerbly.com/articles/why-is-learning-important" rel="noopener noreferrer"&gt;"Why is Learning Important? A Deep Dive Into the Benefits of Being a Lifelong Learner" by Melissa Malec&lt;/a&gt;. This post breaks benefits into those for our wellbeing and those for our careers. &lt;/p&gt;

&lt;p&gt;Some of my favorite quotes from that post are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happiness&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Research suggests that people who practice continuous or lifelong learning are happier on average. This may be because lifelong learning helps people to keep developing their passions and interests, which bring us happiness."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Longevity&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Several scientific studies have shown that lifelong learning activities can help people maintain better brain function as they age."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Innovation&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Learning also drives innovation, which describes the new ideas and technological and cultural developments that people come up with to solve problems and improve their societies."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After dedicating time to learning every day for 1,460 days (4 years), I cannot stress enough how beneficial it has been for me. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have been able to excel in every role that I have held due to rapid upskilling.&lt;/li&gt;
&lt;li&gt;I have a different mindset when thinking about technology.&lt;/li&gt;
&lt;li&gt;I have found joy in side projects that leverage technology I have never touched before.

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/wheeleruniverse/getting-hands-on-with-blazor-nextjs-and-vercel-gg0"&gt;Blazor, Next.js, and Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/creating-serverless-websites-with-aws-bref-and-php-203j"&gt;PHP and Bref&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wheeleruniverse/feb-21-cloudguruchallenge-358d"&gt;Azure and Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Why Pluralsight?
&lt;/h2&gt;

&lt;p&gt;Pluralsight has some of the highest quality training I have ever seen. On top of that, they have a tremendous number of features. Some of which you may not even know about yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Learning Streak 🔥
&lt;/h3&gt;

&lt;p&gt;It should be obvious, but there is a built-in learning tracker. Without this, I would not even know that I had studied for 1,460 days. Without this, I don't think I could have done it at all.&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%2Fzsjbdv22qg4mze8rp7bk.png" 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%2Fzsjbdv22qg4mze8rp7bk.png" alt="Learning Streak" width="715" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Certification Prep 📝
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.pluralsight.com/product/cert-prep" rel="noopener noreferrer"&gt;Certification Prep&lt;/a&gt; offers one of the best ways to tackle technical certifications. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hands-on Labs to build real-world skills.&lt;/li&gt;
&lt;li&gt;Peer Tracker to compare popularity.&lt;/li&gt;
&lt;li&gt;Practice Exams to validate knowledge.&lt;/li&gt;
&lt;li&gt;Program Maps to chart certification pathways.&lt;/li&gt;
&lt;/ul&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%2Fv4ugktaeuvc4kq51uah9.png" 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%2Fv4ugktaeuvc4kq51uah9.png" alt="Cert Prep" width="601" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Impressive Catalog 📚
&lt;/h3&gt;

&lt;p&gt;Pluralsight has more than 7,000 courses in its &lt;a href="https://www.pluralsight.com/browse" rel="noopener noreferrer"&gt;catalog&lt;/a&gt;. I can always find at least a little content on even obscure topics like &lt;em&gt;"Quantum Computing."&lt;/em&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%2F0dohy25scd6m4h1j8nnq.png" 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%2F0dohy25scd6m4h1j8nnq.png" alt="Quantum Search Results" width="550" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Official Training
&lt;/h3&gt;

&lt;p&gt;Did you know that Pluralsight has some authors that are official companies? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://app.pluralsight.com/profile/author/aws-authored" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.pluralsight.com/profile/author/google-cloud" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.pluralsight.com/profile/author/ibm-z" rel="noopener noreferrer"&gt;IBM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content they share here is truly amazing. It is a beautiful thing to see collaboration with third-party training when each of these companies already has their own training platform they would prefer you use instead. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Skill IQ
&lt;/h3&gt;

&lt;p&gt;Skill IQ is an assessment that measures proficiency in a technology skill in 10 to 15 minutes. They have a large range of tests on just about every technology you would like to be tested on, with more coming all the time.&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%2Fvlgk2slk24vmq8e9r05v.png" 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%2Fvlgk2slk24vmq8e9r05v.png" alt="Skill IQ 1" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://help.pluralsight.com/help/what-is-skill-iq" rel="noopener noreferrer"&gt;Pluralsight Help Center&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Skill IQ is a continuous score from 0 to 300. This is based on your percentile in that skill area. Pluralsight's exclusive adaptive skill calculations work by measuring your technical abilities relative to the population of users that have taken that assessment. This doesn't measure your broad intelligence. Your percentile tells you how much more you know than others who have the same skill, on a scale from 0 to 100."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fqhr0osnxeq4efrwgwa2d.png" 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%2Fqhr0osnxeq4efrwgwa2d.png" alt="Skill IQ 2" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After your assessment, Pluralsight can highlight gaps in your knowledge. Better yet, it can link to specific training that you can use to close those gaps right within the Skill IQ assessment page.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Favorite Authors 🧑‍🏫
&lt;/h2&gt;

&lt;p&gt;I know that Pluralsight would be nothing without its amazing authors. I would like to end by recognizing my favorite authors. All of these authors have helped me immensely in different ways. You should check out their content so that they can help you as well!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; A Cloud Guru
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/faye-ellis" rel="noopener noreferrer"&gt;Faye Ellis&lt;/a&gt; for AWS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/mattias-andersson-ps" rel="noopener noreferrer"&gt;Mattias Andersson&lt;/a&gt; for GCP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Pluralsight
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/david-tucker" rel="noopener noreferrer"&gt;David Tucker&lt;/a&gt; for Cloud&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/deborah-kurata" rel="noopener noreferrer"&gt;Deborah Kurata&lt;/a&gt; for Angular&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/kate-gregory" rel="noopener noreferrer"&gt;Kate Gregory&lt;/a&gt; for C++&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/profile/author/sander-mak" rel="noopener noreferrer"&gt;Sander Mak&lt;/a&gt; for Java&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;As always, if you liked this content, maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/wheeleruniverse/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>beginners</category>
      <category>pluralsight</category>
    </item>
    <item>
      <title>Getting Hands-on with Blazor, Next.js and Vercel</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Mon, 12 Feb 2024 08:05:05 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/getting-hands-on-with-blazor-nextjs-and-vercel-gg0</link>
      <guid>https://forem.com/wheeleruniverse/getting-hands-on-with-blazor-nextjs-and-vercel-gg0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://cgc-resume-from-json.vercel.app/" rel="noopener noreferrer"&gt;this website&lt;/a&gt; using Blazor and Next.js then deployed both parts to Vercel. If you're interested in the why or the how then keep reading!&lt;/p&gt;




&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;
Blazor

&lt;ul&gt;
&lt;li&gt;Http.GetFromJsonAsync&lt;/li&gt;
&lt;li&gt;CascadingValue and CascadingParameter&lt;/li&gt;
&lt;li&gt;
Oh My Mobile!😱&lt;/li&gt;
&lt;li&gt;Anchor Navigation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Vercel

&lt;ul&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Next.js

&lt;ul&gt;
&lt;li&gt;API&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Retrospective&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Overview
&lt;/h2&gt;

&lt;p&gt;When planning my 2024 goals, I knew that I wanted to get hands-on with some new technology. Suddenly, I found the following &lt;a href="https://www.linkedin.com/posts/lklint_copilot-blazor-programming-activity-7150700292005576705-lCwB" rel="noopener noreferrer"&gt;LinkedIn post&lt;/a&gt; from &lt;a href="https://www.linkedin.com/in/lklint/" rel="noopener noreferrer"&gt;Lars Klint&lt;/a&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%2Fjof406mfqdy42dvickjp.png" 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%2Fjof406mfqdy42dvickjp.png" alt="Lars Klint LinkedIn Post" width="525" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He mentioned &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;, which was something I hadn't heard about before. I am traditionally a Java developer, yet I am never afraid to try something new. I quickly prepared myself with a couple amazing courses on &lt;a href="https://www.pluralsight.com/" rel="noopener noreferrer"&gt;Pluralsight&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/library/courses/blazor-big-picture/table-of-contents" rel="noopener noreferrer"&gt;Blazor 5: The Big Picture&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/barryluijbregts/" rel="noopener noreferrer"&gt;Barry Luijbregts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/library/courses/asp-dot-net-core-6-blazor-fundamentals/table-of-contents" rel="noopener noreferrer"&gt;ASP.NET Core 6 Blazor Fundamentals&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/gillcleeren/" rel="noopener noreferrer"&gt;Gill Cleeren&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While training, I was thinking of a practical project that I could use to solidify my knowledge. It was then that I recalled a challenge that &lt;a href="https://www.linkedin.com/in/jeremycmorgan/" rel="noopener noreferrer"&gt;Jeremy Morgan&lt;/a&gt; had created during his time at Pluralsight that I intended on completing but never started. The first of an incomplete series, &lt;a href="https://www.allhandsontech.com/programming/code-portfolio-challenge/" rel="noopener noreferrer"&gt;"Code Portfolio Challenge"&lt;/a&gt; tasked with building a resume website dynamically from a JSON file. Although I missed the deadline back in June 2022 by a tad, I thought this would be the perfect way to flex my Blazor knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Blazor
&lt;/h2&gt;

&lt;p&gt;I found the coding experience for Blazor to be similar to that of Angular and React. The difference is that my templates, aka razor files, were mixed with C# instead of JavaScript or TypeScript. Shared components could be created and nested in other pages, just like the other frameworks allow.&lt;/p&gt;

&lt;p&gt;I decided to run with the default Blazor template that comes from the &lt;code&gt;dotnet new blazorwasm -o TodoList&lt;/code&gt; command. The site was aesthetically pleasing from the start. Besides, my goal wasn't to learn CSS; I already knew that.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Http.GetFromJsonAsync
&lt;/h3&gt;

&lt;p&gt;If you're already familiar with C#, then I'm sure you know that you can load a static file using &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.json.httpclientjsonextensions.getfromjsonasync" rel="noopener noreferrer"&gt;Http.GetFromJsonAsync&lt;/a&gt;. That is exactly what I did in order to load the &lt;a href="https://github.com/wheeleruniverse/cgc-resume-from-json/blob/main/web/wwwroot/data/wheeler-resume.json" rel="noopener noreferrer"&gt;resume.json&lt;/a&gt; data that I had created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Home.razor
@if (_resume == null)
{
    &amp;lt;h1&amp;gt;Loading...&amp;lt;/h1&amp;gt;
}
else
{
    &amp;lt;PageTitle&amp;gt;@Resume.Name&amp;lt;/PageTitle&amp;gt;
}

@code {
    private Resume? _resume;

    protected override async Task OnInitializedAsync()
    {
        _resume = await Http.GetFromJsonAsync&amp;lt;Resume&amp;gt;("data/wheeler-resume.json");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; CascadingValue and CascadingParameter
&lt;/h3&gt;

&lt;p&gt;After hacking away for ten minutes, I realized that this app was going to be terrible if I had to fetch the static resume over and over again on every page I had. &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/cascading-values-and-parameters" rel="noopener noreferrer"&gt;CascadingParameter&lt;/a&gt; to the rescue! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cascading values and parameters provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// MainLayout.razor
&amp;lt;CascadingValue Value="@_resume"&amp;gt;
  @Body
&amp;lt;/CascadingValue&amp;gt;

// Home.razor
@code {
    [CascadingParameter]
    public MainLayout.Resume Resume { get; set; } = null!;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now any component that needed the resume data could simply wire it up for dependency injection to save the day!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Oh My Mobile! 😱
&lt;/h3&gt;

&lt;p&gt;With the rise of web activity on cell phones, I knew that it wasn't negotiable that even this side project had to work on mobile and look good too. For most of the styles, I could use traditional &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries" rel="noopener noreferrer"&gt;media queries&lt;/a&gt;. Although, in some cases, I wanted to manipulate the HTML itself for mobile. For that to work, I had to fetch the screen size myself. &lt;/p&gt;

&lt;p&gt;First, I added the following script to my &lt;code&gt;index.html&lt;/code&gt; to create a JavaScript function that I could find with the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet" rel="noopener noreferrer"&gt;IJSRuntime&lt;/a&gt;. Then I created a service that would allow me to isolate that logic from any specific component, as I knew I would need it in multiple places.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.html
&amp;lt;script type="text/javascript"&amp;gt;
    window.BrowserServiceGetDimensions = function () {
        return {
            width: window.innerWidth,
            height: window.innerHeight
        };
    };
&amp;lt;/script&amp;gt;

// BrowserService.cs
using Microsoft.JSInterop;

public class BrowserService
{
    private readonly IJSRuntime _js;

    public BrowserService(IJSRuntime js)
    {
        _js = js;
    }

    public async Task&amp;lt;BrowserScreenSize&amp;gt; GetScreenSize()
    {
        var dimensions = await GetDimensions();
        return dimensions.Width switch
        {
            &amp;gt;= 1024 =&amp;gt; BrowserScreenSize.Desktop,
            &amp;gt;= 768 =&amp;gt; BrowserScreenSize.Tablet,
            _ =&amp;gt; BrowserScreenSize.Mobile
        };
    }

    private async Task&amp;lt;BrowserDimension&amp;gt; GetDimensions()
    {
        return await _js.InvokeAsync&amp;lt;BrowserDimension&amp;gt;("BrowserServiceGetDimensions");
    }
}

public class BrowserDimension
{
    public int Width { get; set; }
    public int Height { get; set; }
}

public enum BrowserScreenSize
{
    Desktop,
    Mobile,
    Tablet,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One usage of this logic was to reduce the number of social icons I displayed on the footer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SocialFooter.razor
@code {
    [Parameter]
    public MainLayout.ResumeUrl[] Urls { get; set; } = null!;

    private MainLayout.ResumeUrl[]? _urls;

    protected override async Task OnParametersSetAsync()
    {
        _urls = await GetUrlsForScreen();
    }

    private async Task&amp;lt;MainLayout.ResumeUrl[]&amp;gt; GetUrlsForScreen()
    {
        var screen = await Browser.GetScreenSize();
        return screen switch
        {
            BrowserScreenSize.Tablet =&amp;gt; Urls.Take(7).ToArray(),
            BrowserScreenSize.Mobile =&amp;gt; Urls.Take(5).ToArray(),
            _ =&amp;gt; Urls
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Anchor Navigation
&lt;/h3&gt;

&lt;p&gt;Frustratingly, anchor navigation didn't work out of the box for Blazor. Luckily, I found &lt;a href="https://www.meziantou.net/anchor-navigation-in-a-blazor-application.htm" rel="noopener noreferrer"&gt;Meziantou's blog&lt;/a&gt; which had a great workaround that I could use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private async Task ScrollToFragment()
{
    var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
    var fragment = uri.Fragment;
    if (fragment.StartsWith('#'))
    {
        // Handle text fragment (https://example.org/#test:~:text=foo)
        // https://github.com/WICG/scroll-to-text-fragment/
        var elementId = fragment[1..];
        var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
        if (index &amp;gt; 0)
        {
            elementId = elementId.Substring(0, index);
        }
        if (!string.IsNullOrEmpty(elementId))
        {
            await JsRuntime.InvokeVoidAsync("NavAnchorScrollToId", elementId);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Vercel
&lt;/h2&gt;

&lt;p&gt;I had no prior experience with &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; before attempting this. I only had the idea because it was one of the few platforms Jeremy mentioned in the challenge posting that I hadn't already experimented with. &lt;/p&gt;

&lt;p&gt;I would be lying if I said that I didn't struggle to deploy Blazor to Vercel. &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%2Feff3h4pg5lbjtquftyp8.png" 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%2Feff3h4pg5lbjtquftyp8.png" alt="Vercel Web Settings" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build Command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet publish --configuration Release --output Publish --self-contained

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Output Directory
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install Command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm &amp;amp;&amp;amp; yum install dotnet-sdk-7.0 -y

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Storage
&lt;/h3&gt;

&lt;p&gt;Maybe that would've been sufficient. Except I accidentally learned about &lt;a href="https://vercel.com/docs/storage" rel="noopener noreferrer"&gt;Vercel Storage&lt;/a&gt;. Did you know that Vercel has free storage offerings like Redis, Postgres, and Blob? I sure didn't know that. Once I did, I had the crazy idea to enhance this application with a share feature. &lt;/p&gt;

&lt;p&gt;I thought it would be interesting for other people to be able to upload their own resume (based on my format) and re-generate the page dynamically. It was easy enough to change the source of the resume in the code. It was much harder to persist that data permanently so that I could recall it with a unique URL.&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%2Fb61ocnevo7qg07fygsn9.png" 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%2Fb61ocnevo7qg07fygsn9.png" alt="Blazor App Test User" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the data uploaded could omit literally any field, I had to cover my bases in the code with lots of null checks. Now the user can choose which fields to retain, and the sections on the page will disappear like magic. &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%2Fn5ph7nteovmfa5odu4de.png" 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%2Fn5ph7nteovmfa5odu4de.png" alt="Blazor App Unknown" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Resume = {}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When importing a JSON, I am saving it in Vercel Blob storage with a unique ID. Then provide a share URL that can be used to reload that same resume. For example, &lt;a href="https://cgc-resume-from-json.vercel.app/?id=f87791d3-c359-42e8-8be2-067ce54a2e20" rel="noopener noreferrer"&gt;https://cgc-resume-from-json.vercel.app/?id=f87791d3-c359-42e8-8be2-067ce54a2e20&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;With this feature, even non-technical people could leverage this site for their own virtual resume without writing any code! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSON is not code. 😶&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Security
&lt;/h3&gt;

&lt;p&gt;Uploading to the Vercel Blob service required some critical thinking. Originally, I didn't want to build a backend for this project. I was pretty fixed on the frontend only. &lt;/p&gt;

&lt;p&gt;Unfortunately, it had to be done. You may not know this, but there is absolutely no secure way to protect sensitive keys like the &lt;code&gt;BLOB_READ_WRITE_TOKEN&lt;/code&gt;, in a client side app. A sophisticated hacker can find that data.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Next.js
&lt;/h2&gt;

&lt;p&gt;Did you know that Vercel created &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;? Well, I didn't. That is, until I tried to navigate the Vercel documentation. I have never felt pressured  to use a specific technology like the way Vercel pressured me to use Next.js. All of their documentation pointed towards it. Trying to use any other technology seemed like I was on my own. &lt;/p&gt;

&lt;p&gt;It was at this pivotal point that I had a decision to make. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do I jump into Next.js, a framework I've never used, for the ease of documentation and integration?&lt;/li&gt;
&lt;li&gt;Do I struggle through the documentation and implementation with a backend framework or language that I already know?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since I would be struggling in either scenario, I chose to dive into Next.js. I've heard the splash it's made in the community and thought it would be a great learning opportunity. &lt;/p&gt;

&lt;p&gt;One more quick course on Pluralsight to ramp up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://app.pluralsight.com/library/courses/nextjs-12-big-picture/table-of-contents" rel="noopener noreferrer"&gt;Next.js 12: The Big Picture&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/coryhouse/" rel="noopener noreferrer"&gt;Cory House&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; API
&lt;/h3&gt;

&lt;p&gt;I got started with &lt;code&gt;npx create-next-app@latest&lt;/code&gt; to scaffold the project. Then I deleted all the files that I didn't need and created a &lt;code&gt;pages/api&lt;/code&gt; directory to hold the API code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any file inside the folder &lt;code&gt;pages/api&lt;/code&gt; is mapped to &lt;code&gt;/api/*&lt;/code&gt; and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size. &lt;a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this simple app, I only required a single TypeScript file, &lt;a href="https://github.com/wheeleruniverse/cgc-resume-from-json/blob/main/app/pages/api/blob.ts" rel="noopener noreferrer"&gt;blob.ts&lt;/a&gt;, with two methods to &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;PUT&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;putBlob&lt;/code&gt; function accepts the resume data as the request body in JSON format and uploads the file to Vercel Blob storage using the &lt;a href="https://vercel.com/docs/storage/vercel-blob/using-blob-sdk" rel="noopener noreferrer"&gt;SDK&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { put, PutCommandOptions } from '@vercel/blob';
import type { NextApiRequest, NextApiResponse } from 'next';

async function putBlob(
  req: NextApiRequest,
  res: NextApiResponse&amp;lt;BlobResponse&amp;gt;
): Promise&amp;lt;void&amp;gt; {
  if (!req.body) {
    sendError(res, 400);
    return;
  }
  const id = crypto.randomUUID();
  const options: PutCommandOptions = {
    access: 'public',
    addRandomSuffix: false,
    contentType: 'application/json',
  };
  try {
    const result = await put(`${id}.json`, JSON.stringify(req.body), options);
    const { downloadUrl, pathname, url } = result;
    res.status(200).json({ id, downloadUrl, pathname, url });
  } catch (error) {
    console.log(error);
    sendError(res, 500);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getBlob&lt;/code&gt; function accepts the unique id as a query parameter and uses that to pull a single blob from the &lt;code&gt;list&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { list } from '@vercel/blob';
import type { NextApiRequest, NextApiResponse } from 'next';

async function getBlob(
  req: NextApiRequest,
  res: NextApiResponse&amp;lt;BlobResponse&amp;gt;
): Promise&amp;lt;void&amp;gt; {
  const id = req.query?.id as string;
  if (!id) {
    sendError(res, 400);
    return;
  }
  const result = await list({
    limit: 1,
    prefix: `${id}.json`,
  });

  if (!result?.blobs?.length) {
    sendError(res, 404);
    return;
  }
  const { downloadUrl, pathname, url } = result.blobs[0];
  res.status(200).send({ id, downloadUrl, pathname, url });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Deployment
&lt;/h3&gt;

&lt;p&gt;With the end in sight, one last battle. I created a &lt;a href="https://vercel.com/blog/monorepos" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt; to improve organization. I didn't want to clutter my GitHub with several disjointed repositories for the same small project.&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%2F6syjskr2odkgsd9v8pul.png" 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%2F6syjskr2odkgsd9v8pul.png" alt="Monorepo" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I couldn't see any clear way to manipulate the "Build &amp;amp; Development Settings" on a per-directory basis. I did see that they had "Root Directory" support at the project level, though. I created another Vercel project for the same GitHub repository. Then I adjusted the "Root Directory" for both projects. &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%2Fn82jceta867454dw28sf.png" 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%2Fn82jceta867454dw28sf.png" alt="Vercel Projects" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At least since I opted to use Next.js, the presets worked to get the app up.&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%2Fhuomyik14doyacb6rxpd.png" 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%2Fhuomyik14doyacb6rxpd.png" alt="Vercel API Settings" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Retrospective
&lt;/h2&gt;

&lt;p&gt;Looking back, I actually learned a lot. Now I've used Blazor, Next.js, and Vercel. Before I started this journey, I hadn't touched any of them before.&lt;/p&gt;

&lt;p&gt;With that said, I don't think that I would go this route again. I was able to stitch these components together like a baby Frankenstein monster, but I don't necessarily recommend it. &lt;/p&gt;

&lt;p&gt;To save yourself the headaches I suffered,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/deploy-blazor" rel="noopener noreferrer"&gt;Deploy Blazor to Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;If you want to use Vercel, then use Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would encourage you to check out the website for yourself. Maybe share it with your friends and colleagues. I would love any brutally honest feedback. &lt;a href="https://cgc-resume-from-json.vercel.app/" rel="noopener noreferrer"&gt;https://cgc-resume-from-json.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As always if you liked this content, maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/wheeleruniverse/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>nextjs</category>
      <category>vercel</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Navigating AWS Certifications in 2024</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Tue, 30 Jan 2024 16:41:16 +0000</pubDate>
      <link>https://forem.com/aws-builders/navigating-aws-certifications-in-2024-hap</link>
      <guid>https://forem.com/aws-builders/navigating-aws-certifications-in-2024-hap</guid>
      <description>&lt;p&gt;If you are an AWS certificate aficionado like myself then you may already know where this is going. If you're normal then don't scroll away just yet. You may still find value here! &lt;/p&gt;

&lt;h2&gt;
  
  
  Brief History
&lt;/h2&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%2F3jl6ans0ixfj1s18mjom.png" 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%2F3jl6ans0ixfj1s18mjom.png" alt="AWS Certs" width="800" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AWS certification landscape has always, to me, been very simple to navigate. There certifications are divided into 4 levels. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Foundational: Building the Bedrock&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laying the groundwork with essential, non-technical knowledge.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Associate: Bridging the Gap&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mastering intermediate-level, broad knowledge to bridge foundational and advanced concepts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Professional: Elevating Expertise&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaling heights with advanced-level, broad knowledge, demonstrating mastery in complex AWS scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Specialty: Fine-Tuning Focus&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharpening expertise with advanced-level, specialized knowledge in specific AWS domains.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These Associate and Professional certifications are role-based and are related. Elaborating the Solutions Architect Professional (SA Pro) exam is similar to to the Solutions Architect Associate (SAA), yet elevated in every way. The SA Pro exam is much broader (the number of unique services/features covered) and much deeper (the level of detail required for specific services/features). This relation and progression is natural; expected. &lt;/p&gt;

&lt;p&gt;Unlike the role-based paths, the Specialty certifications are built around a strategic area. These are often sought after by experts in the fields they target. That networking wiz you work with may be studying for the Advanced Networking Specialty exam at this very moment. That makes sense, as Enterprise companies may expect that their domain leaders be accredited by AWS as "experts" in those fields they work in. Still I find candidates struggling to comprehend how difficult these exams may be.&lt;/p&gt;

&lt;p&gt;If your only goal is to acquire a specialized certification you may not see the need in sitting the Foundational or Associate level exams beforehand. I would implore you to consider a learning path that uses these easier exams as a stepping stone that will solidify core AWS knowledge that could appear on any exam. A few of these critical services are EC2, IAM, S3, and VPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recent Changes
&lt;/h2&gt;

&lt;p&gt;Before you finalize your 2024 goals you may want to consider the recent changes that AWS has been making to their certification program. &lt;/p&gt;

&lt;p&gt;In November 2023, &lt;a href="https://aws.amazon.com/blogs/training-and-certification/registration-open-for-aws-certified-data-engineer-associate-beta-exam/" rel="noopener noreferrer"&gt;AWS announced the new Data Engineer Associate certification&lt;/a&gt;. At the same time they marked the Data Analytics Specialty for retirement. These two exams had a similar exam scope so the common perception was that the Data Engineer Associate is a scaled back version of the Specialty exam. After all the Data Analytics Specialty earned a reputation for being difficult. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.cbtnuggets.com/blog/certifications/cloud/is-the-aws-data-analytics-worth-it" rel="noopener noreferrer"&gt;CBT Nuggets&lt;/a&gt; said, "an advanced, challenging certification exam and requires extensive knowledge of data analytics technologies and solutions".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/data-engineers-notes/preparing-for-the-aws-certified-data-analytics-specialty-exam-c978a00569fd" rel="noopener noreferrer"&gt;Constantin Lungu&lt;/a&gt; "found it legitimately hard". &lt;/li&gt;
&lt;li&gt;and many more similar stories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given the feedback this change is welcoming, in my opinion. Although, I wish the changes would've stopped there.&lt;/p&gt;



&lt;p&gt;Today, &lt;a href="https://aws.amazon.com/certification/coming-soon/" rel="noopener noreferrer"&gt;AWS announced two more certifications are retiring&lt;/a&gt;! 😲 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database Specialty&lt;/li&gt;
&lt;li&gt;SAP on AWS Specialty&lt;/li&gt;
&lt;/ul&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%2Flwpdluiccqqz99awopqv.png" 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%2Flwpdluiccqqz99awopqv.png" alt="AWS Expiring" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Golden Jacket Club
&lt;/h2&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%2Fr93okrr32ibxdx4k1f8a.jpeg" 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%2Fr93okrr32ibxdx4k1f8a.jpeg" alt="Gold Jacket" width="800" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you didn't know there is a large number of people that have earned all of the AWS certifications, &lt;a href="https://dev.to/wheeleruniverse/fully-aws-certified-the-5-most-common-questions-i-received-1pk8"&gt;like myself&lt;/a&gt;. Many of these people have been given a golden jacket that they boast about on &lt;a href="https://www.google.com/search?q=site%3Alinkedin.com+%22AWS%22+%22Gold%22+%22Jacket%22" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Personally I can't help, but feel distraught about these recent announcements. For many others who trained diligently in this epic quest I would bet the feeling may be mutual. It's unclear if these changes indicate that we can expect new exams soon like we saw with the Data Engineer Associate. Alternatively, it could mean that the gaps in the AWS certification curriculum will surely grow. I can reference that there was already a sizable gap in AWS IoT services before these changes.&lt;/p&gt;

&lt;p&gt;Part of me is afraid that if a slew of more exams are dropped it will feel like starting over just to get back to where I am now. Maybe I will grow through the journey? Maybe not. Yet if the gaps are left then I fear what that would do. Would Enterprise companies be forced to forego AWS services that don't have accredited training around? Independent companies could validate skills in these areas, but it won't carry the same weight Amazon has. What incentive does a candidate have to study services that they won't be tested on over those they definitely will be?&lt;/p&gt;

&lt;p&gt;I wonder if these exams were retired due to lack of participation in these strategic areas, or was it something else? The &lt;a href="https://aws.amazon.com/blogs/training-and-certification/announcing-new-certification-aws-certified-sap-on-aws-specialty/" rel="noopener noreferrer"&gt;SAP on AWS Specialty&lt;/a&gt;, is shocking to me, since it was announced recently in April 2022.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;With these recent changes I am almost certain that Amazon is not done yet. I suspect we will see more certification changes announced in the coming months for some of the reasons I covered in this post. It would be nice if the Training and Certification team could tune us into their broader picture. If they did that we could better arrange our AWS training goals for 2024. &lt;/p&gt;

&lt;p&gt;Until then you should study cautiously. Keeping in mind that the certification you're targeting may go away before you're ready to sit your exam. That SA Pro at the end of your road map may not be the last stop. &lt;/p&gt;

&lt;p&gt;If you liked this content maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/wheeleruniverse/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
      <category>training</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Parenting vs. Cloud Computing: Overcoming Challenges and Embracing Growth</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Mon, 08 Jan 2024 16:32:44 +0000</pubDate>
      <link>https://forem.com/aws-builders/parenting-vs-cloud-computing-overcoming-challenges-and-embracing-growth-1i3b</link>
      <guid>https://forem.com/aws-builders/parenting-vs-cloud-computing-overcoming-challenges-and-embracing-growth-1i3b</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Over the past seven years, I've been immersed in the tech industry, focusing on cloud computing since 2020. Throughout my journey, I've encountered numerous misconceptions about the field from family, friends, and even colleagues. One of the most common is the belief that cloud computing is too challenging to learn. However, as a father to two beautiful boys, I've come to realize that raising children is far more demanding than mastering cloud computing.&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%2F9pl3coljsuqn3osbp3ti.jpeg" 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%2F9pl3coljsuqn3osbp3ti.jpeg" alt="family" width="800" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If I can do it, so can you! With the increasing shift towards cloud technologies, you might need to adapt to stay relevant in the industry. I've witnessed system administrators managing traditional servers being replaced by new talent due to their reluctance to embrace cloud technologies. As &lt;a href="https://www.mclennan.edu/john-maxwell/biography.html" rel="noopener noreferrer"&gt;John Maxwell&lt;/a&gt; once said, "Change is inevitable. Growth is optional."&lt;/p&gt;

&lt;p&gt;Interestingly, many people who are hesitant to learn cloud computing don't question their ability to raise children. In a recent talk titled "&lt;a href="https://www.youtube.com/watch?v=-1YjEQYjpJs&amp;amp;pp=ygUVUGx1cmFsc2lnaHQgSHVyY3VsZWFu" rel="noopener noreferrer"&gt;False External Perception of the Tech Industry&lt;/a&gt;," I explored the accessibility of the tech industry and debunked the myth that it's too difficult to enter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Tasks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Changing a Diaper vs. Creating an AWS S3 Bucket
&lt;/h3&gt;

&lt;p&gt;Picture this - it's 3 am, and you're groggily attempting to change a diaper in the dark while your baby wails in protest. Parents know this all too well. In contrast, creating an AWS S3 bucket, a basic cloud storage service, involves a user-friendly interface and just a few clicks. There's no mess, no crying, and no sleep deprivation to worry about.&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%2F79acg6fcomqq19bxt6r3.png" 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%2F79acg6fcomqq19bxt6r3.png" alt="s3" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gentle Parenting vs. Elasticity in Cloud Computing
&lt;/h3&gt;

&lt;p&gt;The principles of Gentle Parenting, which emphasize empathy, understanding, and respect, share similarities with the concept of elasticity in cloud computing. Both gentle parents and cloud computing systems adapt to changing needs and provide a flexible, nurturing environment. This comparison underscores the importance of adaptability and responsiveness in both parenting and technology.&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%2F6n9sxqig0h5rsinyiyzi.png" 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%2F6n9sxqig0h5rsinyiyzi.png" alt="elasticity" width="788" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Tantrums vs. Understanding AWS Lambda
&lt;/h3&gt;

&lt;p&gt;Although seemingly unrelated, handling tantrums and understanding AWS Lambda both require a problem-solving mindset and adaptability. Parents must assess the situation, identify the root cause, and address the issue while comforting their child. Similarly, when working with AWS Lambda, developers need to grasp the underlying triggers, monitor the function's performance, and fine-tune the code and configuration.&lt;/p&gt;

&lt;p&gt;Patience and adaptability are vital in both scenarios. Parents should remain calm during tantrums, while developers must be open to exploring different solutions when working with AWS Lambda. Embracing these qualities can lead to success in both parenting and cloud computing.&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%2Fszkoneyejnxwnghfwy5u.png" 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%2Fszkoneyejnxwnghfwy5u.png" alt="lambda" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, the experiences of parenting and working in the tech industry, particularly cloud computing, have more in common than one might initially think. Both require adaptability, patience, problem-solving skills, and the ability to learn and grow.&lt;/p&gt;

&lt;p&gt;It's important to remember that you have the potential to succeed in the tech industry, just as you do in parenting. Embrace the challenges, invest time in learning, and trust your abilities. As you embark on this journey, you'll find that the tech industry is not as intimidating as it may seem, and the rewards of a successful career in cloud computing can be just as fulfilling as raising well-adjusted children.&lt;/p&gt;

&lt;p&gt;Don't let misconceptions or self-doubt hold you back. If you can navigate the complexities and challenges of parenthood, you're more than capable of learning and mastering cloud computing skills. As you continue to grow and develop, you'll be better equipped to face the ever-evolving landscape of the tech industry.&lt;/p&gt;

&lt;p&gt;Ultimately, the key to success in both parenting and cloud computing is having the right mindset, persistence, and resilience. By acknowledging that learning and growth are continuous processes, you can foster a positive attitude that will enable you to excel in your chosen career path and create a more fulfilling life for yourself and your family.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploying a Mostly Serverless Website on GCP</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Tue, 26 Jul 2022 14:45:53 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/deploying-a-mostly-serverless-website-on-gcp-134d</link>
      <guid>https://forem.com/wheeleruniverse/deploying-a-mostly-serverless-website-on-gcp-134d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;With the technology that we have today there are many options for hosting a website. We are particularly interested in &lt;a href="https://cloud.google.com/serverless" rel="noopener noreferrer"&gt;Serverless Computing&lt;/a&gt; and opted to run our website in that fashion; well mostly. When we chose &lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Platform&lt;/a&gt;, we were unable to find a serverless relational database system. Our Java Spring Boot application was constructed to use a relational database so shifting to another database offering wasn’t something we wanted to do at this time. The architecture that we chose to implement consisted of the following GCP services. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/cdn" rel="noopener noreferrer"&gt;Cloud CDN&lt;/a&gt; for distribution and caching &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/dns" rel="noopener noreferrer"&gt;Cloud DNS&lt;/a&gt; for DNS registration and routing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/load-balancing" rel="noopener noreferrer"&gt;Cloud Load Balancing&lt;/a&gt; for distribution &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; for the backend logic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/sql" rel="noopener noreferrer"&gt;Cloud SQL&lt;/a&gt; for persistent storage&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/storage" rel="noopener noreferrer"&gt;Cloud Storage&lt;/a&gt; for static website hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This Cloud SQL service is the only non-serverless part of the architecture. &lt;/p&gt;
&lt;/blockquote&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%2Fmkg6jkfdhc8t6p8496e1.png" 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%2Fmkg6jkfdhc8t6p8496e1.png" alt="gcp-architecture" width="453" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Our team is extremely familiar with &lt;a href="https://aws.amazon.com/what-is-aws/" rel="noopener noreferrer"&gt;Amazon Web Services&lt;/a&gt; but wanted to explore other clouds to gain that knowledge and provide a personal comparison. We believe that if we simply remain within our comfort zone then we will never truly grow. Additionally, we could be missing out on a better alternative if we lack the courage to explore our options. The bitter truth is that there is no clear-cut cloud provider that is simply better than the rest. You can rank cloud providers on different aspects and shake up the results. The cloud provider that you choose to use should be the one that provides the most value to you for the project that you’re working on. &lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Deployment
&lt;/h2&gt;

&lt;p&gt;When exploring options on how to deploy the architecture we briefly considered &lt;a href="https://cloud.google.com/deployment-manager/docs" rel="noopener noreferrer"&gt;Google Cloud Deployment Manager&lt;/a&gt;. Upon further investigation we were led to use &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; instead. It was evident that Cloud Deployment Manager did not have the necessary support for the resource types that we were trying to create. Many of the supported resource types were still listed in beta. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://cloud.google.com/deployment-manager/docs/configuration/supported-resource-types" rel="noopener noreferrer"&gt;https://cloud.google.com/deployment-manager/docs/configuration/supported-resource-types&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Terraform had complete support for all the necessary resources listed above. We used Terraform Cloud to track the configuration changes and store the state.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Code Deployment
&lt;/h2&gt;

&lt;p&gt;We realized that if we did not automate code deployments our deployed resources would quickly become stale. In the continued spirit to try something new, we decided to use &lt;a href="https://cloud.google.com/build" rel="noopener noreferrer"&gt;Google Cloud Build&lt;/a&gt; for our code automation. With the lack of our code changes this approach turned out to be free for us. We created a build trigger that would watch our GitHub repository and created a cloudbuild.yaml file that would configure the build job. The documentation claimed that just the Dockerfile was necessary, however, in practice we could not get that to work. We were required to create the yaml file for Cloud Build to successfully update the revision on Cloud Run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options:
  logging: CLOUD_LOGGING_ONLY

steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/rest-java:$COMMIT_SHA', '.']
    timeout: 300s

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/rest-java:$COMMIT_SHA']
    timeout: 300s

  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'rest-java', '--image', 'gcr.io/$PROJECT_ID/rest-java:$COMMIT_SHA', '--region', 'us-central1']
    timeout: 300s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Serverless Relational Database Offerings
&lt;/h2&gt;

&lt;p&gt;When debating the database solution for our application we were really seeking for a scalable serverless database that wouldn’t bill us for idle time. Options like &lt;a href="https://aws.amazon.com/athena" rel="noopener noreferrer"&gt;AWS Athena&lt;/a&gt;, &lt;a href="https://aws.amazon.com/rds/aurora/serverless/" rel="noopener noreferrer"&gt;AWS Aurora Serverless&lt;/a&gt;, and &lt;a href="https://azure.microsoft.com/en-us/services/cosmos-db/" rel="noopener noreferrer"&gt;Azure Cosmos DB&lt;/a&gt; immediately came to mind. We believed that GCP would have a comparable service, yet we could not find one. Even after consulting the &lt;a href="https://cloud.google.com/free/docs/aws-azure-gcp-service-comparison" rel="noopener noreferrer"&gt;GCP cloud service comparison documentation&lt;/a&gt; we were still unable to find any serverless relational database offering. For these reasons we chose the path of least resistance and opted to use a micro instance of Cloud SQL for MySQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating Performance
&lt;/h2&gt;

&lt;p&gt;With our basic application running on GCP for a couple months we have been pleased with the performance and stability of the architecture. Our Cloud Run scaling settings were intentionally set low to meet application expectations as well as to avoid potential bill shock from an autoscaling fiasco. Like &lt;a href="https://aws.amazon.com/apprunner/" rel="noopener noreferrer"&gt;AWS App Runner&lt;/a&gt; and &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; we faced cold start delays when scaling up from zero. We could’ve tried to architect around this with something like an automated trigger or always-running instances. Although, the cold start was not a problem for our use-case. We also felt that provisioning always-running instances defeats the purpose of a serverless service. Can a service be considered “serverless” if it can’t scale down to zero so that you’re not paying for idle compute? We said no. &lt;/p&gt;

&lt;h2&gt;
  
  
  Operating Costs
&lt;/h2&gt;

&lt;p&gt;We found that GCP costs were comparable to AWS when the services were matched one-to-one. However, because the AWS CDN service &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt; is a standalone product a simpler, cheaper, alternative architecture is possible on AWS. The GCP CDN service Cloud CDN requires usage in parallel with their Cloud Load Balancer service, which has a minimum operating cost of ~$18/month, even with no traffic. Finally, our Cloud SQL instance bills hourly as well costing us about ~$9/month. This combined ~$27 doesn’t seem like a lot, yet is drastic when comparing low traffic serverless applications that would cost pennies to host on another cloud provider. &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%2Fngxv4stlwhz9gwv45pa5.png" 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%2Fngxv4stlwhz9gwv45pa5.png" alt="aws-architecture" width="547" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the end we’re glad that we explored this avenue. Our in-depth analysis for this project confirmed that GCP is not the ideal hosting solution for us. The cons for this low traffic serverless application outweigh the pros. We could build a simpler application on AWS with the following changes that would improve the scalability and reduce the cost. Using CloudFront as the CDN removes the need to use the expensive Load Balancing service. Using Aurora Serverless provides us a relational database that can scale to zero so that we’re not paying for idle time. It’s true that Aurora Serverless may face slower query response times when scaling, still we believe it’s worth the cost savings. Lastly, Aurora Serverless would provide the ability to scale past the performance we could have achieved with our single Cloud SQL instance and provided high availability so that we would not be worried about a single point of failure on the database. Perhaps we’ll explore GCP again in the future for another use-case. &lt;/p&gt;

&lt;p&gt;If you liked this content maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheeleruniverse" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://linkedin.com/in/wheeleruniverse" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>cloudskills</category>
      <category>gcp</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Lightsail - AWS Easy Mode</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Mon, 25 Apr 2022 17:06:36 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/lightsail-aws-easy-mode-13ae</link>
      <guid>https://forem.com/wheeleruniverse/lightsail-aws-easy-mode-13ae</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Ellis Parry-Nweye and Justin Wheeler have analyzed the AWS product &lt;a href="https://aws.amazon.com/lightsail/" rel="noopener noreferrer"&gt;Lightsail&lt;/a&gt; and provided their analysis here. The analysis was done by attempting to host a simple Java Spring Boot application on &lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/understanding-instances-virtual-private-servers-in-amazon-lightsail" rel="noopener noreferrer"&gt;Lightsail Instances&lt;/a&gt;. These instances were fronted by a &lt;a href="https://aws.amazon.com/lightsail/features/load-balancing" rel="noopener noreferrer"&gt;Lightsail Load Balancer&lt;/a&gt;. We leveraged &lt;a href="https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-lightsail-now-offers-object-storage-for-storing-static-content/" rel="noopener noreferrer"&gt;Lightsail Object Storage&lt;/a&gt; to stage the jar file that we would deploy. &lt;br&gt;
The described architecture could be created using standard AWS services like &lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;EC2&lt;/a&gt; for the instances, &lt;a href="https://aws.amazon.com/elasticloadbalancing/" rel="noopener noreferrer"&gt;ELB&lt;/a&gt; for the Load Balancing, and &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;S3&lt;/a&gt; for the Object Storage. However, Lightsail aims to provide a single interface to provide the same functionality more simply. &lt;/p&gt;

&lt;p&gt;Lightsail even includes simpler variants of other AWS services not analyzed in the current project to keep the original analysis lightweight.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDN distributions; like &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Containers; like &lt;a href="https://aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;ECS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Databases; like &lt;a href="https://aws.amazon.com/rds/" rel="noopener noreferrer"&gt;RDS&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;The conclusions that were drawn from the project were as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lightsail can be summarized as "AWS Easy Mode ." The service offers everything that many customers will need and is significantly less daunting than using the other AWS services directly. Straightforward monthly pricing simplifies cloud spending similarly to &lt;a href="https://www.digitalocean.com/pricing" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;. Standard AWS pricing can be incredibly complex. &lt;/li&gt;
&lt;li&gt;Lightsail services can be limited and will certainly not be bleeding edge. The configuration options are less than those offered by the aligned AWS services. For instance, using the Lightsail Load Balancer is undoubtedly easier than using the AWS Elastic Load Balancing service. The configuration options provided there are severely restricted. It's not even noticeable which flavor of Load Balancer is used behind the scenes for the Lightsail variant. When the ELB service allows the user to choose the type used first, Application, Gateway, or Network. Then the configuration options explode further to provide complete control. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Facts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Instances
&lt;/h3&gt;

&lt;p&gt;The process to create an instance was plain and simple. The configuration options seemed lacking compared to the overwhelming number of those given within the EC2 console. There is even an option to create multiple instances in a single shot by modifying the quantity input box. Surprisingly the console requires an Availability Zone selection meaning that the bulk creation can only create multiple instances within a single AZ. That logic goes against AWS best practices and will hurt the architecture's high availability/fault tolerance.&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%2Fgrl7hp9hsw6ypmwfnm3x.PNG" 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%2Fgrl7hp9hsw6ypmwfnm3x.PNG" alt="Lightsail Instances" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One nice benefit of Lightsail instances is that they offer pre-configured click-to-launch applications and stacks that can drastically benefit teams using those services. Some of the popular offerings include WordPress and a LAMP stack. This feature was not explored during the project, perhaps only because a Java solution was not available. Albeit Java is not hard to configure either. The Amazon Linux OS option was selected, and &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/ec2-install-extras-library-software/" rel="noopener noreferrer"&gt;amazon-linux-extras&lt;/a&gt; was used to install the required software. &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%2Fhzvgc1rzukq1vo6gve0d.PNG" 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%2Fhzvgc1rzukq1vo6gve0d.PNG" alt="Lightsail Instance Apps" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every Lightsail instance comes with a built-in firewall relatable to a security group in EC2. HTTP and SSH are permitted by default, which doesn't feel very secure. Since this project did not require HTTPS, these default rules were suitable. Along with the firewall, a public IP and private IP can be found on the networking tab of the console. Interestingly a VPC did not have to be created. The assumption is that Lightsail is leveraging VPCs within an AWS-owned account. &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%2F1c0xdgtkvjzwnulasugk.PNG" 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%2F1c0xdgtkvjzwnulasugk.PNG" alt="Lightsail Instance Networking" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving on, Lightsail makes creating snapshots easy. These snapshots are a helpful backup tool but are required to resize the instance as well. An insufficient instance was launched for the first attempt that had to be resized to cope with the Java Spring Boot application's requirements. That discovery only occurred after the instance was bootstrapped. Unlike EC2, you will need to snapshot the instance and then launch a brand-new instance from that snapshot if you would like to scale up. Other helpful options were noted, such as the ability to copy to another region or even export the snapshot to EC2. &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%2Favztw2mwqdm72z7zj02h.PNG" 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%2Favztw2mwqdm72z7zj02h.PNG" alt="Lightsail Instance Snapshots" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some final notes of importance are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance history is tracked, which can be incredibly useful in debugging endeavors and auditing purposes.&lt;/li&gt;
&lt;li&gt;Instance metrics are tracked, which can aid in debugging tasks.
&lt;/li&gt;
&lt;li&gt;It's easy to connect to the instance from the console. &lt;/li&gt;
&lt;li&gt;IPv6 networking is also supported. &lt;/li&gt;
&lt;/ul&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%2F7njynog32cpw1lhommk6.PNG" 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%2F7njynog32cpw1lhommk6.PNG" alt="Lightsail Instance History" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Load Balancer
&lt;/h3&gt;

&lt;p&gt;For an instance, the Load Balancer was easy to create. Unlike the Elastic Load Balancing service, the Lightsail Load Balancer does not require Target Groups to be made, even if they may be used behind the scenes. The LB provides a DNS name that can be used out of the box. HTTP is enabled by default with the option to allow HTTPS if you conduct a couple of extra steps. That wasn't done since HTTP was sufficient for the use case. &lt;/p&gt;

&lt;p&gt;Unfortunately, it was impossible to disable HTTP, and there was no option to redirect HTTP to HTTPS like many services offer. This functionality is available for CloudFront and the ELB service, so, oddly, it was not included here. Further analysis uncovered additional features like session persistence that could be enabled with a simple checkbox. Overall, the Lightsail Load Balancer provided the required functionality with ease and without the bells and whistles that could overwhelm. &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%2Fzne9u22hionl6oq9btxj.PNG" 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%2Fzne9u22hionl6oq9btxj.PNG" alt="Lightsail Load Balancer" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Object Storage
&lt;/h3&gt;

&lt;p&gt;Object storage was not used that much during the experiment. It was primarily used to stage the jar file required to run the Java Spring Boot application. It's no surprise that creating a bucket was easy. The domain name is provided when the storage is made s3. This means that S3 is used to support Lightsail Object Storage.&lt;/p&gt;

&lt;p&gt;Although, there were crucial differences. Upon creation, a user must choose a bundle that matches the storage and transfer they expect. This is probably to simplify the billing, yet it could mean that Lightsail Object Storage could be more expensive than S3 if you over-provision. Alternatively, if you under-provision, then overage fees per GB apply. &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%2Fpr9se9edfg2bxgid012b.PNG" 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%2Fpr9se9edfg2bxgid012b.PNG" alt="Lightsail Object Storage Pricing" width="598" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, a good number of features exist for the service, like permission management, metrics, versioning, and logging. These are a subset of what is offered by S3 and are likely very similar. Except most of these were not explored too much during the exercise. Permissions default to least privilege, again like S3. The instances that needed access to the files had to be granted access deliberately. The S3 CLI worked against the bucket when connecting from a Lightsail instance but connecting from outside Lightsail did not work. An invisible barrier separates the Lightsail Object Storage buckets from the S3 buck. There was the discovery of a migration path to S3 if the Lightsail feature set was outgrown. &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%2F9wq57wxgn94y3cxrcokd.PNG" 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%2F9wq57wxgn94y3cxrcokd.PNG" alt="Lightsail Object Storage" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, Lightsail is an excellent service for the right people. It can be thought of as "AWS Easy Mode" as the features and configuration options will surely not overwhelm. The centralized console is polished without the pesky need to remember a hundred acronyms. The pricing is clear, which makes budgeting and planning a breeze. Lightsail offers migration paths that can be followed if the customer ever requires something more without starting over. &lt;/p&gt;

&lt;p&gt;To elaborate on “the right people” since it may be broad. The people that would benefit the most from Lightsail would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers for prototyping work.&lt;/li&gt;
&lt;li&gt;New AWS users.&lt;/li&gt;
&lt;li&gt;Small scale companies.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>cloud</category>
      <category>cloudskills</category>
    </item>
    <item>
      <title>Fully AWS Certified - The 5 Most Common Questions I Received</title>
      <dc:creator>Justin Wheeler</dc:creator>
      <pubDate>Thu, 17 Mar 2022 16:27:27 +0000</pubDate>
      <link>https://forem.com/wheeleruniverse/fully-aws-certified-the-5-most-common-questions-i-received-1pk8</link>
      <guid>https://forem.com/wheeleruniverse/fully-aws-certified-the-5-most-common-questions-i-received-1pk8</guid>
      <description>&lt;h3&gt;
  
  
  The 5 Most Common Questions I Received
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;What is the point of pursing every AWS certification?&lt;/li&gt;
&lt;li&gt;What training material did I use?&lt;/li&gt;
&lt;li&gt;How would you rank the AWS certifications by difficulty?&lt;/li&gt;
&lt;li&gt;Which certification path is right for me?&lt;/li&gt;
&lt;li&gt;How do I enter the cloud computing industry?&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;After four gruelling years of studying, along with balancing my other responsibilities, I was able to achieve all of the available AWS certifications. Here is my &lt;a href="https://www.credly.com/users/justin-wheeler.8cf239e4/badges" rel="noopener noreferrer"&gt;Credly&lt;/a&gt; if you would like some proof. When I shared this accomplishment on &lt;a href="https://www.linkedin.com/in/wheelerswebservices/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; I received a lot of visibility! &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%2F1ytnv0nzcacnyi3y2b6i.PNG" 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%2F1ytnv0nzcacnyi3y2b6i.PNG" alt="LinkedIn Screenshot" width="472" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can imagine, this attention brought a ton of congratulations and numerous questions. Often these questions were very similar, which gave me the idea to write this post. In doing so I aim to: 1.) Save myself from repeating further; 2.) Provide helpful answers to a greater audience than my LinkedIn inbox. Without further delay the five most common questions I received are after earning all of the AWS certifications are...&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%2Ftd78m8pmkbwh1pecmcnw.png" 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%2Ftd78m8pmkbwh1pecmcnw.png" alt="AWS Badges" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What is the point of pursing every AWS certification?
&lt;/h3&gt;

&lt;p&gt;I would firstly say that there is no career where it makes sense to obtain every single AWS certification. This is because AWS certifications are created for specific roles. For example, a Cloud Architect should not concern themselves with the Machine Learning Specialty certification (unless they're going to be architecting an ML solution). &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%2Fogqwakl3yrijmy6cnsyx.PNG" 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%2Fogqwakl3yrijmy6cnsyx.PNG" alt="A Cloud Guru Certification Guide" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Complete &lt;a href="https://go.acloudguru.com/rs/194-UHP-609/images/Cert-Guide-AWS-2020.pdf" rel="noopener noreferrer"&gt;A Cloud Guru Certification Guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that said, I had created a personal goal for myself to achieve all of the certifications. That's because I actually found joy in learning about each and every piece of AWS. These certification exams made me confident that I was retaining the information that I was learning through my studying. They also provided some information on the "unknown unknowns" so that I could close those gaps that I might not have realized I had.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What training material did I use?
&lt;/h3&gt;

&lt;p&gt;I mentioned the training providers that I used in my LinkedIn post as a way of giving credit, but I will go more in depth here so that you can understand how I actually used each provider. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning Content&lt;/strong&gt;&lt;br&gt;
I have used the following providers and believe that A Cloud Guru has the best content. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://acloudguru.com/" rel="noopener noreferrer"&gt;A Cloud Guru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aws.training/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudacademy.com/" rel="noopener noreferrer"&gt;Cloud Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/learning/" rel="noopener noreferrer"&gt;LinkedIn Learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pluralsight.com/" rel="noopener noreferrer"&gt;Pluralsight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.udemy.com/" rel="noopener noreferrer"&gt;Udemy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.whizlabs.com/" rel="noopener noreferrer"&gt;Whizlabs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Pluralsight acquired A Cloud Guru in 2021.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Cloud Guru's content is enough to pass any AWS certification exam. Their platform has some really nice features like hands-on labs and an exam simulator that does a really good job of simulating a real AWS exam for practice. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Documentation&lt;/strong&gt;&lt;br&gt;
AWS has amazing &lt;a href="https://docs.aws.amazon.com/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; that provides great technical details. It is often critical when working in the real world or performing a hands-on lab. Although, you can easily get lost in technical details that will not help you pass an exam. For this reason I typically refer to the documentation only if A Cloud Guru suggests that a particular section is important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Whitepapers&lt;/strong&gt;&lt;br&gt;
AWS &lt;a href="https://aws.amazon.com/whitepapers/" rel="noopener noreferrer"&gt;whitepapers&lt;/a&gt; are informational documents that describe best practices in relation to AWS services around a specific topic. Some of these papers can provide answers to the questions found on AWS exams. However, there are too many whitepapers to read them all. Again, A Cloud Guru comes to the rescue by highlighting any whitepapers that should be read for a given exam within their courses. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice Tests&lt;/strong&gt;&lt;br&gt;
I have used the following providers and found that Whizlabs has the best practice tests. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://acloudguru.com/" rel="noopener noreferrer"&gt;A Cloud Guru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aws.training/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudacademy.com/" rel="noopener noreferrer"&gt;Cloud Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.whizlabs.com/" rel="noopener noreferrer"&gt;Whizlabs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whizlabs offers multiple unique tests so that you can be guaranteed to see various questions from all of the exam domains to highlight any weak spots in your knowledge. &lt;/p&gt;

&lt;p&gt;This varies from A Cloud Guru and Cloud Academy as they only offer a single test that is generated from a question base that is larger than the number of the questions on the exam. Meaning if you take their exam multiple times you will likely see different questions, however, this can't be guaranteed, and you will definitely see repeat questions if you take the test enough times. &lt;/p&gt;

&lt;p&gt;Unfortunately the AWS official practice tests are the worst for studying since they don't provide information on questions that are wrong or explanations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flashcards&lt;/strong&gt;&lt;br&gt;
I have only used &lt;a href="https://quizlet.com/" rel="noopener noreferrer"&gt;Quizlet&lt;/a&gt; for online flashcards. Whenever I review practice tests I will look at all questions for services or concepts that I was unsure about; even if I got the question correct. Then I create flashcards to solidify that knowledge. &lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. How would you rank the AWS certifications by difficulty?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ I mentioned this earlier, yet I will reiterate it again. AWS certifications are often associated with a specific role.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That would mean that if you are proficient in that role you will most likely have a much easier time passing that exam than if you are brand new to it. If you're working as a Cloud Architect, than the architecture exams should be easier for you than a Developer taking the same exam. For this reason, these difficulty rankings are based on my personal experience. If you have a different background your opinion may vary and that's expected since you may have domain knowledge that I don't (or did not before studying for the exam).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is my ranking from easiest to hardest:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;li&gt;Developer Associate&lt;/li&gt;
&lt;li&gt;Solutions Architect Associate&lt;/li&gt;
&lt;li&gt;SysOps Administrator Associate&lt;/li&gt;
&lt;li&gt;Security Specialty&lt;/li&gt;
&lt;li&gt;SAP on AWS Specialty&lt;/li&gt;
&lt;li&gt;Database Specialty&lt;/li&gt;
&lt;li&gt;DevOps Engineer Professional&lt;/li&gt;
&lt;li&gt;Data Analytics Specialty&lt;/li&gt;
&lt;li&gt;Solutions Architect Professional&lt;/li&gt;
&lt;li&gt;Advanced Networking Specialty&lt;/li&gt;
&lt;li&gt;Machine Learning Specialty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before I started studying for the Advanced Networking and Machine Learning certifications, I had very little knowledge in these areas. I had to do a lot of studying on the domain before I even looked at any AWS aspects of these so they were extremely difficult for me. Most of the content was brand new to me. If you have prior experience with networking or machine learning concepts you will likely have an easier time tackling these exams than I did.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Which certification path is right for me?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ I recommend that everyone should start with the Cloud Practitioner certification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I know a popular opinion in the industry is that this foundational certification is a waste of time, but I see value in it. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides foundational experience about cloud computing and AWS&lt;/li&gt;
&lt;li&gt;Provides experience on the AWS exam format&lt;/li&gt;
&lt;li&gt;Easiest and cheapest AWS exam offered&lt;/li&gt;
&lt;li&gt;If you pass, you will receive a 50% off voucher for your next exam&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next certification exam after Cloud Practitioner will vary depending on your career goals. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want to be an Executive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🛑 Stop! Cloud Practitioner is really all you need. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want to be an Architect&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;li&gt;Solutions Architect Associate&lt;/li&gt;
&lt;li&gt;Security Specialty&lt;/li&gt;
&lt;li&gt;Solutions Architect Professional&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;You want to be a Developer&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;li&gt;Developer Associate&lt;/li&gt;
&lt;li&gt;SysOps Administrator Associate&lt;/li&gt;
&lt;li&gt;DevOps Engineer Professional&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;You want to be a Data Analyst&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;li&gt;Solutions Architect Associate&lt;/li&gt;
&lt;li&gt;Data Analytics Specialty&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;You want to be a Machine Learning Engineer&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Practitioner&lt;/li&gt;
&lt;li&gt;Solutions Architect Associate&lt;/li&gt;
&lt;li&gt;Machine Learning Specialty&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are my opinions based on my experiences.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. How do I enter the cloud computing industry?
&lt;/h3&gt;

&lt;p&gt;I always say that you can enter the industry by acquiring four things. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a) Certification(s)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cloud computing industry is composed of multiple roles. It would be better if you decided, which role you would like to target and know that some roles are more difficult to enter than others. &lt;/p&gt;

&lt;p&gt;For instance, it may be easier to enter a company as a Cloud Developer that transitions into a Cloud Architect than it may be to enter that same company as a Cloud Architect. Typically Cloud Architects must make really important decisions that can be extremely impactful to companies. For these reasons the barrier to enter is a lot higher. &lt;/p&gt;

&lt;p&gt;If you would like some help deciding which role is right for you then I would recommend checking out the &lt;a href="https://learn.acloud.guru/series/so-you-want-to-be-a" rel="noopener noreferrer"&gt;So You Want To Be A&lt;/a&gt; series from A Cloud Guru.&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%2Fijrkq8fe6il3qh69chwl.PNG" 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%2Fijrkq8fe6il3qh69chwl.PNG" alt="So You Want To Be Screenshot" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Certifications can be great tools to help make you stand up, but it won't be enough on it's own. It's also crucial that you actually understand the content that the certification is testing or you won't get far at all. Using exam dumps (cheating) will only cheat yourself.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b) Hands-on Experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This might be the most important. You need to actually know how to use these things you're learning so much about. If you are studying S3 then you should use S3. Create an AWS account and use it often. Use the console, the CLI, the SDK, etc. Explore the various services, features, and troubleshoot bugs when you run into them (you will run into them). This is truly the only way you will actually learn. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ I recommend that if you're worried about using your own AWS account, you consider using a lab or playground environment that is managed by somebody else.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://acloudguru.com/platform/labs" rel="noopener noreferrer"&gt;A Cloud Guru Labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudacademy.com/platform/hands-on-labs-challenges-1/" rel="noopener noreferrer"&gt;Cloud Academy Labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qwiklabs.com/" rel="noopener noreferrer"&gt;Qwiklabs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;c) Proof of Experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Saying you can do something and proving it are very different things. Often times you will need to build a portfolio or repository that you can use to showcase your work. Most companies will not give you a technical interview where you need to login to some AWS environment and complete some task like they might do for a programming assessment. Companies will want you to provide some examples of cloud projects that you've created. &lt;/p&gt;

&lt;p&gt;This proof can literally be a large number of things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write a CloudFormation template that builds AWS infrastructure&lt;/li&gt;
&lt;li&gt;Write a shell script that uses the AWS CLI&lt;/li&gt;
&lt;li&gt;Write a python program that reads from a DynamoDB&lt;/li&gt;
&lt;li&gt;Write a blog about a new AWS service that you're using&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are having a hard time thinking of some project to build you should check out the &lt;a href="https://acloudguru.com/blog/tag/cloudguruchallenge" rel="noopener noreferrer"&gt;#CloudGuruChallenge&lt;/a&gt; from A Cloud Guru. These are realistic problems that don't provide a solution. With these challenges you will get to architect and build the solution yourself.&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%2F6rbzlaaw24uy53jj38c2.PNG" 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%2F6rbzlaaw24uy53jj38c2.PNG" alt="Machine Learning Cloud Guru Challenge Screenshot" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d) Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Social media like LinkedIn can be an incredible tool to help you enter the industry. If you follow the other steps and are actively interacting with the community through comments, posts, blogs, etc. then you will likely draw recruiters to you. When the recruiter reaches out to you, it places you in a much better position than as if you were to apply for the same job from their careers webpage. &lt;/p&gt;

&lt;p&gt;You should also be able to breeze through the interview process since you will have the certifications that highlight your experience coupled with the hands-on experience to back that up. You can supply your past projects as proof of experience to the recruiters to stand out even further. &lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I hope these answers will help you in some way. I would love to hear from you and what other questions you may have for me. Good luck on your cloud journey!&lt;/p&gt;

&lt;p&gt;If you liked this content maybe you would like to &lt;a href="https://www.buymeacoffee.com/wheelersweb" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/wheelerswebservices/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>career</category>
      <category>cloud</category>
      <category>training</category>
    </item>
  </channel>
</rss>
