From Static HTML to Cloud Infrastructure: My Cloud Resume Challenge Journey
When I first read about the Cloud Resume Challenge, I knew that it would be tough. I chose to complete the challenge using Azure. I’d learned HTML, CSS, and JavaScript years ago via freecodecamp.org, so the resume portion of the challenge wasn’t as difficult as it may be for some. I’m a Senior IT Specialist, not a developer, so my web development skills have declined over the years. The resume took a few hours. The cloud infrastructure behind it took about two weeks of troubleshooting, reading documentation, and staring at GitHub Actions logs at midnight. Here's what I learned, and what actually surprised me along the way.
Starting Simple, Getting Complicated Fast
The challenge starts deceptively easy: build your resume in HTML and CSS. As an IT professional with a background in DoD environments, I'm comfortable with systems and tools, but I'd never built a full cloud-deployed web application from scratch. I figured HTML would be the warm-up lap before the real race.
What I didn't expect was how quickly the layers stacked up. By step 8, I was configuring a CosmosDB instance with the Table API in serverless mode. By step 12, I was writing ARM templates to define infrastructure as code. By step 14, I had GitHub Actions automatically running tests and deploying to Azure on every push to main. None of that was in my job description, nor was it a requirement for my degree program (B.S. Information Technology).
What Surprised Me About Azure Functions and CosmosDB
The biggest surprise with Azure Functions was how much the runtime version matters. I chose Node.js as my runtime and immediately ran into version compatibility issues. GitHub Actions was warning me that Node 20 was deprecated, my ARM template was pinned to Node 18, and Azure's supported versions didn't include Node 24 yet. Getting all three, the Function App, the ARM template, and the GitHub Actions workflow, to agree on the same runtime version was more work than I expected.
CosmosDB surprised me differently. I chose the Table API expecting it to behave like a simple key-value store. What I didn't anticipate was how the partition key and row key structure worked in practice, and how the SDK handled the case where a row doesn't exist yet. My first version of the visitor counter crashed on the very first visit because there was no row to read. I had to restructure the logic to catch that 404, create the row, and return a count of 1 — then on every visit after that, read, increment, and update. A small bug, but it took longer than I'd like to admit to track down.
What IaC Solved That Manual Clicking Couldn't
Before this project, my Azure experience was mostly clicking through the portal to configure things. That works fine once. But the moment I needed to tear something down and rebuild it, which happened more than once during debugging, I realized how fragile manual configuration is. I couldn't remember exactly which settings I'd changed, in what order, or why.
By defining the infrastructure in an ARM template, every architectural decision became explicitly documented in code rather than hidden behind manual portal configurations. The storage account, the consumption plan, the Function App, and the application settings are all defined in one JSON file. When something broke, I could delete the resource group entirely and redeploy from scratch in minutes, knowing that it would come out the same way every time. That's a level of reliability I didn't have before, and I can see immediately why it matters in enterprise environments.
What the CI/CD Pipeline Taught Me About Automation
Setting up GitHub Actions was the part of this project that changed how I think about software deployment. Before this, deploying meant manually uploading files or clicking a button in a portal. Now, every time I push code to main, the pipeline automatically installs dependencies, runs my Jest tests, deploys the ARM template, and publishes the Function App. If the tests fail, nothing deploys. The code has to prove itself before it goes anywhere.
The lesson that stuck with me: automation doesn't just save time; it enforces discipline. (I’m looking forward to digging deeper into Terraform.) I can't accidentally deploy broken code because the pipeline won't let me. That's not something you get from clicking around in a portal.
I also learned the hard way not to skip the test step. Early on, I had a workflow that deployed even when tests failed, because I hadn't wired up the dependency correctly. A few bad deployments later, I fixed the pipeline so that deployment is blocked entirely if any test fails. That's the way it should work.
The Bug That Took the Longest to Fix
Hands down: the ResourceGroup name must be set for error in the ARM deploy step. The error message made it sound like I was missing a value in my workflow file, but the resource group secret was set correctly. The real issue was that azure/arm-deploy needed explicit Azure login credentials via a service principal — just passing the subscription ID and resource group name wasn't enough. Once I created the service principal with az ad sp create-for-rbac and added the JSON output as an AZURE_CREDENTIALS secret, everything clicked into place.
It's the kind of bug where the error message points you in completely the wrong direction, and the fix is buried in a GitHub issue thread from two years ago. Finding it felt like a genuine victory.
Next Steps
- Complete the AZ-104
- Dive deeper into Terraform
- Solidify Docker skills
Final Thoughts
The Cloud Resume Challenge is well named in my opinion. It's not just a resume project; it's a tour of modern cloud development practices compressed into a single build. I came in as an IT professional comfortable with systems and support. I came out having written infrastructure as code, built and tested a serverless API, and set up automated deployment pipelines. Those are skills I'll carry into every role from here on.
If you're considering doing it: start! The hard parts are where the learning is; get frustrated but keep pushing.
Top comments (0)