KeyModules

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.

Scan your YAML
  • 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.

    What 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.

    Clouds you run (optional)

    One email when there's something to try.