A founder asked me last quarter whether they should move from AWS to Google Cloud. Their monthly bill was around $900. Migrating would have cost them roughly six weeks of engineering time to save, optimistically, $200 a month. We spent an afternoon on their RDS instance sizes and their unattached EBS volumes instead, and the bill dropped by a third by the end of the week.
That conversation happens a lot. The cloud provider question feels strategic, so it gets executive attention, while the decisions that actually determine your cost and reliability — instance sizing, data transfer paths, how you handle state — get made by whoever was on the ticket.
Let me try to put the provider question in its proper place, and then talk about the parts that matter more.
The Provider Choice Is Mostly Decided by Things That Are Not Technical
All three major clouds can run your application. They all have managed databases, object storage, queues, container orchestration, serverless compute and a global network. The feature gaps that exist are real but narrow, and they almost never apply to the workload in front of you.
What actually decides it, in my experience:
What your team already knows. An engineer who has debugged an IAM policy at 2am on AWS is worth more to you than a marginally better managed service on a platform nobody has operated. Hiring in most markets also skews heavily toward AWS experience, which matters when you need to grow the team.
What your customers require. Enterprise clients with a Microsoft estate will ask for Azure, and sometimes their procurement process makes it non-negotiable. Public sector work often comes with data residency requirements that shortlist the provider for you.
Existing commitments. If the company already has an enterprise agreement or credits, that discount is usually larger than any architectural advantage you would gain by switching.
The specific managed service you cannot live without. This is the only genuinely technical tiebreaker I take seriously, and it is usually about data or AI. If your entire analytics story is BigQuery, that pulls you to Google. If you are deep in Active Directory and Microsoft 365, Azure removes a category of integration pain.
Beyond that, pick one and get good at it. Multi-cloud as a strategy for a small or mid-sized team is a way to be mediocre at two platforms while doubling your operational surface. Multi-cloud as a consequence — analytics in one place, the application in another — is fine and common. There is a difference between deliberately spreading a workload and simply having two vendors.
Serverless: Wonderful, Until It Is Not
I use serverless a lot and I recommend it a lot, so this is not a takedown. But the honeymoon is real and the hangover is specific.
Serverless is genuinely excellent for spiky, event-driven, stateless work. Image processing on upload. Webhook receivers. Scheduled jobs. Anything where traffic is unpredictable and idle time is common. The economics are unbeatable when your function runs 40,000 times a month and sleeps the rest of the time, and the operational load is close to zero.
Where I have watched it go wrong:
Steady, high-volume traffic. Past a certain sustained request rate, a couple of right-sized containers on a small cluster cost noticeably less than per-invocation billing. The crossover point moves, so measure it rather than assuming, but it exists and teams blow past it without checking.
Database connections. This one catches people repeatedly. A traditional connection pool assumes long-lived processes. Hundreds of concurrent function instances each opening a Postgres connection will exhaust your database's connection limit long before the database runs out of CPU. You need a proxy or a pooler in front, and you need it before the traffic arrives, not during the incident.
Long-running work. Execution time limits are hard limits. Report generation that took 40 seconds during testing will take 400 for your biggest customer, and you will end up rewriting it as a queue-plus-worker anyway. Start there if you can see it coming.
Local development and testing. Emulators have improved, but a distributed system made of twenty functions and five queues is harder to reason about, debug and test than a single service that does the same thing. That cost is paid by every developer every day and it never shows up on the invoice.
My default now for a new product is a boring one: a container running a normal server for the main application, with serverless functions around the edges for the genuinely event-driven parts. It gives you the operational simplicity of one deployable for most of your code and the elasticity of functions where elasticity is worth something.
The Architecture Decisions That Actually Move the Needle
Provider aside, these are the ones I have seen determine whether a system stays cheap and calm or becomes expensive and fragile.
Where your state lives. Almost every hard scaling problem is really a state problem. Keep application servers stateless, push session state into Redis or a signed token, and put anything durable in a managed database you did not build yourself. Once your compute is disposable, most of the other decisions become reversible.
Data transfer, not compute. Egress and cross-zone traffic are the line items that surprise people. A chatty service that talks to its database across availability zones will quietly bill you for every conversation. Put things that talk to each other constantly close to each other, and put a CDN in front of anything users download repeatedly.
Right-sizing as a habit. The default instance is almost never the correct instance. I have yet to audit a cloud account that did not have at least one over-provisioned database, one forgotten test environment running 24/7, one unattached volume and a pile of old snapshots. That audit takes an afternoon and it pays for itself the same month.
Commit only to what you have measured. Reserved capacity and savings plans are large discounts for workloads you can predict. Run for a few months first, look at the floor of your usage rather than the average, and commit to the floor. Committing early to a guess is how teams end up paying for capacity they no longer need.
Security Is Part of the Architecture, Not a Later Phase
The cloud-specific failures are consistent enough to write down as a list, and none of them are sophisticated: storage buckets that are public when they should not be, databases with public endpoints, IAM policies with wildcards that nobody has ever read back, long-lived access keys in environment files, and secrets that were committed once and never rotated.
Two habits prevent most of it. Define infrastructure as code so that every resource is reviewable in a pull request and scannable before it exists — a public bucket caught in a Terraform plan costs nothing to fix. And use short-lived, role-based credentials instead of static keys everywhere you possibly can, including in CI.
The rest is review discipline. Permissions accumulate, environments multiply, and the person who set something up temporarily has usually left. A recurring calendar entry to read your own IAM policies is an unfashionable security control that works better than most tools.
The Short Version
Choose the provider your team can operate, not the one with the best conference keynote. Use serverless where traffic is spiky and containers where it is steady. Watch data transfer and instance sizing before you watch anything else on the bill. Write your infrastructure down in code so the decisions are reviewable.
And be very slow to migrate. Cloud migrations are sold as strategy and paid for in engineering months. Unless the current platform is genuinely blocking something you need to do, the same weeks spent tuning what you already run will usually produce a bigger result — and you get to keep the team you have.



