Vendor lock-in is not a moral failing of cloud providers or a conspiracy. It is a cost model: the providers have engineered a pricing structure where the cheapest way to use a service is to use more of the same provider, and the most expensive operation in the catalog is the one that takes data elsewhere. Once you read the invoice as a switching-cost schedule, "lock-in" becomes a concrete, computable number — and you can decide how much of it you are willing to pay.
Egress fees: the tariff on leaving
The centerpiece of the lock-in cost model is data egress — the charge for bytes leaving the provider's network:
S3 -> internet (us-east-1) $0.09 per GB, first 10 TB
S3 -> same region EC2 $0.00
S3 -> other AWS region $0.02 per GB
EBS snapshots -> different region $0.09 per GB (plus transfer)
CloudFront (CDN egress) ~$0.085 per GB, first 10 TB
NAT gateway data processing $0.045 per GBNotice the structure: ingress is free, same-provider traffic is near-free, cross-provider is metered, and egress to the internet is the premium tier. That is a tariff whose entire purpose is to make the marginal cost of using the same provider lower than the cost of leaving. The numbers are not subtle about it:
# the migration invoice nobody prints before starting
size_tb=100
egress_rate=0.09
echo "egress of ${size_tb} TB from S3: \$$(python3 -c \
"print(f'{(100*1024*$egress_rate)/1:,.0f}')")"100 TB out of S3 is ~$9,200 in egress alone — before you pay for the transfer machines, the destination's ingress, or any downtime. Scale that to a petabyte data lake and the egress fee alone becomes a seven-figure line item. That number is the lock-in, expressed as a price.
The control-plane API is the real lock
Egress is the visible tariff; the control-plane APIs are the invisible one. You cannot migrate a workload by copying data — you must also reimplement the contract your application was written against:
auth AWS IAM (policies, roles, STS) vs. GCP IAM / OIDC
secrets AWS KMS, Secrets Manager vs. provider equivalents
queues SQS vs. SNS vs. EventBridge vs. RabbitMQ/Kafka
registry ECR vs. ECR Public vs. Docker Hub/GHCR
observability CloudWatch, X-Ray vs. Datadog/PrometheusEvery one of these is a different API with different SDKs, different IAM-shaped permission models, and different operational semantics (SQS's at-least-once with visibility timeouts has no direct analog in RabbitMQ's ack model; EventBridge schema validation is not the same as Kafka's). The application code — your actual business logic — is usually the easy part to move. The control-plane plumbing is the part that gets rewritten, retested, and re-certified.
The provider-specific services (DynamoDB, S3, Lambda, Aurora) are where the switching cost concentrates: DynamoDB's single-digit-millisecond consistency model and its GSI semantics don't map 1:1 onto Postgres or Cassandra, so "port the workload" becomes "re-architect the data access pattern." This is data gravity in its purest form: data and control plane accumulate in the same place, and every byte and every API call makes the next decision to stay cheaper than the decision to leave.
The migration math
A migration is a project with a real budget, so price it like one. The honest estimate has five components:
1. egress and transfer the tariff above, per TB
2. engineering time API rewrites: weeks per service, not days
3. parallel-run infrastructure both platforms running during cutover
4. retesting your CI/CD, load tests, and compliance runs
5. residual risk migration incidents during cutoverThe interesting property: component one (egress) is usually the smallest of the five. A 10 TB database is ~$1,000 in egress but months of engineering. That is why "lock-in" arguments that focus on egress fees miss the point — the control plane is the expensive part, and it is expensive in time, not bytes. The egress tariff is a rounding error on the real switching cost; it exists to make the decision feel metered rather than free.
What is actually portable
Portability is a spectrum, and the spectrum is predictable:
highly portable: containers + Kubernetes manifests, Terraform (mostly),
stateless services, standard protocols (HTTP, gRPC, SQL,
S3-compatible object stores like MinIO)
moderately portable: managed databases (schema is portable, ops is not),
queuing (semantics differ), CI/CD pipelines
deeply locked: serverless event models, proprietary DBs (DynamoDB),
control-plane auth (IAM), managed ML/AI servicesTwo notes on the "portable" column. First, S3-compatibility is a de facto standard now — most object stores (MinIO, Cloudflare R2, GCP) speak the S3 API, which is why R2's killer feature was zero egress fees: they removed the tariff on a protocol everyone already speaks. Second, Kubernetes is the great portability blur: your deployment manifests move, but the managed control plane (EKS vs. GKE vs. AKS), the IAM integration, and the CNI/CSI plugins stay behind.
The rational strategy
Lock-in is not all-or-nothing; it is a portfolio decision with a cost per service:
- Price the exit before you enter. Before adopting any service, write the migration cost for it — egress + API rewrite time. If the answer is "re-architecture," you have a strategic dependency, and you should treat it as one: minimize the surface area it touches, isolate it behind an interface of your own (a repository abstraction over DynamoDB costs a day to write and makes the exit a swap, not a rewrite).
- Keep the crown jewels portable. Data is what you can't re-create — use object storage with standard APIs (S3-compatible, versioned, with replication you control) so the data layer is never the hostage.
- Accept lock-in where the value exceeds the exit cost. Provisioned services (managed Postgres, serverless compute) genuinely remove operational work; paying a switching cost for less toil is a legitimate trade. The mistake is lock-in you didn't notice, not lock-in you priced.
- Multi-cloud is not the answer; the interface is. Running the same workload in three clouds triples the control-plane surface and the egress bill. What defeats lock-in is a thin, tested abstraction — not redundancy of vendors.
The runtime view
- Egress fees are a tariff schedule: ingress free, same-provider cheap, leaving metered at the premium tier.
- The control plane — IAM, queues, observability, proprietary databases — is where the real switching cost lives, measured in engineering months, not bytes.
- Migration cost = egress + API rewrites + parallel-run + retesting + risk; the egress line is usually the smallest of the five.
- Lock-in is a portfolio decision: price the exit per service, isolate what you can't afford to re-architect, and accept dependency only where the value is priced, not discovered.