Skip to content
Technology and engineering

I built on Azure what I already knew on Google Cloud, and this is what I ran into

September 26, 2026

I'd spent years on Google Cloud and had never built on Azure. So I did, and this is what I ran into, told by someone carrying the other cloud's reflexes.

A dim data center aisle
Photo by Erik Mclean on Unsplash

I've spent years designing and running platforms on Google Cloud, and until recently I didn't have a single Azure account. When a client asked me about Azure, my answer came from other people's comparisons, so I decided to build on Azure what I already knew how to build on Google Cloud and write down what I ran into.

This is the overview: the findings that changed how I think about the platform most, with what I'd do differently from day one. Each one has its own article in the series, with the detail and the numbers.

What I built and how I tested it

A lab landing zone in Terraform: a hub network with Azure Firewall carrying all traffic in and out, an application network peered to it, a private AKS cluster with Entra identity and no local accounts, and an image registry, a secret vault and storage reachable only through private endpoints.

I tested it in two layers. First without a cloud: 170 Terraform tests against Azure's schema, no credentials, before opening the trial account and its 30-day clock. Then against real Azure: nine checks that attack every control and fail if they can't measure anything. The first layer guards the shape; the second guards what only Azure's API knows. How I organized that without an account is in part 5.

Networking: deny by default comes with fine print

In a custom Google Cloud network, inbound traffic is denied by default. On Azure, every security group ships with AllowVnetInBound at priority 65000, so anything in the VirtualNetwork tag, peered networks included, talks to everything. To get the same posture you add a deny of your own, and that's where the fine print is: several platform pieces depended on that implicit allow.

If you're going to deny internal traffic, declare three allows from the start: nodes to their own subnet, because AKS puts the API server's private endpoint there; AzureLoadBalancer to the nodes, because the health probe falls inside VirtualNetwork and its default rule sits below your deny; and the pod range, if you run CNI Overlay with more than one node. Turn on virtual network flow logs too, because neither the firewall nor the security group will show you what gets denied inside a subnet. The cluster that spent 87 minutes creating without a single error is in part 3.

Permissions: control and data are separate

On Google Cloud I was used to a project owner being able to do almost anything. On Azure, Owner of the subscription holds every control plane action and no data actions: it can't read a secret, read the Terraform state or use kubectl on a cluster with Azure RBAC.

The practical move is to design the data plane separately and scope each role to the resource it protects, not the resource group. And switch off the back doors, storage shared keys and Kubernetes local accounts, so the roles are the only way in. The exact roles and the one exception I kept are in part 4.

Inbound: decide early whether you need to know who's visiting

If inbound traffic from the internet comes through a DNAT rule on Azure Firewall, the firewall also translates the source, and your application sees the firewall's address instead of the visitor's. Microsoft documents it, and check nine confirms it on every run.

The consequence is a design one: without the client's address there's no per-client limiting, no per-country rules and no useful log when something gets abused, and externalTrafficPolicy set to Local won't bring it back. If any requirement mentions one of those three, budget for a layer 7 piece in front from day one, like Application Gateway or Front Door. The options I weighed and why I kept the firewall are in part 2.

Logging: nothing comes switched on

On Google Cloud, Admin Activity audit logs are on and can't be turned off. On Azure the subscription's activity log exists, but almost everything else is declared resource by resource: the firewall's diagnostics into dedicated tables, the cluster API server's audit, the virtual network's flow logs. A resource without its diagnostics is a resource you'll know nothing about when it fails.

The trial has rules of its own

Some limits belong to the offer, not to Azure. The free trial allows three public IP addresses, and Azure Firewall Basic already takes two: one for traffic and one for the management plane that tier requires. That ruled out a public load balancer per application and pushed all inbound traffic through the firewall. Spot machines aren't available on the free trial either, so the node runs at the regular price.

The cost came to about 15 dollars a month for the permanent layer and about 54 cents an hour while the firewall and cluster are up, nearly three quarters of it the firewall. That's why the layer that bills by the hour gets destroyed at the end of every session.

What sums it up

On Azure almost nothing comes done: you declare it. The network trusts itself until you override it, data doesn't belong to the owner until you assign it, and logs don't exist until you ask for them. It gives you more explicit control and charges for it in work, and a migration plan from Google Cloud that doesn't budget for that work finds out during the deployment, which is the most expensive moment to do so.

So, Azure or Google Cloud?

At this level both do everything, and what decides is what the customer already has. Identity outweighs any service: if the company lives in Microsoft 365, its directory is already Entra ID and already paid for, and moving it to another cloud means paying for identity twice.

AzureCostLow if you already pay for Microsoft 365 and Windows or SQL Server licensesRiskMore platform hours: nearly everything is declaredVerdictWhen identity and licenses already live with Microsoft
Google CloudCostLow for data and analytics, no licenses to reuseRiskFederating identity if the company lives in Microsoft 365VerdictData, multi-region, small teams and containers
BothCostTwice the operations, two security postures and cross-cloud egressRiskRunning two clouds badly is riskier than one going downVerdictOnly for inherited estates, a better service, regulation or leverage

The six criteria I use in order of weight, and the few cases where running both clouds at once makes sense, are in part 6.

Try it yourself

The code, the guide to deploy it in your own subscription, the thirteen decisions with what each one costs and the evidence from the last run are at github.com/dhatune/platform-blueprints, in the azure folder.

How many of the reflexes you bring from your current cloud are really assumptions the other cloud doesn't share?

Sign in to say it was useful

Notices

I let you know when I publish.

Conversation