Back to Blogs

Deploy Like You Mean It: A DevOps Uplift Story

We rebuilt the entire deployment platform behind KloudSync — pipelines, infrastructure, identity, and an API gateway front door — while production kept serving traffic. Here's what we changed, what surprised us, and the thinking behind every choice.

Developer workstation showing version control and deployment pipelines on screen

The Deploy That Did Too Much

Every project starts with a deployment setup that feels fine. Ours did too. Push code, pipeline runs, everything deploys. Simple.

The problem was the word everything. Every code push didn't just ship the application — it also re-provisioned the infrastructure around it. Storage, monitoring, key vaults, the compute plan — all redeployed on every push, every time, whether they'd changed or not.

It worked, but it was the deployment equivalent of demolishing and rebuilding your kitchen every time you cook dinner. Slow, noisy, and one day — risky. Infrastructure changes and code changes have completely different blast radii, and our pipeline treated them as the same thing.

So we drew a line. Several, actually.

Rule One: Code Ships Often, Platforms Change Rarely

The first decision was to split deployments by rate of change:

  • Application deployments happen constantly — every push to the main branch builds, tests, and ships code automatically.
  • Environment infrastructure (storage, monitoring, vaults) changes occasionally — so it's gated behind an explicit opt-in checkbox on the same pipeline.
  • Shared platform infrastructure (the API gateway, the shared compute plan) changes rarely — so it gets its own pipeline that can only be triggered manually, with per-resource checkboxes to deploy exactly what you intend and nothing else.
Application Pipeline
▶ Automatic — every push
Build & test — the full unit suite must pass before anything moves
Package — one artifact, deployed to the target environment
Provision infra — only if the opt-in checkbox was ticked
Publish the API — registers the app's API and policies on the gateway, every run
Platform Pipeline
✋ Manual only — never on push
Select resources — checkboxes for each platform component
Preview — a what-if diff shows exactly what would change before anything does
Apply — or stop at the preview with a validate-only switch
Report — deployment outputs land in the run summary
The principle

A deployment should never do more than you asked it to. Selective deployment isn't about saving compute minutes — it's about making the blast radius of every action visible and intentional.

Tick a Box, Deploy a Thing

The platform pipeline is where this idea really earns its keep. The shared infrastructure isn't one monolith — it's a handful of independent components that change on completely different schedules. The gateway might get a new policy this week; the shared compute plan might not change for months. Redeploying all of it to touch one piece is exactly the over-reach we were trying to kill.

So when you start the platform pipeline by hand, it asks you a simple question: what, exactly, do you want to deploy? Each shared component gets its own checkbox. Tick one, tick several, tick everything — the run touches only what you selected and leaves the rest completely alone.

Run platform deployment branch: main
API gateway & shared components Provision the gateway, its named values, products and policy fragments
Shared compute plan Provision the serverless plan the applications run on
Validate only Run the preview and stop — change nothing
▶ Run deployment

It reads like a checklist because that's exactly what it is. The default state ships everything, so a routine run stays simple — but the moment you need surgical precision, it's one tick away. Behind the scenes each box maps to a single component template that's either included in the run or skipped entirely. No flags to remember, no scripts to edit, no comment-out-this-line.

The Dry Run That Saved Us More Than Once

Notice the last checkbox: validate only. It's the most important one in the list, and it's the reason we trust this pipeline with shared infrastructure at all.

Tick it, and the pipeline does everything except the part that changes reality. It compiles the templates, contacts the cloud, and produces a preview of every change it would make — resources it would create, properties it would modify, anything it would delete — then stops cold without applying a single one. A deployment dress rehearsal.

⊘ Validate only
  • Compiles & validates every template
  • Generates a full change preview (the diff)
  • Surfaces tier limits & policy errors early
  • Applies nothing — reality is untouched
  • Safe to run from any branch, any time
▶ Apply
  • Runs the same preview first, always
  • Then commits only the selected components
  • Adopts existing resources with zero drift
  • Writes deployment outputs to the run summary
  • The intended, reviewed change goes live

This is how we adopted live, already-running infrastructure into code without holding our breath. We'd write the template, run validate-only, and read the preview: if it said "no changes," we knew the code matched reality perfectly and applying it was a non-event. If it said it wanted to modify something, we'd found a mismatch before it could bite. The preview also caught a tier limitation on day one — but that's a story two sections from now.

Why this matters for shared infra

Application deployments are easy to roll back — redeploy the previous build. Shared infrastructure is not. A wrong move on a gateway or a compute plan affects every environment at once. Validate-only turns an irreversible action into a reviewable one: see the diff, then decide.

Everything Is a File

The second rule: if it exists in the cloud, it exists in source control. Every resource — compute plans, gateways, vaults, monitoring, identity — is described in Azure Bicep templates, organised as one folder per component. Each component is small enough to read in one sitting, and a thin orchestrator file wires them together per environment. The pipelines themselves run on GitHub Actions, authenticating to Azure with passwordless OIDC federation against Microsoft Entra ID — no deployment secrets stored anywhere.

That includes resources that already existed. Rather than recreating the platform from scratch, we wrote templates that matched the live configuration exactly, then verified with preview diffs that adopting them under code management would change precisely nothing. Reproducibility without a rebuild.

Azure Bicep GitHub Actions Entra ID · OIDC Folder per component What-if previews Adopt, don't rebuild Zero portal click-ops
2 pipelines — one for apps, one for the platform
0 portal clicks needed to rebuild any environment
1 checkbox between you and an infrastructure change

The Azure Stack

We're a Microsoft shop, and this platform is built entirely on Azure-native services — every one of them deployed and managed through Bicep. Here's the full bill of materials and the job each resource does:

Azure Resource What It Does for Us
Azure API Management (Consumption) The shared external gateway — one front door for every API, owning CORS and backend credentials
Azure Functions (.NET isolated) The serverless application tier — scales to zero between conversations, pays per execution
App Service Plan (Consumption Y1) The shared serverless compute plan the function apps run on
Azure Key Vault Every secret lives here — referenced by apps at runtime, never stored in code or config
User-Assigned Managed Identity The application's permanent passport — survives app rebuilds, anchors all role assignments
Azure Storage Function runtime storage, accessed via managed identity — no connection strings anywhere
Application Insights + Log Analytics Every invocation traced and queryable — observability per environment
Azure RBAC All access is role-based and granted to the managed identity — zero passwords in the platform
Why all-in on consumption tiers?

A consultancy website's traffic is bursty — quiet for hours, busy in spikes. Consumption-tier API Management, Functions, and the serverless compute plan all scale to zero and bill per use. The entire platform costs less per month than a single coffee — and scales instantly when traffic arrives.

The Identity That Outlives Everything

Here's a lesson we earned the hard way. Our application authenticates to Azure Storage and Azure Key Vault using a managed identity — a Microsoft Entra ID identity with no passwords, no connection strings, just Azure RBAC role assignments. Originally that identity was system-assigned: born with the app, and — as we discovered — buried with it too.

During the uplift we deleted and recreated an application instance. The new instance got a brand-new identity, but the old role assignments — still pointing at the dead identity — were left behind like name tags for someone who no longer works here. The next deployment tried to reuse them and the platform refused: role assignments can't change owners. Deployment failed.

The fix wasn't a workaround — it was a better architecture. We switched to a user-assigned managed identity: a standalone identity resource that exists independently of any application. The app can be deleted and recreated a hundred times; the identity, and every permission granted to it, survives untouched.

Design lesson

Tie long-lived things (permissions) to long-lived anchors (standalone identities), not to short-lived things (app instances). Any resource your pipeline can destroy and recreate should never be the anchor for anything else.

Server infrastructure with network cabling representing cloud platform foundations

One Front Door

Before the uplift, the website called our serverless API directly. It worked, but it meant every cross-cutting concern — CORS, throttling, future authentication — had to live inside application code. And the API itself was reachable by anyone who knew the address.

Now everything goes through Azure API Management. The browser only ever talks to the gateway; the gateway is the only thing that talks to the application. That one change moved a whole class of problems out of the codebase and into a policy layer:

Request flow — platform architecture
MICROSOFT AZURE Browser Public website API Management Gateway · policies Functions Serverless · key-protected Key Vault Secrets Managed Identity Standalone App Insights Observability 1 2 3 4
  1. 1
    The browser calls the gateway over HTTPS — the only endpoint it knows. CORS is validated at the edge against the allowed origin; the browser never holds a secret.
  2. 2
    The gateway applies shared policies, then routes through a backend definition that injects the function key server-side. The key lives in the gateway's credential store — never in code or the browser.
  3. 3
    The application validates the key — no key, no entry (HTTP 401) — and runs in-app guardrails for validation and per-IP rate limiting.
  4. 4
    Secrets are read from the vault and downstream AI services are called, all authenticated by the application's standalone managed identity — no passwords anywhere. Every hop is traced for observability.

The part we like most: the API definition on the gateway is never written by hand. The application describes its own API — it generates an OpenAPI specification from the code at runtime. On every deployment, the pipeline exports that live specification and imports it into the gateway. If an endpoint changes in code, the gateway learns about it on the next deploy, automatically. No drift, no stale documentation, no human in the loop.

Azure API Management Azure Functions CORS at the edge Key injection per backend No anonymous endpoints Self-describing OpenAPI Imported on every deploy

The Day the Tier Said No

Not everything went to plan — and that's worth writing about too.

The original design put rate limiting at the gateway: a tidy policy capping requests per second per client. We'd flagged it as an assumption to verify, and verification came quickly — the first deployment failed with a very clear message: that policy isn't available on API Management's Consumption tier. The serverless pricing model we'd chosen for its economics simply doesn't include it.

We had three options: pay for a higher tier (a permanent monthly cost to rate-limit a chat widget), pretend we didn't need rate limiting (no), or enforce it in the application layer where our validation guardrails already lived. We chose the third — and made the limit configurable, so both the request count and the time window can be tuned per environment without touching code.

Honest takeaway

Cloud pricing tiers have teeth. List your assumptions before you build, test the risky ones first, and design so that a "no" from the platform costs you an afternoon — not a re-architecture. Our gateway design survived the surprise untouched; only the rate limit moved house.

The Process Behind It

The technical choices matter, but the process is what made this safe. Every piece of this uplift followed the same loop:

1

Design first, in writing

Each change started as a short design document — the goal, the options considered, the trade-offs, and the chosen approach. Approved before any code was written.

2

Validate the risky assumption first

Every design listed what could sink it. The riskiest assumption got tested before anything else was built — which is exactly how the rate-limit surprise cost us an hour instead of a week.

3

Preview before apply

Infrastructure changes always ran a what-if diff first. If the preview showed anything unexpected, we stopped. Production resources were adopted into code only after the diff proved a no-op.

4

Verify, then cut over

New paths were proven in a non-production environment end-to-end — gateway reachable, direct access locked, origin rules enforced — before production switched. The final cutover was sequenced to keep downtime to minutes.

None of this is exotic. It's just discipline applied consistently — and it's the same process we bring to client engagements, scaled to fit.

What This Buys Us

The visible result is almost anticlimactic: the website works exactly as it did before. That was the point. Underneath, though:

  • Deployments are intentional. Code ships automatically; infrastructure changes only when a human explicitly asks, with a preview of the consequences.
  • Everything is reproducible. Any environment can be rebuilt from source control alone. The platform has no memory that isn't written down.
  • The API has one front door. Cross-cutting policy lives at the gateway; the application is unreachable without a key it never has to manage itself.
  • Identity is permanent. Applications come and go; permissions survive, anchored to standalone identities.
  • The next change is cheap. New policy at the gateway? Edit one XML file. New environment? One parameter file. New shared resource? One folder and a checkbox.

That last point is the real return on investment. DevOps uplift isn't about making today's system fancier — it's about making every future change smaller, safer, and more boring. Boring deployments are the goal. We highly recommend them.

What's Next

This article covered the deployment and platform side of the uplift. The application architecture got the same treatment — clean layering, vertical slices, and a test-first workflow across both the website and the backend — and that story deserves its own write-up. That blog is coming next.

In the meantime, if your own deployments feel like demolishing the kitchen to cook dinner, we should talk. This is exactly the kind of uplift we do for clients — and we've got the scars to prove the lessons are first-hand.

Ready to Make Your Deployments Boring?

Talk to the KloudSync Team