Skip to main content

Command Palette

Search for a command to run...

The Cloud Resume Challenge: A Field Report

What a 25-year infrastructure veteran learned building a serverless Azure project from scratch

Updated
10 min read
H
I've spent 25 years at the intersection of technology and business outcomes — as an infrastructure architect, vCIO, and solutions advisor across enterprise, MSP, and owner-operated environments. I write about cloud, Microsoft 365, automation, and what it actually looks like to help a department solve a problem with technology rather than just deploy a tool. Currently hands-on at a managed services provider while pursuing AZ-104 and CISSP reinstatement.

The Cloud Resume Challenge: A Field Report

I've been in IT for 25 years. Active Directory, Exchange, RDS farms, SD-WAN, PKI infrastructure. I've built all of it across enterprise and MSP environments. When I started job hunting, I expected that experience to open doors.

It did, just not the right ones. The callbacks I got were for junior roles. Hands-on, on-prem, break-fix work. The roles that matched my experience level wanted recent cloud skills I couldn't back up with anything concrete. I had the AZ-900. I understood the concepts. But understanding concepts and having something to show for it are two different things.

That's what led me to the Cloud Resume Challenge.

If you haven't heard of it, the Cloud Resume Challenge was created by Forrest Brazeal as a hands-on project for people who want to prove cloud skills rather than just claim them. You build a resume website hosted entirely in the cloud: static frontend, serverless backend, NoSQL database, CI/CD pipelines. The point isn't the resume. The point is everything you have to figure out to get it live.

I went in confident. I've seen enough infrastructure to know that cloud is mostly familiar concepts in unfamiliar packaging. What I didn't expect was how many of the official docs I found were already out of date, and how much that would matter.

For a full view of the architecture, see the interactive diagram here.


Chunk 1: Frontend — Static Site on Azure Storage

The frontend is the straightforward part of the challenge, or it's supposed to be. You build a static HTML/CSS resume, host it in an Azure Storage account with static website hosting enabled, and put a CDN in front of it for HTTPS and custom domain support.

The HTML and CSS are Claude-assisted. Front-end development isn't where I'm headed, so I used Claude to generate a clean starting point and focused my energy on the infrastructure and automation layers.

The CDN is where I hit my first wall.

The challenge documentation and most online references still point to Azure CDN Standard classic profiles. When I went to set one up in the portal, the option simply wasn't there. A little digging confirmed why: Azure CDN Classic was retired in August 2025. Microsoft's replacement is Azure Front Door Standard, which works fine but runs about $35 a month. For a portfolio project, that's a non-starter.

I already had a Cloudflare account from other work, so the answer was obvious. Cloudflare's free tier handles HTTPS termination, custom domains, and CDN, everything I needed. A CNAME record pointing to the Azure Storage static site endpoint, a couple of Cloudflare settings, and it was done.

The lesson here isn't that Azure CDN is bad. It's that the official CRC documentation still references workflows that no longer exist, and if you're following the challenge as written you'll hit this wall. Know your alternatives before you start Chunk 1.


Chunk 2: Backend — Azure Function, CosmosDB + GitHub Organization

The backend goal is straightforward: an HTTP-triggered Azure Function that reads a visitor count from CosmosDB, increments it by one, writes it back, and returns the updated count as JSON. One function, one responsibility, one API endpoint. Simple by design.

The function itself is Python. I'm not a Python developer, so I used Claude to generate the initial code, sanity checked it, and validated it against the expected behavior with unit tests. Python is on my list. The second pass on this project will include writing it myself.

Before writing a line of code I made a deliberate decision about repo structure: separate repositories for frontend and backend. A monorepo with folders would have worked, but separate repos felt right for a few reasons. Frontend and backend deploy independently. Each has its own pipeline triggered by its own push events. And practically speaking, more repos means more reps with GitHub, and I needed the reps. Intuition and deliberate practice aren't mutually exclusive.

The CosmosDB regional availability issue is where I made my first avoidable mistake, and where I learned something genuinely useful about how Azure organizes its infrastructure.

I had deployed everything else, the Function App, storage, and supporting resources, in South Central US. When I went to deploy CosmosDB, I looked at the available regions, didn't see South Central US in the recommended list, and moved on to the next closest option: Central US. What I didn't do was scroll down past all the European and Asia Pacific regions to find more US regions. CosmosDB Table API is available in South Central US. It just isn't surfaced in the recommended section of the portal.

Here's why. Azure divides regions into two categories: recommended and alternate. Recommended regions support the broadest range of services and availability zones. Alternate regions extend Azure's footprint within the same data residency boundary and exist primarily to optimize latency and support disaster recovery, but they don't support availability zones, so Azure deprioritizes them in the portal UI. South Central US is an alternate region. It works fine for CosmosDB Table API. The portal just doesn't lead with it.

The result is a cross-region architecture: the Azure Function in South Central US talking to CosmosDB in Central US. Once I understood the mistake I had a decision to make: fix it or leave it. I left it. The architecture works, the cross-region latency is negligible for a low-traffic portfolio project, and the cost implications are minimal. At production scale that calculus changes, but engineering for scale you don't have is its own kind of mistake.

The lesson isn't just "scroll down." Know the difference between recommended and alternate regions before you make deployment decisions. The portal UI implies completeness. It doesn't provide it.


Chunk 3: Frontend/Backend Integration + Tests

With the backend API live and the static site deployed, Chunk 3 is where the two halves connect. A small piece of JavaScript in the frontend fetches the visitor count from the Azure Function endpoint and displays it on the page. Every page load triggers the function, which reads the current count from CosmosDB, increments it, writes it back, and returns the updated value as JSON. The JavaScript receives it and drops it into the DOM.

The JavaScript is Claude-assisted, same situation as the Python. I used it to generate the fetch code, sanity checked it, and it worked first try. No CORS issues, no endpoint confusion. Sometimes things just work.

JavaScript is on the same list as Python. The second pass will include writing it myself.

The tests are where I spent the most time in this chunk. Two unit tests covering the critical path of the visitor counter function: one verifying the response shape returns valid JSON with a count key, one verifying the core logic increments the count by one. Both mock out the CosmosDB call entirely so they run against the function logic in isolation with no real infrastructure needed.

The friction point was one I should not have overlooked: missing pytest in the requirements.txt file. The pipeline failed, the error pointed straight at it, and five minutes later it was fixed. The only real cost was the mild embarrassment of a self-inflicted wound that obvious.

The distinction between unit tests and integration tests is worth understanding if you're new to testing. Unit tests answer "does my code do what I think it does?" Integration tests answer "does the whole system work?" You want both. A proper smoke test, hitting the live endpoint after deployment and verifying the whole stack responds correctly, is the right next step and a natural follow-up post.


Chunk 4: Infrastructure as Code + CI/CD

Chunk 4 is where everything gets automated: infrastructure defined as code and deployments triggered by Git.

The Bicep file came together with Claude's help, consistent with the rest of the code in this project. What's worth noting is how I used it: not as a deployment artifact, but as a learning tool. I didn't use the Bicep file to deploy the infrastructure. I built the project manually first, then used the Bicep file as a reference, something I can hold up next to the actual deployment and use to understand how the two map to each other. The second pass will deploy from Bicep. That's the point.

The two GitHub Actions pipelines, one in each repo, both triggered on push to main, came together quickly. Having already worked through the backend pipeline logic in Chunk 3, the YAML structure was familiar. The frontend pipeline deploys static files to Azure Storage. The backend pipeline runs the pytest unit tests and if they pass, deploys the Azure Function. Both pipelines authenticate to Azure using credentials stored as GitHub secrets, straightforward for a first pass. OIDC federated identity is the more secure approach and is on the list for the second pass.

The Cloudflare cache purge is worth a mention here since the frontend pipeline is where you'd typically add it. The concern is that Cloudflare might serve stale cached files after a deployment. I tested it instead of assuming: pushed a change and checked the live site. Updates were reflecting without any purge step. After some research the reason turned out to be straightforward. Cloudflare doesn't cache HTML by default, only static assets like CSS, JavaScript, and images. Since my resume changes were HTML only, there was never anything in the Cloudflare cache to purge. If your pipeline deploys changes to CSS or JavaScript, a cache purge step is worth adding. For HTML-only changes it's a non-issue.


Wrap-Up: What I Took Away

The most valuable thing this project confirmed is something I suspected going in but needed to prove to myself: the concepts transfer.

If you've spent years managing Active Directory, designing multi-site networks, or running Exchange migrations, you already think in systems. You understand dependencies, failure modes, and why architecture decisions made early create constraints later. Cloud doesn't replace that instinct. It just gives it a new surface to work on.

The gap that job descriptions make look enormous is mostly a gap in reps, not in fundamentals. Azure Functions are just code running on infrastructure someone else manages. CosmosDB is just a database with a different API. GitHub Actions is just automation triggered by events. The details are genuinely not that difficult to figure out once you stop treating cloud as a foreign discipline and start treating it as a foreign language. It mirrors what you already know. Your on-prem experience isn't a liability, it's context that most cloud-native engineers don't have. And the gap is smaller than job descriptions make it seem.

That said, the utility of the project matters. I didn't do this to check a box. I did it because building something real, with real constraints, is the only way to find out where your understanding actually breaks down. You don't learn that CosmosDB Table API is available in South Central US but buried under alternate regions in the portal by reading documentation. You learn it by deploying something and hitting the wall.

If I did it again, I'd start with the architecture diagram before writing a single line of code. Having the full picture in front of you before you start makes every decision, region selection, resource grouping, pipeline structure, faster and more deliberate. A diagram forces you to think through the whole system before you're committed to any part of it. I didn't do that, and I felt the cost of it.

What's next: I'm going to rework the entire project from scratch. More reps, more automation, and to lock in understanding rather than just completion. The first pass teaches you what you don't know. The second pass is where you actually learn it.

10 views