“Multi-cloud” gets sold as a strategy. It usually isn’t one — it’s a consequence. A department already has an Azure tenancy from its M365 estate, a delivery partner built the last service on AWS, and a data science team wants BigQuery. Nobody planned this. Somebody has to run it anyway.
After a decade architecting platforms across AWS, Azure and Google Cloud for public-sector clients, here’s what actually matters once you’re past the “which cloud is best” debate and into keeping production workloads secure, compliant and affordable.
1. Sovereignty and classification come before architecture
On commercial projects, region selection is a performance decision. On government projects, it’s a legal one first.
Before a single Terraform module gets written, we settle:
- What classification does this data carry — OFFICIAL, OFFICIAL-SENSITIVE, or higher?
- Which region(s) satisfy the residency requirement, and does the provider’s shared-responsibility model actually meet it, or just claim to?
- Who holds the encryption keys — the provider, or does the engagement require customer-managed keys (CMK/BYOK)?
# Encryption key ownership is a decision, not a default
resource "aws_kms_key" "official_sensitive" {
description = "CMK for OFFICIAL-SENSITIVE workloads"
deletion_window_in_days = 30
policy = data.aws_iam_policy_document.key_admins.json
# Key never leaves customer control — no provider-managed fallback
}
Get this wrong and no amount of good engineering fixes it later. We’ve seen migrations paused for months because data residency was assumed rather than verified.
2. Pick the right cloud per workload, not one cloud for everything
The teams that struggle with multi-cloud are the ones trying to make every workload portable everywhere. The teams that succeed pick a primary provider per workload based on what it’s actually good at, and standardise the seams between them.
| Workload | Where we typically land it | Why |
|---|---|---|
| GOV.UK-style citizen services | AWS GovCloud / UK regions | Mature PaaS ecosystem, established public-sector accreditation |
| M365-integrated case management | Azure | Native AD, Graph API, existing tenancy |
| Data platforms & analytics | GCP | BigQuery cost model, strong data tooling |
| Container workloads | Whichever region residency demands | Portable by design — see below |
The mistake isn’t using three clouds. It’s using three clouds without deciding why for each one.
3. Kubernetes is the only thing that makes multi-cloud bearable
Compute, IAM and networking APIs differ across AWS, Azure and GCP in ways that never fully abstract away. Kubernetes is the closest thing to a common substrate, and it’s the one investment that pays off regardless of which providers you end up running.
# Same manifest, three managed control planes
# EKS, AKS and GKE all accept this without modification
apiVersion: apps/v1
kind: Deployment
metadata:
name: claims-api
spec:
replicas: 3
template:
spec:
containers:
- name: claims-api
image: registry.internal/claims-api:1.4.2
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "500m", memory: "512Mi" }
We still run provider-native services underneath — RDS, Cosmos DB, Cloud SQL — because reimplementing managed databases on Kubernetes is rarely worth it. But the application layer stays portable, and that portability is what lets a department move a workload when a framework contract changes.
4. FinOps is a weekly habit, not an annual review
Cloud cost overruns in the public sector rarely come from one bad decision. They come from nobody looking until the invoice lands on a director’s desk.
# What an unmanaged multi-cloud bill actually looks like after 12 months
AWS: forecast £18k/mo → actual £31k/mo (orphaned EBS volumes, over-provisioned RDS)
Azure: forecast £9k/mo → actual £14k/mo (dev/test resources never torn down)
GCP: forecast £4k/mo → actual £4.2k/mo (tagged and budget-alerted from day one)
The GCP line is the outlier for a reason — it had budget alerts and mandatory tagging enforced by policy from launch. The other two didn’t, until we added it.
# Tag enforcement at the policy layer, not the honour system
resource "aws_organizations_policy" "require_cost_tags" {
content = jsonencode({
tags = {
"cost-centre" = { tag_key = { "@@assign" = "cost-centre" }, enforced_for = { "@@assign" = ["ec2:instance", "rds:db"] } }
}
})
}
Tag enforcement, budget alerts and a weekly cost review are the difference between a forecast and a fantasy.
5. Infrastructure as code is the single source of truth — or it’s nothing
Multi-cloud without IaC means three consoles, three sets of tribal knowledge, and a change history that lives in nobody’s head after they leave. Terraform (or OpenTofu) with per-provider modules behind a shared interface is what makes audits survivable.
module "network" {
source = "./modules/network/${var.cloud_provider}"
cidr = var.network_cidr
env = var.environment
}
Every change goes through the same PR review, the same plan-before-apply gate, the same audit trail — regardless of which cloud it targets. That consistency matters more to an ISO 27001 or Cyber Essentials Plus auditor than which provider you picked.
Key takeaways
- Classify before you architect — sovereignty and key ownership are legal decisions, not infrastructure ones
- Choose deliberately per workload — multi-cloud by accident is a liability; multi-cloud by design is a strength
- Kubernetes is the portability layer — invest there, not in chasing full cloud-agnosticism everywhere
- FinOps is continuous — tag enforcement and budget alerts beat a quarterly spreadsheet every time
- IaC is your audit trail — if it’s not in code and reviewed, it didn’t happen
Multi-cloud isn’t inherently a good or bad strategy. It’s a set of constraints somebody else already handed you. The engineering job is making those constraints boring — predictable, auditable, and cheap to run.
Comments
Loading comments…