Use the cloud, just don't let your code know which one
My one rule against vendor lock-in. Only use managed open source, so your application talks PostgreSQL, RabbitMQ and Kubernetes, and never a cloud SDK.
Every cloud provider will happily sell you a proprietary database, a proprietary queue and a proprietary way to run functions. They’re often good products. They’re also the walls of the room you’ll be living in for the next ten years.
I don’t think the answer is to avoid managed services. Running your own PostgreSQL cluster at 3 a.m. is not a badge of honour. My rule is simpler:
Use managed services, but only managed open source.
What that looks like
Your application should talk to protocols and APIs that exist outside any one cloud:
- PostgreSQL, not DynamoDB, Cosmos DB or Spanner. Every cloud runs managed Postgres, and so do plenty of smaller providers.
- RabbitMQ (or Kafka, or NATS), not SQS, Pub/Sub or Service Bus.
- Kubernetes, not a platform-specific container or function runtime.
- Redis or Valkey for caching, wherever it’s offered.
Let the provider handle the patching, backups, failover and upgrades. That’s what you’re paying for. But the thing your code connects to should be the same thing you could run anywhere else tomorrow.
The test: grep for the cloud
Here’s a quick check I use. Search your application code for cloud SDK imports:
grep -rE "boto3|aws-sdk|@aws-sdk|google-cloud|@google-cloud|azure-" src/
In an ideal codebase the only hits are in one small, well-named module, and that module is about object storage (more on that below). Everything else talks a connection string.
Cloud-specific code belongs in infrastructure
You can’t avoid cloud-specific code completely, and you shouldn’t try. Networking, IAM, workload identity and the managed database resource itself are all different per provider. That’s fine, as long as that code lives in your infrastructure layer (OpenTofu, Helm values, cluster config) and not in your application.
Moving providers then means rewriting your infrastructure modules. That’s real work, but it’s bounded and well understood. Rewriting every service that imported a proprietary SDK is not.
The one exception: object storage
Object storage is where this rule bends. There’s no open-source “protocol” that every cloud implements natively. But there is a de facto standard: the S3 API.
MinIO, Ceph, Cloudflare R2, and most European providers like Scaleway, OVHcloud and Hetzner all speak S3. Google Cloud Storage has an S3-compatible interoperability API. Azure Blob Storage is the odd one out.
So:
- Write against the S3 API, and keep that client behind a small interface of your own.
- If you need Azure, or want to stay fully neutral, use an abstraction library that handles several backends.
- Keep provider-specific features (lifecycle rules, replication, events) in infrastructure config, not in code.
What you give up
Some things, honestly. Proprietary services can be cheaper at extreme scale, and some, like global strongly consistent databases, have no open equivalent. If you genuinely need one, use it, and go in with your eyes open.
But most teams don’t need them. They reach for them because they’re one click away in the console. A year later, the “migration” line in their risk register quietly says not feasible.
Managed open source keeps that line honest. You get the operational benefits of the cloud and keep the option to leave. That option is worth a lot more than it costs, and it’s also the foundation for what I think real digital sovereignty means.