Cloud Migration - Breaking Free from Hyperscaler Lock-in
Vendor lock-in at cloud hyperscalers, the portability obligations of the EU Data Act and a five-phase migration path with Polycrate to sovereign Kubernetes-native infrastructure, with examples such as AWS RDS to CloudNativePG.
Cloud Migration - Breaking Free from Hyperscaler Lock-in
The challenge: vendor lock-in at cloud hyperscalers
Cloud hyperscalers such as AWS, Azure and Google Cloud offer convenient, managed services – but at a high price: proprietary APIs, non-standardized data formats, egress fees and contractual hurdles create systematic vendor lock-in.
Typical lock-in mechanisms:
- 🔒 Proprietary services: RDS, DynamoDB, S3, Lambda with AWS-specific APIs
- 💰 Egress fees: high costs for data transfer when switching
- 📦 Non-portable formats: vendor-specific IaC (CloudFormation, ARM templates)
- 🔗 Service chaining: deeply integrated services make partial migration difficult
- 📄 Contractual binding: enterprise agreements with discount structures bind long-term
The consequences:
- ❌ No ability to switch in case of price increases or quality problems
- ❌ Dependence on third-country jurisdiction (CLOUD Act, FISA)
- ❌ Lack of sovereignty over your own infrastructure
- ❌ Difficult compliance (DORA, NIS-2, Data Act)
The EU Data Act: portability as an obligation
The Data Act (EU 2023/2854) enters into force on September 12, 2025 and creates binding rules for cloud switching and interoperability.
Core Data Act requirements for cloud providers
| Requirement | Description | Deadline |
|---|---|---|
| Open interfaces | Standardized, documented APIs for export | Immediately |
| Machine-readable formats | Data in structured, common formats | Immediately |
| Functional equivalence | At least for IaaS features | Immediately |
| Egress fee phase-out | Gradual reduction down to €0 | By 12.09.2028 |
| Multi-cloud support | Enable parallel operation and gradual migration | Immediately |
| Documented switching processes | Transparent switching processes before contract | Immediately |
What does this mean for companies?
- ✅ Right to switch – cloud customers must be able to switch
- ✅ Portability – full export of data, apps, configurations
- ✅ Interoperability – open standards instead of proprietary formats
- ✅ Free of charge – no egress fees from 2028
- ✅ Independence – no contractual or technical lock-ins
But: the Data Act demands rights – it does not provide the tools to implement them.
Polycrate: the ideal tool for sovereign cloud migration
Polycrate was built to enable standardized, portable infrastructure automation – exactly what the Data Act demands and what hyperscalers have prevented for years.
Why Polycrate for cloud migration?
1. Ansible: industry standard for heterogeneous infrastructure
Polycrate uses Ansible as its core engine – the de-facto industry standard for automating heterogeneous IT landscapes.
graph LR
Polycrate[Polycrate] --> Ansible[Ansible]
Ansible --> AWS[AWS]
Ansible --> Azure[Azure]
Ansible --> GCP[Google Cloud]
Ansible --> K8s[Kubernetes]
Ansible --> Linux[Linux Hosts]
Ansible --> Docker[Docker]
style Ansible fill:#e74c3c,color:#fff
style Polycrate fill:#3498db,color:#fff
Advantages:
- ✅ Non-proprietary: Ansible is open source, no vendor lock-in
- ✅ Universally applicable: AWS, Azure, GCP, bare metal, K8s, Docker
- ✅ Declarative: infrastructure as code, versionable, reproducible
- ✅ Established: over 60% of companies use Ansible (Red Hat Survey 2023)
- ✅ Agentless: no software installation on target systems required
2. Kubernetes-native architecture
Polycrate focuses on Kubernetes as the target platform – the only truly cloud-agnostic infrastructure layer.
# workspace.poly - Kubernetes-native app deployment
name: my-workspace
blocks:
- name: my-cluster
from: registry.my-org.com/blocks/k8s/cluster:1.0.0
- name: my-app
from: registry.my-org.com/blocks/k8s/my-app:1.0.0
kubeconfig:
from: my-cluster # Uses the kubeconfig from the cluster block
config:
namespace: production
replicas: 3
persistence:
enabled: true
size: 50Gi
Why Kubernetes?
- 🌍 Cloud-agnostic: runs identically on AWS, Azure, GCP, bare metal
- 📦 Container-native: standard artifacts (OCI images), no proprietary formats
- 🔄 Portable: workloads can be moved between clusters
- 🛠️ Standardized: CNCF standards (kubectl, Helm, operators)
- 🔓 Lock-in-free: no vendor can "own" Kubernetes
3. Free, production-ready blocks from the PolyHub
The PolyHub offers curated, free blocks for common infrastructure patterns – as hyperscaler alternatives.
Examples: hyperscaler → K8s-native alternative
| Hyperscaler service | Polycrate block (open source) | Technology |
|---|---|---|
| AWS RDS (PostgreSQL) | ayedo/k8s/cloudnative-pg |
CloudNativePG Operator |
| AWS ElastiCache (Redis) | ayedo/k8s/redis |
Redis with Sentinel HA |
| AWS DocumentDB (MongoDB) | ayedo/k8s/mongodb |
MongoDB with replica sets |
| AWS ALB/ELB | ayedo/k8s/nginx |
NGINX Ingress Controller |
| AWS Certificate Manager | ayedo/k8s/cert-manager |
cert-manager + Let's Encrypt |
| AWS ECR | ayedo/k8s/harbor |
Harbor container registry |
| AWS CloudWatch | ayedo/k8s/victoria-metrics-stack |
VictoriaMetrics + Grafana |
| AWS Secrets Manager | ayedo/k8s/hashicorp-vault |
HashiCorp Vault |
| AWS SQS/SNS | ayedo/k8s/rabbitmq-operator |
RabbitMQ Operator |
All PolyHub blocks are:
- 🆓 Open source (Apache 2.0)
- ✅ Production-tested
- 📖 Fully documented
- 🔄 Versioned (semantic versioning)
- 🧪 Extensible (via inheritance)
# Pull a block from the PolyHub
polycrate blocks pull cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.3.1
# Deploy
polycrate run ayedo/k8s/cloudnative-pg install
4. Extensive cloud-native toolchain preinstalled
The Polycrate container ships all the tools needed for cloud migration – no local installations required.
Kubernetes & containers:
kubectl v1.32.1 - Kubernetes CLI
helm v3.18.4 - Package manager for Kubernetes
cilium v0.18.5 - Networking & security
velero v1.16.1 - Backup & disaster recovery
argocd v3.0.6 - GitOps deployment
docker - Container runtime CLI
skopeo - Container image management
Automation & IaC:
ansible-core - Automation engine
yq v4.45.1 - YAML/JSON processor
jq - JSON processor
age v1.2.1 - Encryption tool
Cloud provider CLIs (via Ansible):
AWS (boto3) - AWS SDK for Python
Hetzner (hcloud) - Hetzner Cloud API client
VMware (PyVmomi) - VMware vSphere API
OpenStack - OpenStack APIs
Azure - Azure SDKs (via Ansible modules)
GCP - Google Cloud SDKs (via Ansible modules)
Migration-specific tools:
rsync - Data synchronization
openssh-client - Remote access
docker-compose - Multi-container apps
git - Version control
apache2-utils - HTTP benchmarking (ab, htpasswd)
Ansible collections for providers:
community.general - 1000+ modules for various systems
community.docker - Docker & Docker Compose automation
community.vmware - VMware vSphere automation
netbox.netbox - IPAM & DCIM integration
Assessment workflow with Polycrate:
# workspace.poly - migration assessment
name: aws-migration-assessment
blocks:
- name: aws-inventory
actions:
- name: scan-resources
playbook: scan-aws-resources.yml
- name: export-rds-schemas
playbook: export-rds-schemas.yml
- name: analyze-dependencies
playbook: analyze-dependencies.yml
- name: cost-analysis
actions:
- name: calculate-savings
playbook: calculate-savings.yml
Example: RDS database assessment with preinstalled tools
# scan-aws-resources.yml
---
- name: Scan AWS RDS Instances
hosts: localhost
tasks:
- name: Get all RDS instances
amazon.aws.rds_instance_info:
region: eu-central-1
register: rds_instances
- name: Export to JSON
copy:
content: "{{ rds_instances | to_nice_json }}"
dest: "{{ artifacts_root }}/aws-rds-inventory.json"
- name: Analyze schemas
shell: |
for instance in $(cat {{ artifacts_root }}/aws-rds-inventory.json | jq -r '.instances[].db_instance_identifier'); do
pg_dump -h ${instance}.abc123.eu-central-1.rds.amazonaws.com \
-U admin \
--schema-only \
-f {{ artifacts_root }}/${instance}-schema.sql
done
Execution:
# Everything runs in the Polycrate container - no local tools required!
polycrate run aws-inventory scan-resources
polycrate run aws-inventory export-rds-schemas
polycrate run aws-inventory analyze-dependencies
polycrate run cost-analysis calculate-savings
Advantages:
- ✅ No local installation: all tools in the container, consistent versions
- ✅ Cross-provider: AWS, Azure, GCP, VMware, Hetzner – all included
- ✅ Assessment-ready: start with inventory right away
- ✅ Migration-ready: schema export, data sync, testing – all available
- ✅ Versioned: exactly defined tool versions (see
Dockerfile.goreleaser)
5. Provider-independent infrastructure as code
Polycrate workspaces are fully portable – no lock-in to tools, providers or platforms.
# workspace.poly - provider-independent description
name: production-stack
blocks:
# Cluster on any provider (Linux servers)
- name: k8s-cluster
from: cargo.ayedo.cloud/ayedo/linux/k8s-1.27:0.3.7
config:
servers: 3
agents: 5
# Apps on the cluster (provider-agnostic)
- name: postgres
from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.3.1
kubeconfig:
from: k8s-cluster
config:
namespace: databases
persistence:
size: 100Gi
storageClass: fast-ssd
- name: app
from: webapp-base:1.0.0
kubeconfig:
from: k8s-cluster
config:
domain: myapp.example.com
replicas: 3
workflows:
- name: deploy-complete-stack
steps:
- name: provision-cluster
block: k8s-cluster
action: provision
- name: deploy-postgres
block: postgres
action: install
- name: deploy-app
block: app
action: install
This workspace runs identically on:
- ☁️ AWS (via Hetzner Cloud, EKS, EC2)
- ☁️ Azure (AKS, VMs)
- ☁️ Google Cloud (GKE, Compute Engine)
- 🖥️ Bare metal (your own servers)
- 🏠 On-premise (VMware, Proxmox)
Switching? → Just swap the kubeconfig, everything else stays identical.
6. OCI-registry-based sharing
Polycrate uses OCI-compatible registries (Docker, Harbor, etc.) for block distribution – an open standard, no proprietary format.
# Develop a block
cd blocks/my-custom-postgres
# Push to a private registry
polycrate block push registry.company.com/infra/postgres:1.0.0
# Use in another workspace
polycrate blocks pull registry.company.com/infra/postgres:1.0.0
Data Act compliant:
- ✅ Machine-readable format (OCI Image Spec)
- ✅ Open standard (no vendor lock-in)
- ✅ Portable (any OCI registry works)
- ✅ Versioned (image tags)
The migration path: from hyperscaler to sovereign K8s-native infrastructure
Phase 1: assessment & planning
Goal: inventory of the current hyperscaler dependencies
# Workspace for migration assessment
name: migration-assessment
blocks:
- name: inventory
actions:
- name: scan-aws
playbook: scan-aws.yml
- name: analyze-dependencies
playbook: analyze-dependencies.yml
Result: inventory of all resources, service dependencies, migration candidates
Phase 2: target architecture design
Goal: identify K8s-native alternatives for every hyperscaler service
Migration strategy:
graph TB
AWS[AWS Services] --> Analysis[Analysis & Mapping]
Analysis --> K8s[Kubernetes-Native<br/>Alternatives]
AWS --> RDS[RDS PostgreSQL]
AWS --> ElastiCache[ElastiCache Redis]
AWS --> ELB[ALB/ELB]
AWS --> ECR[ECR]
AWS --> CloudWatch[CloudWatch]
RDS --> PG[CloudNativePG<br/>ayedo/k8s/cloudnative-pg]
ElastiCache --> Redis[Redis Sentinel<br/>ayedo/k8s/redis]
ELB --> Ingress[NGINX Ingress<br/>ayedo/k8s/nginx]
ECR --> Harbor[Harbor Registry<br/>ayedo/k8s/harbor]
CloudWatch --> Prom[VictoriaMetrics + Grafana<br/>ayedo/k8s/victoria-metrics-stack]
K8s --> PG
K8s --> Redis
K8s --> Ingress
K8s --> Harbor
K8s --> Prom
style AWS fill:#ff9900,color:#fff
style K8s fill:#326ce5,color:#fff
style Analysis fill:#3498db,color:#fff
Phase 3: parallel build-up with Polycrate
Goal: build the new K8s-native infrastructure in parallel to the existing one
# workspace.poly - migration workspace
name: migration-production
blocks:
# New K8s cluster on Linux servers
- name: new-cluster
from: cargo.ayedo.cloud/ayedo/linux/k8s-1.27:0.3.7
config:
servers: 3
agents: 5
# PostgreSQL migration (RDS → CloudNativePG)
- name: postgres-migration
from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.3.1
kubeconfig:
from: new-cluster
config:
namespace: databases
app:
persistence:
size: 200Gi # Take the size from RDS
storageClass: fast-ssd
backup:
enabled: true
schedule: "0 2 * * *"
actions:
- name: migrate-from-rds
playbook: migrate-rds.yml
# Redis migration (ElastiCache → Redis Sentinel)
- name: redis-migration
from: cargo.ayedo.cloud/ayedo/k8s/redis:0.1.5
kubeconfig:
from: new-cluster
config:
namespace: cache
persistence:
enabled: true
size: 50Gi
# Ingress (ALB → NGINX)
- name: ingress
from: registry.my-org.com/blocks/k8s/nginx:1.0.0
kubeconfig:
from: new-cluster
# Monitoring (CloudWatch → VictoriaMetrics + Grafana)
- name: monitoring
from: cargo.ayedo.cloud/ayedo/k8s/victoria-metrics-stack:1.0.0
kubeconfig:
from: new-cluster
config:
retention: 30d
storage: 100Gi
workflows:
- name: setup-target-infrastructure
steps:
- name: provision-cluster
block: new-cluster
action: provision
- name: deploy-ingress
block: ingress
action: install
- name: deploy-monitoring
block: monitoring
action: install
- name: deploy-postgres
block: postgres-migration
action: install
- name: deploy-redis
block: redis-migration
action: install
- name: migrate-data
prompt:
message: "Start data migration? (Downtime required)"
steps:
- name: backup-rds
block: postgres-migration
action: backup-from-rds
- name: stop-old-app
block: legacy-app
action: stop
- name: restore-to-cloudnativepg
block: postgres-migration
action: restore
- name: verify-data
block: postgres-migration
action: verify
- name: start-new-app
block: new-app
action: start
Execution:
# Build the target infrastructure
polycrate workflows run setup-target-infrastructure --blocks-auto-pull
# Perform the data migration
polycrate workflows run migrate-data --force
# Verification
polycrate run postgres-migration verify
polycrate run new-app health-check
Phase 4: cutover & validation
Goal: redirect traffic to the new infrastructure, shut down the old one
# Cutover workflow
workflows:
- name: cutover
prompt:
message: "Start production cutover?"
steps:
# Switch DNS/load balancer to the new system
- name: update-dns
block: dns
action: point-to-new-cluster
# Traffic validation
- name: monitor-traffic
block: monitoring
action: watch-metrics
# Stop old services (after soak period)
- name: shutdown-old-services
block: legacy
action: stop
Phase 5: decommission hyperscaler resources
Goal: tear down the old hyperscaler resources
workflows:
- name: cleanup-aws
prompt:
message: "Really delete AWS resources?"
steps:
- name: delete-rds
block: aws-cleanup
action: delete-rds-instance
- name: delete-elasticache
block: aws-cleanup
action: delete-elasticache-cluster
- name: delete-alb
block: aws-cleanup
action: delete-load-balancers
Practical examples
Example 1: PostgreSQL migration (AWS RDS → CloudNativePG)
# workspace.poly - PostgreSQL migration block definition
name: migration-production
blocks:
- name: postgres-migration
from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.3.1
kubeconfig:
from: new-cluster
config:
namespace: databases
aws:
rds_endpoint: "mydb.abc123.eu-central-1.rds.amazonaws.com"
rds_database: "production"
rds_user: "admin"
actions:
- name: backup-from-rds
playbook: backup-rds.yml
- name: restore
playbook: restore.yml
- name: verify
playbook: verify.yml
# backup-rds.yml
---
- name: Backup from AWS RDS
hosts: localhost
tasks:
- name: Create backup from RDS
ansible.builtin.shell: |
pg_dump \
-h {{ block.config.aws.rds_endpoint }} \
-U {{ block.config.aws.rds_user }} \
-d {{ block.config.aws.rds_database }} \
-F custom \
-f {{ artifacts_root }}/blocks/postgres-migration/rds-backup.dump
environment:
PGPASSWORD: "{{ lookup('env', 'RDS_PASSWORD') }}"
- name: Verify backup
ansible.builtin.stat:
path: "{{ artifacts_root }}/blocks/postgres-migration/rds-backup.dump"
register: backup_file
- name: Fail if backup missing
ansible.builtin.fail:
msg: "Backup file not created"
when: not backup_file.stat.exists
# restore.yml
---
- name: Restore to CloudNativePG
hosts: localhost
tasks:
- name: Get CloudNativePG primary pod
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig_path }}"
kind: Pod
namespace: "{{ block.config.namespace }}"
label_selectors:
- "cnpg.io/cluster={{ block.name }}"
- "role=primary"
register: primary_pod
- name: Copy backup to pod
kubernetes.core.k8s_cp:
kubeconfig: "{{ kubeconfig_path }}"
namespace: "{{ block.config.namespace }}"
pod: "{{ primary_pod.resources[0].metadata.name }}"
remote_path: "/tmp/backup.dump"
local_path: "{{ artifacts_root }}/blocks/postgres-migration/rds-backup.dump"
state: to_pod
- name: Restore backup
kubernetes.core.k8s_exec:
kubeconfig: "{{ kubeconfig_path }}"
namespace: "{{ block.config.namespace }}"
pod: "{{ primary_pod.resources[0].metadata.name }}"
command: |
pg_restore \
-d {{ block.config.aws.rds_database }} \
-U postgres \
/tmp/backup.dump
Execution:
# Backup from RDS
export RDS_PASSWORD="your-rds-password"
polycrate run postgres-migration backup-from-rds
# Restore into CloudNativePG
polycrate run postgres-migration restore
# Verification
polycrate run postgres-migration verify
Example 2: complete stack migration
# workspace.poly - full stack migration
name: complete-migration
blocks:
# Cluster on Linux servers
- name: cluster
from: cargo.ayedo.cloud/ayedo/linux/k8s-1.27:0.3.7
config:
servers: 3
agents: 5
# Databases
- name: postgres
from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.3.1
kubeconfig:
from: cluster
- name: redis
from: cargo.ayedo.cloud/ayedo/k8s/redis:0.1.5
kubeconfig:
from: cluster
# Infrastructure
- name: ingress
from: registry.my-org.com/blocks/k8s/nginx:1.0.0
kubeconfig:
from: cluster
- name: cert-manager
from: registry.my-org.com/blocks/k8s/cert-manager:1.0.0
kubeconfig:
from: cluster
# Monitoring
- name: monitoring
from: cargo.ayedo.cloud/ayedo/k8s/victoria-metrics-stack:1.0.0
kubeconfig:
from: cluster
# Registry
- name: harbor
from: registry.my-org.com/blocks/k8s/harbor:1.0.0
kubeconfig:
from: cluster
workflows:
- name: migrate-complete-stack
steps:
# Infrastructure
- name: provision-cluster
block: cluster
action: provision
- name: deploy-ingress
block: ingress
action: install
- name: deploy-cert-manager
block: cert-manager
action: install
# Monitoring
- name: deploy-monitoring
block: monitoring
action: install
# Databases
- name: deploy-postgres
block: postgres
action: install
- name: deploy-redis
block: redis
action: install
# Registry
- name: deploy-harbor
block: harbor
action: install
# Perform the entire migration
polycrate workflows run migrate-complete-stack --blocks-auto-pull
Advantages of a Polycrate-based migration
1. Data Act compliance
- ✅ Open standards: Ansible, Kubernetes, OCI images
- ✅ Machine-readable formats: YAML, JSON
- ✅ Full portability: workspaces can be moved between providers
- ✅ No egress fees: block transfer via standard registries
- ✅ Functional equivalence: K8s-native services offer the same features
2. Sovereignty & control
- 🏛️ EU hosting: Kubernetes clusters can be operated in Germany/the EU
- 🔐 Full control: no vendor can force access
- 🔑 Customer-managed keys: encryption with your own keys
- 🛡️ DORA/NIS-2 compliant: complete exit strategies documented
3. Cost efficiency
- 💰 No egress fees: data transfer between your own systems is free
- 📉 Transparent prices: bare metal/Hetzner instead of AWS markup
- 🆓 Open-source stack: PostgreSQL instead of RDS, Redis instead of ElastiCache
- 📊 Predictable costs: no surprises from hidden fees
4. Technical independence
- 🔄 Provider switch: switch to another Kubernetes provider at any time
- 🧱 Modular architecture: services can be replaced individually
- 📦 Standard artifacts: container images, Helm charts, Ansible playbooks
- 🌍 No vendor binding: open-source ecosystem instead of vendor lock
- 🛠️ Extensive toolchain: kubectl, helm, ansible & cloud provider CLIs preinstalled
5. Compliance & audit
- 📋 Documented processes: all migration steps versioned in Git
- 🔍 Reproducible: exactly the same infrastructure can be rebuilt at any time
- 📊 Audit trail: complete history of all changes
- ✅ Certifiable: ISO 27001, BSI IT-Grundschutz compatible
Relation to other concepts
- Why Polycrate?: fundamental Polycrate philosophy
- PolyHub: free blocks as hyperscaler alternatives
- Ansible: industry standard for automation
- Kubernetes: cloud-agnostic target platform
- Workflows: orchestration of complex migration processes
- Registry: OCI-based block sharing for portability
- Recipes: practical migration examples
Next steps
-
Perform an assessment: - Install Polycrate:
curl -sSL https://get.polycrate.io | bash- Create an inventory workspace with the preinstalled cloud provider tools - Scan AWS/Azure/GCP resources (boto3, Azure SDK, GCP SDK already included) - Perform a cost analysis -
Explore the PolyHub: - Visit hub.polycrate.io for K8s-native alternatives - Create a hyperscaler service → PolyHub block mapping
-
Start a pilot: - Migrate a non-critical service as a proof of concept - Get started right away with the preinstalled tools (kubectl, helm, ansible)
-
Build the workspace: - Build the target infrastructure in parallel to the hyperscaler - Use PolyHub blocks for Postgres, Redis, monitoring etc.
-
Plan the data migration: - Define backup/restore workflows with Ansible - Schema export via pg_dump/mongodump (preinstalled)
-
Perform the cutover: - Redirect traffic to the new infrastructure - Monitoring with the VictoriaMetrics stack
-
Decommission: - Tear down the old hyperscaler resources - Automate with provider-specific Ansible modules
Ready for sovereign cloud infrastructure?
Contact ayedo for: - Migration assessments - Architecture workshops - Hands-on support with the implementation - Custom block development for specific use cases