The Context
When I was brought in to participate in the Asegurus project, the situation was typical of companies that grew fast: legacy infrastructure that worked, but no longer scaled. Old clusters, manual deployments, databases without centralized management, and a team distributed between Mexico and Argentina that needed to collaborate more efficiently.
This post is a summary of how we approached the migration, what decisions we made, and the lessons the process left us with.
The Initial State
The existing infrastructure had several problems common to legacy setups:
- Outdated clusters — Old orchestrator versions without support, with configurations nobody wanted to touch for fear of breaking something
- Manual deployments — The process of getting code to production involved SSHs, homemade scripts, and a concerning number of steps that depended on tribal knowledge
- Self-hosted databases — MySQL and PostgreSQL running on VMs without decent automated backups, no failover, no real monitoring
- Zero CI/CD — Or rather, there was a half-abandoned Jenkins that nobody trusted enough to touch
- Isolated teams — Devs in Mexico did their part, those in Argentina did theirs, and integration was a constant headache
It worked, but barely. And every time something needed to scale or a significant change was required, it was a high-risk operation.
The Migration Strategy
We weren't going to do a big bang. That never works. The strategy was incremental migration with rollback at every step.
Phase 1: Modern Kubernetes
The first step was setting up updated, well-configured Kubernetes clusters:
- Managed clusters — We stopped maintaining our own masters. We used managed Kubernetes services so the cloud provider handles the control plane
- Namespaces by team and environment — Development, staging, and production clearly separated, with RBAC so each team only had access to what they needed
- Standardized Helm charts — Every service has its chart with per-environment values. No loose yamls or ad-hoc configurations
- Unified ingress — A single entry point with automatic TLS, rate limiting, and header-based routing
The migration was service by service. We'd spin up the service in the new cluster, run it in parallel with legacy, validate everything worked the same, then switch traffic. If something failed, immediate rollback.
Phase 2: Managed Databases
This was probably the most delicate step. Migrating production databases without downtime is one of the most stressful things you can do in infrastructure.
- RDS/Cloud SQL — We moved all databases to managed services with read replicas, hourly automatic backups, and automatic failover
- Migration with replicas — We configured replication from legacy databases to the new ones, let them sync, then did the cutover during low-traffic windows
- Connection pooling — We implemented PgBouncer for PostgreSQL and ProxySQL for MySQL, reducing connection overhead and improving latency
- Monitoring from day one — Grafana alerts for replication lag, query times, active connections, and disk space
There were long nights. But in the end, no database migration had more than 30 seconds of downtime, and most were zero-downtime.

Phase 3: Automated CI/CD
With the new infrastructure in place, we needed to automate the entire deployment flow:
- GitHub Actions as the main CI/CD engine
- Standard pipeline: lint → test → build → push image → deploy to staging → smoke tests → deploy to production
- GitOps with ArgoCD — The desired state of each environment lives in Git. ArgoCD handles syncing the cluster with what's in the repo
- Canary deployments — For critical services, we don't deploy 100% at once. We start with 10% of traffic, monitor metrics, and if everything looks good, scale gradually
- Automatic rollback — If error rate metrics go above threshold during a canary, ArgoCD automatically reverts
The result: from deployments that took hours and required someone on-call, we moved to automatic deployments that take less than 15 minutes from push to production.
Phase 4: Distributed Collaboration
With teams in Mexico and Argentina, we needed collaboration to be fluid:
- Preview environments per PR — Each pull request spins up its own ephemeral environment so anyone can test changes without waiting for staging
- Documentation as code — ADRs (Architecture Decision Records) in the repo, not in wikis that nobody updates
- Rotating on-call — Shared on-call calendar with documented playbooks for each incident type
- Unified observability — A single Grafana, a single stack of logs, metrics, and traces that all teams can query
The Lessons
After months of migration, there are several takeaways:
What Worked
- Incremental migration — Always. Never big bang. The cost of going slower is infinitely less than breaking everything
- Invest in observability first — Before migrating any service, make sure you can see what's happening. If you can't measure, you can't migrate with confidence
- Ephemeral environments — Game changer for distributed teams. Eliminates the "staging is busy" bottleneck
- Document decisions, not just code — ADRs were key for new team members to understand the why behind each architectural decision
What I'd Do Differently
- Start with CI/CD before infrastructure — In hindsight, automating deployments first would have made the infra migration smoother
- More load testing early on — We did load testing, but should have started earlier. Some bottlenecks were discovered late
- More structured communication — With two countries and multiple time zones, async decisions need more process than we had at the beginning
The Numbers
Because at the end of the day, what matters is results:
- Deploy time: from ~2 hours (manual) to ~12 minutes (automated)
- Monthly downtime: from ~4 hours to less than 5 minutes
- Recovery time: from hours to minutes (automatic rollback)
- Infra cost: 35% reduction by consolidating and using managed resources
- Developer experience: internal surveys went from 4.2/10 to 8.1/10
Closing
Migrating legacy infrastructure is never sexy. It's not the kind of project that generates immediate applause because most of the work is invisible to the end user. But it's the kind of work that determines whether your team can move fast or if every change is a gamble.
Asegurus' stack today is modern, automated, observable, and manageable by a distributed team. It's not perfect — nothing is — but the difference between where we were and where we are now is on another level.
If you're considering a similar migration, my advice is simple: start small, measure everything, and don't try to be a hero. Infrastructure gets migrated one service at a time.