Free tool · no sign-up
Find the cloud lock-in in your Kubernetes YAML.
Paste a manifest or helm template output. Lock-in scan flags every line tied to AWS, Google Cloud or Azure, tells you what breaks if you move it, and what to change first.
- 44 checks
- AWS · Google Cloud · Azure
- Runs in your browser
Lock-in scan
Your YAML stays on this page. The scan is plain JavaScript running in your browser, and nothing you paste is sent anywhere, so there's no need to strip secrets first.
Nothing leaves your browser. Pattern matching only, so treat results as a checklist, not a verdict.
Paste YAML on the left, or load the example.
Fixing all of this by hand? We're building tools that move Kubernetes workloads and their data between clouds.
Get early accessWhat it checks
Every rule the scanner runs. Breaks means it won't work on another cloud. Changes silently means it applies but behaves differently. Still tied means it works, but keeps a dependency on the old cloud.
Amazon Web Services 15 checks
- Breaks
EBS volume driver
Only exists on AWS. Volumes provisioned with it stay Pending on any other cloud, and EBS snapshots can't be restored there.
Define a StorageClass with the same name on the target that uses its own CSI driver, then copy the data at file level.
On GKE: Use pd.csi.storage.gke.io in the StorageClass (type pd-balanced or pd-ssd).
On AKS: Use disk.csi.azure.com in the StorageClass (skuName StandardSSD_LRS or Premium_LRS).
- Breaks
EKS storage class name
gp2, gp3 and the rest are EBS volume types. The target cluster won't have a class by that name unless you create one.
Use neutral class names like fast or standard, and define them in each cluster.
On GKE: GKE ships standard-rwo (balanced PD) and premium-rwo (SSD PD), or create a class with the old name.
On AKS: AKS ships managed-csi (Standard SSD) and managed-csi-premium (Premium SSD), or create a class with the old name.
- Breaks
EFS file system
EFS only mounts inside AWS. A volume pointing at an fs- ID has nothing to attach to anywhere else.
Map it to Filestore or Azure Files and copy the files across.
On GKE: Filestore through filestore.csi.storage.gke.io, with the standard-rwx or premium-rwx class.
On AKS: Azure Files through file.csi.azure.com, with the azurefile-csi or azurefile-csi-premium class.
- Breaks
Hard-coded EBS volume ID
A PersistentVolume bound to a volume ID belongs to one AWS account and one zone.
Switch to a dynamically provisioned claim and restore the data into it.
On GKE: A static PV on GKE names its disk by path (projects/<project>/zones/<zone>/disks/<name>); a fresh claim is simpler.
On AKS: A static PV on AKS names its disk by resource ID (/subscriptions/<id>/resourceGroups/<rg>/providers/Microsoft.Compute/disks/<name>); a fresh claim is simpler.
- Breaks
IAM role for the service account (IRSA)
This annotation is how the pod gets AWS credentials. Other clouds ignore it, so every AWS call from the pod fails.
Use the target's workload identity. If the pod still needs AWS, federate the new cluster's identity into IAM.
On GKE: Annotate the service account with iam.gke.io/gcp-service-account and grant it roles/iam.workloadIdentityUser on that Google service account.
On AKS: Label the pod azure.workload.identity/use: "true", annotate the service account with azure.workload.identity/client-id, and add a federated credential to the managed identity.
- Breaks
EKS node selector
Picks nodes by EKS node group, Karpenter label or EC2 instance type. Nothing matches on another cloud, so the pods never schedule.
Select on labels you own (say, pool: general) and apply them to nodes in every cluster.
On GKE: GKE labels nodes with cloud.google.com/gke-nodepool and machine types like e2-standard-4; labels you own stay portable.
On AKS: AKS labels nodes with kubernetes.azure.com/agentpool and VM sizes like Standard_D4s_v5; labels you own stay portable.
- Breaks
AWS availability zone
Zone names are different on every cloud. Affinity or volumes pinned to us-east-1a leave pods Pending elsewhere.
Spread across zones with topologySpreadConstraints on topology.kubernetes.io/zone instead of naming them.
On GKE: GKE zones look like us-central1-a.
On AKS: AKS zone labels look like eastus-1.
- Changes silently
AWS load balancer annotation
Other clouds ignore it and build a default load balancer. An internal one can come up public, and TLS settings are dropped.
Translate each annotation to the target's own, and check the load balancer's scheme before sending traffic.
On GKE: For an internal load balancer: networking.gke.io/load-balancer-type: "Internal". A GKE Service does not terminate TLS; do that at a Gateway or Ingress.
On AKS: For an internal load balancer: service.beta.kubernetes.io/azure-load-balancer-internal: "true". The Azure load balancer does not terminate TLS; do that at an Ingress or Application Gateway.
- Breaks
AWS Load Balancer Controller ingress
Nothing claims the alb ingress class on another cloud, so the Ingress never gets an address.
Move to the target's ingress class, or to Gateway API with a controller you run in every cluster.
On GKE: ingressClassName: gce (or gce-internal), or a Gateway with a gke-l7 gateway class.
On AKS: Application Gateway (ingressClassName: azure-application-gateway) or the app routing add-on (webapprouting.kubernetes.azure.com).
- Still tied
Image in Amazon ECR
Another cloud can pull it, but needs AWS credentials to do so and pays egress on every pull.
Mirror images to the target's registry, or to one every cluster can reach cheaply.
On GKE: Artifact Registry (<region>-docker.pkg.dev/<project>/<repo>); nodes pull with their service account.
On AKS: Azure Container Registry (<name>.azurecr.io), attached with az aks update --attach-acr.
- Still tied
AWS resource ARN
Points at a specific AWS resource: a certificate, a queue, a key. It either moves too or gets reached across clouds.
List each ARN and decide: move it, replace it, or keep it and plan the network path and credentials.
On GKE: Google Cloud names resources by path (projects/<project>/...) and identities by service account email; map each ARN to what replaces it.
On AKS: Azure names resources by ID (/subscriptions/<id>/resourceGroups/...); map each ARN to what replaces it.
- Still tied
S3 bucket
The data stays in AWS. Reading it from another cloud costs egress and adds latency.
Decide whether the bucket moves. If it stays, put the egress cost in the plan.
On GKE: Cloud Storage; Storage Transfer Service copies from S3.
On AKS: Blob Storage; AzCopy copies from S3.
- Still tied
AWS region in config
Usually an SDK or endpoint setting. Right if the app keeps talking to AWS, wrong if that dependency is meant to move.
Find what reads it, and move it into per-cluster config.
On GKE: Google Cloud regions look like us-central1.
On AKS: Azure regions look like eastus.
- Still tied
AWS Secrets Manager or Parameter Store
Secrets are read from AWS at runtime. On another cloud the pod needs AWS credentials, and the secret store becomes a cross-cloud dependency.
Replicate the secrets into the target's store, or keep one store and give every cluster a way to authenticate to it.
On GKE: Secret Manager, through External Secrets (provider gcpsm) or the Secret Manager add-on for GKE.
On AKS: Key Vault, through External Secrets (provider azurekv) or the Key Vault secrets provider add-on.
- Still tied
Route 53 DNS automation
cert-manager or external-dns is set up to change Route 53 using AWS credentials.
Give it credentials that work from the new cluster, or point it at another DNS provider.
On GKE: cert-manager: the cloudDNS solver; external-dns: --provider=google.
On AKS: cert-manager: the azureDNS solver; external-dns: --provider=azure.
Google Cloud 14 checks
- Breaks
Persistent Disk driver
Only exists on Google Cloud. Claims using it stay Pending elsewhere, and PD snapshots stay in Google Cloud.
Define a StorageClass with the same name on the target that uses its own CSI driver, then copy the data at file level.
On EKS: Use ebs.csi.aws.com in the StorageClass (type gp3).
On AKS: Use disk.csi.azure.com in the StorageClass (skuName StandardSSD_LRS or Premium_LRS).
- Breaks
GKE storage class name
These class names come with GKE. The target cluster won't have them unless you create them.
Use neutral class names like fast or standard, and define them in each cluster.
On EKS: Create a gp3 class (ebs.csi.aws.com) under the old name, or use neutral names everywhere.
On AKS: AKS ships managed-csi and managed-csi-premium, or create a class with the old name.
- Breaks
Filestore or Cloud Storage FUSE volume
Both drivers are GKE add-ons. The volume has nothing to mount on another cloud.
Use the target's file storage, or mount object storage through a driver you install yourself.
On EKS: EFS (efs.csi.aws.com) for Filestore; Mountpoint for Amazon S3 (s3.csi.aws.com) for Cloud Storage FUSE.
On AKS: Azure Files (file.csi.azure.com) for Filestore; the Blob CSI driver (blob.csi.azure.com) for Cloud Storage FUSE.
- Breaks
GKE Workload Identity
Maps the Kubernetes service account to a Google service account. Elsewhere it's ignored and Google API calls fail.
Use the target's workload identity, or set up workload identity federation from the new cluster into Google Cloud.
On EKS: IRSA (eks.amazonaws.com/role-arn on the service account) or an EKS Pod Identity association.
On AKS: Label the pod azure.workload.identity/use: "true", annotate the service account with azure.workload.identity/client-id, and add a federated credential to the managed identity.
- Breaks
GKE node selector
Selects GKE node pools, Spot VMs or machine families. No node carries these labels on another cloud.
Select on labels you own and apply them to nodes in every cluster.
On EKS: EKS labels node groups with eks.amazonaws.com/nodegroup; Karpenter marks Spot with karpenter.sh/capacity-type: spot.
On AKS: AKS labels pools with kubernetes.azure.com/agentpool and Spot nodes with kubernetes.azure.com/scalesetpriority: spot.
- Breaks
Google Cloud zone
Pinning pods or volumes to a zone like europe-west1-b leaves them Pending on any other cloud.
Spread across zones with topologySpreadConstraints on topology.kubernetes.io/zone instead of naming them.
On EKS: EKS zones look like us-east-1a.
On AKS: AKS zone labels look like eastus-1.
- Changes silently
GKE load balancer annotation
Ignored elsewhere. An internal load balancer can come up public, and BackendConfig settings like timeouts and Cloud Armor are dropped.
Translate each annotation to the target's own, and check the load balancer's scheme before sending traffic.
On EKS: For an internal load balancer: service.beta.kubernetes.io/aws-load-balancer-scheme: internal (AWS Load Balancer Controller).
On AKS: For an internal load balancer: service.beta.kubernetes.io/azure-load-balancer-internal: "true".
- Breaks
GKE ingress or GKE-only resource
The gce ingress class and resources like ManagedCertificate exist only on GKE. Elsewhere they fail to apply or do nothing.
Use the target's ingress class or Gateway API, and cert-manager for certificates.
On EKS: ingressClassName: alb with the AWS Load Balancer Controller; certificates from ACM through alb.ingress.kubernetes.io/certificate-arn.
On AKS: Application Gateway or the app routing add-on; certificates from Key Vault or cert-manager.
- Still tied
Image in Artifact Registry or GCR
Another cloud can pull it, but needs Google credentials and pays egress on every pull.
Mirror images to the target's registry, or to one every cluster can reach cheaply.
On EKS: Amazon ECR (<account>.dkr.ecr.<region>.amazonaws.com); nodes pull with their instance role.
On AKS: Azure Container Registry (<name>.azurecr.io), attached with az aks update --attach-acr.
- Still tied
Cloud Storage bucket
The data stays in Google Cloud. Reading it from another cloud costs egress and adds latency.
Decide whether the bucket moves. If it stays, put the egress cost in the plan.
On EKS: S3; DataSync copies from Cloud Storage.
On AKS: Blob Storage; AzCopy copies from Cloud Storage.
- Still tied
Google Cloud region in config
Usually an SDK or endpoint setting. Right if the app keeps talking to Google Cloud, wrong if that dependency is meant to move.
Find what reads it, and move it into per-cluster config.
On EKS: AWS regions look like us-east-1.
On AKS: Azure regions look like eastus.
- Still tied
Google Secret Manager
Secrets are read from Google Cloud at runtime, so every other cluster needs a way to authenticate to it.
Replicate the secrets into the target's store, or keep one store and federate access to it.
On EKS: Secrets Manager, through External Secrets (provider aws, service SecretsManager) or the Secrets Store CSI driver.
On AKS: Key Vault, through External Secrets (provider azurekv) or the Key Vault secrets provider add-on.
- Still tied
Cloud DNS automation
cert-manager or external-dns is set up to change Cloud DNS using Google credentials.
Give it credentials that work from the new cluster, or point it at another DNS provider.
On EKS: cert-manager: the route53 solver; external-dns: --provider=aws.
On AKS: cert-manager: the azureDNS solver; external-dns: --provider=azure.
- Still tied
Google Cloud resource path
Points at a topic, secret, database or other resource in one Google Cloud project.
List each one and decide: move it, replace it, or keep it and plan the network path and credentials.
On EKS: AWS names the same resources by ARN (arn:aws:sns:..., arn:aws:secretsmanager:...).
On AKS: Azure names them by resource ID (/subscriptions/<id>/resourceGroups/...).
Microsoft Azure 11 checks
- Breaks
Azure Disk, Files or Blob driver
These drivers only work on Azure. Claims using them stay Pending elsewhere, and disk snapshots stay in Azure.
Define a StorageClass with the same name on the target that uses its own CSI driver, then copy the data at file level.
On EKS: ebs.csi.aws.com for disks, efs.csi.aws.com for Azure Files, Mountpoint for Amazon S3 for Blob.
On GKE: pd.csi.storage.gke.io for disks, Filestore for Azure Files, Cloud Storage FUSE for Blob.
- Breaks
AKS storage class name
These class names come with AKS. The target cluster won't have them unless you create them.
Use neutral class names like fast or standard, and define them in each cluster.
On EKS: Create a gp3 class (ebs.csi.aws.com) under the old name, or use neutral names everywhere.
On GKE: GKE ships standard-rwo and premium-rwo, or create a class with the old name.
- Breaks
Azure Workload Identity
Gives the pod an Entra ID identity. Elsewhere the webhook isn't there and calls to Azure fail.
Use the target's workload identity, or federate the new cluster's service accounts into Entra ID.
On EKS: IRSA (eks.amazonaws.com/role-arn on the service account) or an EKS Pod Identity association.
On GKE: Annotate the service account with iam.gke.io/gcp-service-account and grant it roles/iam.workloadIdentityUser on that Google service account.
- Breaks
AKS node selector
Selects AKS agent pools, Spot priority or Azure VM sizes. No node matches on another cloud.
Select on labels you own and apply them to nodes in every cluster.
On EKS: EKS labels node groups with eks.amazonaws.com/nodegroup and instance types like m6i.xlarge; labels you own stay portable.
On GKE: GKE labels pools with cloud.google.com/gke-nodepool and machine types like e2-standard-4; labels you own stay portable.
- Changes silently
Azure load balancer annotation
Ignored elsewhere. Drop azure-load-balancer-internal and the service comes up with a public IP.
Translate each annotation to the target's own, and check the load balancer's scheme before sending traffic.
On EKS: For an internal load balancer: service.beta.kubernetes.io/aws-load-balancer-scheme: internal (AWS Load Balancer Controller).
On GKE: For an internal load balancer: networking.gke.io/load-balancer-type: "Internal".
- Breaks
Application Gateway or AKS app routing ingress
Both controllers exist only on Azure, so the Ingress never gets an address anywhere else.
Move to the target's ingress class, or to Gateway API with a controller you run in every cluster.
On EKS: ingressClassName: alb with the AWS Load Balancer Controller.
On GKE: ingressClassName: gce (or gce-internal), or a Gateway with a gke-l7 gateway class.
- Still tied
Image in Azure Container Registry
Another cloud can pull it, but needs Azure credentials and pays egress on every pull.
Mirror images to the target's registry, or to one every cluster can reach cheaply.
On EKS: Amazon ECR (<account>.dkr.ecr.<region>.amazonaws.com); nodes pull with their instance role.
On GKE: Artifact Registry (<region>-docker.pkg.dev/<project>/<repo>); nodes pull with their service account.
- Still tied
Azure Storage account
The data stays in Azure. Reading it from another cloud costs egress and adds latency.
Decide whether the storage account moves. If it stays, put the egress cost in the plan.
On EKS: S3; DataSync copies from Azure Blob Storage.
On GKE: Cloud Storage; Storage Transfer Service copies from Azure Blob Storage.
- Still tied
Azure Key Vault
Secrets are read from Key Vault at runtime, so every other cluster needs a way to authenticate to Azure.
Replicate the secrets into the target's store, or keep one store and federate access to it.
On EKS: Secrets Manager, through External Secrets (provider aws) or the Secrets Store CSI driver.
On GKE: Secret Manager, through External Secrets (provider gcpsm) or the Secret Manager add-on for GKE.
- Still tied
Azure region in config
Usually an SDK setting or a zone label like eastus-1. Right if the app keeps talking to Azure, wrong if that dependency is meant to move.
Find what reads it, and move it into per-cluster config.
On EKS: AWS regions look like us-east-1.
On GKE: Google Cloud regions look like us-central1.
- Still tied
Azure DNS automation
cert-manager or external-dns is set up to change Azure DNS using Azure credentials.
Give it credentials that work from the new cluster, or point it at another DNS provider.
On EKS: cert-manager: the route53 solver; external-dns: --provider=aws.
On GKE: cert-manager: the cloudDNS solver; external-dns: --provider=google.
Any cloud 4 checks
- Breaks
Hard-coded loadBalancerIP
Reserved addresses belong to one cloud account. The field is deprecated too, and the Service waits forever if the address isn't available.
Reserve an address on the target and set it with that cloud's annotation, or let it assign one and update DNS.
On EKS: On EKS with an NLB, allocate Elastic IPs and list them in service.beta.kubernetes.io/aws-load-balancer-eip-allocations.
On AKS: On AKS, use service.beta.kubernetes.io/azure-load-balancer-ipv4 (or azure-pip-name) with an IP in the node resource group.
- Breaks
hostPath volume
The data sits on one node's disk. It isn't in any PVC backup and won't exist on a new cluster.
Move the data to a PersistentVolumeClaim, or confirm it's disposable.
- Breaks
Pod pinned with nodeName
Node names come from the cloud (ip-10-0-1-23.ec2.internal, gke-pool-1-abc). That node won't exist anywhere else.
Use node labels and affinity instead.
- Still tied
Volume snapshots used as backup
CSI snapshots live in the source cloud's disk service. You can't restore them into another cloud, so they don't count as a cross-cloud backup.
Keep a file-level or application-level copy outside the source cloud as well.
Finding the locks is the easy part.
Moving the workload, its volumes and its data to another cloud is the hard part. That's what KeyModules is building.
-
In development
Cross-cloud restore
Back up a namespace in one cloud and bring it up in another, volumes included.
-
In development
Multi-cloud storage
Keep object storage in sync across providers without writing the replication jobs yourself.
Get early access
Early builds go to people on the list first. Tell us which clouds you run and we'll start with the setups that match yours.