{"id":4214,"date":"2026-02-18T10:36:23","date_gmt":"2026-02-18T13:36:23","guid":{"rendered":"https:\/\/beontech.wpengine.com\/?p=4214"},"modified":"2026-04-06T09:07:24","modified_gmt":"2026-04-06T12:07:24","slug":"from-elastic-beanstalk-to-ec2","status":"publish","type":"post","link":"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/","title":{"rendered":"From Elastic Beanstalk to EC2: Regaining Control in Production"},"content":{"rendered":"\n<p>Elastic Beanstalk served us well for years. It allowed us to move fast, abstract infrastructure complexity, and focus on shipping product. But as the system matured, the very abstractions that once accelerated us started introducing operational friction.<\/p>\n\n\n\n<p>In this article, we\u2019ll walk through how we migrated a production Ruby on Rails application from AWS Elastic Beanstalk to a custom EC2-based infrastructure powered by Auto Scaling Groups. We\u2019ll break down the reasoning behind the decision, the architectural redesign, and the step-by-step process that allowed us to execute the migration with zero downtime.<\/p>\n\n\n\n<p>You\u2019ll see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Why Elastic Beanstalk started limiting us in a mature production system<\/li>\n\n\n\n<li>How we redesigned our infrastructure using EC2 and Auto Scaling Groups<\/li>\n\n\n\n<li>The blue\/green deployment strategy that eliminated downtime<\/li>\n\n\n\n<li>How we rebuilt observability with Grafana and Loki<\/li>\n\n\n\n<li>The migration phases, challenges, and lessons learned<\/li>\n\n\n\n<li>The measurable results after the cutover<br><\/li>\n<\/ul>\n\n\n\n<p>By the end, you\u2019ll have a clear picture of what it takes to move away from a managed PaaS and fully own your production infrastructure.<\/p>\n\n\n\n<p>So without further ado, let\u2019s get into it.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why We Decided to Migrate<\/strong><\/h2>\n\n\n\n<p>Elastic Beanstalk is a powerful PaaS. For early-stage products, it removes complexity and enables rapid deployment. However, long-lived production systems have different priorities: determinism, deep visibility, and runtime stability.<\/p>\n\n\n\n<p>Over time, several issues began to compound.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Cost Inefficiency&nbsp;<\/strong><\/h3>\n\n\n\n<p>Cost alone didn\u2019t justify the migration, but it was an early indicator that we were paying for abstraction layers we no longer needed.<\/p>\n\n\n\n<p>Immediately, we could compare both schemas.<\/p>\n\n\n\n<p><strong>Monthly AWS Costs (Elastic Beanstalk)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>EB Environment Fee: $0.01\/hour \u00d7 730 hours = $7.30<\/li>\n\n\n\n<li>Classic Load Balancer: $22\/month<\/li>\n\n\n\n<li>2\u00d7 EC2 t3a.small: $30\/month<\/li>\n\n\n\n<li>RDS db.t3.micro: $30\/month<\/li>\n\n\n\n<li>Data transfer: $5\/month<\/li>\n\n\n\n<li>CloudWatch logs: $3\/month<br><\/li>\n<\/ul>\n\n\n\n<p><strong>TOTAL: ~$97\/month<\/strong><\/p>\n\n\n\n<p><strong>Custom EC2 + ASG (Projected)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Application Load Balancer: $16\/month<\/li>\n\n\n\n<li>2\u00d7 EC2 t3a.small: $30\/month<\/li>\n\n\n\n<li>RDS db.t3.micro: $30\/month<\/li>\n\n\n\n<li>Data transfer: $5\/month<\/li>\n\n\n\n<li>CloudWatch logs: $1\/month<br><\/li>\n<\/ul>\n\n\n\n<p><strong>TOTAL: ~$82\/month<\/strong><\/p>\n\n\n\n<p>Executing a correct migration from Elastic Beanstalk to custom EC2 instances would result in approximately <strong>$15\/month (15.7%) in savings<\/strong>.<\/p>\n\n\n\n<p>That number alone isn\u2019t transformative. But it revealed something more important: we were paying for convenience layers \u2014 environment management, Classic Load Balancer defaults, platform coupling \u2014 that no longer provided proportional value to a mature production system.<\/p>\n\n\n\n<p>The cost reduction was a side effect. The real win was control.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Limited Customization<\/strong><\/h3>\n\n\n\n<p>Elastic Beanstalk manages the instance lifecycle. That includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OS configuration<\/li>\n\n\n\n<li>Runtime installation<\/li>\n\n\n\n<li>Service orchestration<\/li>\n\n\n\n<li>nginx configuration<\/li>\n\n\n\n<li>Deployment flow<br><\/li>\n<\/ul>\n\n\n\n<p>At first, this feels convenient. Over time, it becomes restrictive.<\/p>\n\n\n\n<p>Customizing behavior means relying on .ebextensions and platform hooks \u2014 YAML files with embedded Bash scripts executed in a partially opaque order. Debugging failures becomes difficult. Logs are scattered across multiple locations. When an instance fails, it may be replaced automatically before proper root cause analysis is even possible.<\/p>\n\n\n\n<p>As infrastructure grows in complexity, this abstraction layer makes the system harder to reason about.<\/p>\n\n\n\n<p>What we need instead is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Deterministic startup order<\/li>\n\n\n\n<li>Explicit service definitions<\/li>\n\n\n\n<li>Predictable dependency management<\/li>\n\n\n\n<li>Clear ownership of runtime configuration<br><\/li>\n<\/ul>\n\n\n\n<p>At that stage, the abstraction no longer reduces complexity \u2014 it hides it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Deployment Artifacts Expiration<\/strong><\/h3>\n\n\n\n<p>One of the most subtle but dangerous issues involved deployment artifacts. Elastic Beanstalk relied on build artifacts generated by GitHub Actions. Those artifacts expire after 90 days. If an auto-scaling event occurred months after the last deployment, the new instance might attempt to fetch an artifact that no longer existed.<\/p>\n\n\n\n<p>Nothing in the code had changed \u2014 yet the instance could fail to launch.<\/p>\n\n\n\n<p>Scaling events should be boring and predictable. In our case, they were time-sensitive and fragile. That coupling between historical CI artifacts and runtime infrastructure was unacceptable.<\/p>\n\n\n\n<p><strong>Ruby Version Constraints<\/strong><\/p>\n\n\n\n<p>Our application depended on Ruby 2.6.6 and OpenSSL 1.1. Meanwhile, newer Amazon Linux distributions ship with OpenSSL 3.<\/p>\n\n\n\n<p>Elastic Beanstalk manages the underlying OS and runtime. When the platform updated, dependency changes occasionally broke compatibility. These incidents often surfaced during business hours and required urgent fixes.<\/p>\n\n\n\n<p>Production infrastructure should not be a moving target controlled by upstream platform updates. We needed to explicitly own the runtime stack.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Observability Gaps<\/strong><\/h3>\n\n\n\n<p>Elastic Beanstalk provides metrics and logs, but primarily at the instance level. That works for basic debugging, but it doesn\u2019t give us system-wide visibility.<\/p>\n\n\n\n<p>What we need is a unified, centralized view of production behavior.<\/p>\n\n\n\n<p>We want:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Log aggregation across all instances<\/li>\n\n\n\n<li>Real-time search during incidents<\/li>\n\n\n\n<li>Cross-instance traceability<\/li>\n\n\n\n<li>Long-term retention (30+ days)<\/li>\n\n\n\n<li>Custom dashboards combining infrastructure and application metrics<\/li>\n<\/ul>\n\n\n\n<p>Downloading log bundles after an issue occurs isn\u2019t observability. It\u2019s reactive troubleshooting. True observability means detecting, diagnosing, and resolving issues with live data \u2014 not after-the-fact investigation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Architecture<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Before Migration<\/strong><\/h3>\n\n\n\n<p>Before the migration, the infrastructure follows a standard Elastic Beanstalk-managed setup. The load balancer, auto scaling configuration, instance lifecycle, and application bootstrap are all controlled by the platform. While this simplifies initial deployment, it also means many runtime decisions happen implicitly. The diagram below illustrates how traffic flows from Route 53 through a Classic Load Balancer into EB-managed EC2 instances, where nginx and the application server are configured automatically by the platform.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"955\" height=\"763\" src=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-3-955x763.png\" alt=\"Infrastructure before migration\" class=\"wp-image-4215\" style=\"width:580px;height:auto\" srcset=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-3-955x763.png 955w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-3-300x240.png 300w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-3-768x614.png 768w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-3.png 1084w\" sizes=\"auto, (max-width: 955px) 100vw, 955px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Deployment Flow<\/strong><\/h3>\n\n\n\n<p>The deployment process in Elastic Beanstalk relies on uploading an application version and triggering a platform-managed update. During this process, instances download the new artifact, execute .ebextensions scripts, and restart services in place. Because services are restarted on active instances, this flow inherently introduces brief downtime. The following diagram shows how a push to master propagates through CodeBuild and Elastic Beanstalk before reaching EC2 instances.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><img loading=\"lazy\" decoding=\"async\" src=\"blob:https:\/\/beontech.wpengine.com\/c0df20ed-0f16-4516-9a1c-d48416fec416\" width=\"624\" height=\"353\"><\/h3>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>After Migration<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Custom EC2 + ASG Architecture<\/strong><\/h3>\n\n\n\n<p>After the migration, the architecture maintains a similar high-level structure but shifts control to explicitly managed components. Instead of relying on EB-managed lifecycles, each EC2 instance bootstraps itself using versioned scripts. An Application Load Balancer distributes traffic across an Auto Scaling Group configured for blue\/green instance refreshes. Monitoring services run alongside application services, providing full visibility into runtime behavior. The diagram below outlines the new flow and service responsibilities.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"661\" src=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-5-1024x661.png\" alt=\"Custom EC2 + ASG Architecture\" class=\"wp-image-4218\" style=\"width:574px;height:auto\" srcset=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-5-1024x661.png 1024w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-5-300x194.png 300w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-5-768x496.png 768w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-5.png 1328w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Custom CI\/CD flow<\/strong><\/h3>\n\n\n\n<p>The CI\/CD pipeline also changes significantly. Instead of pushing artifacts directly into Elastic Beanstalk, the deployment process builds the application in GitHub Actions, uploads the bundle to S3, and triggers an Auto Scaling Group instance refresh. New instances launch, fetch artifacts during bootstrap, pass health checks, and only then receive production traffic. This flow guarantees zero-downtime deployments and eliminates dependency on expiring CI artifacts. The diagram below illustrates the end-to-end pipeline.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"579\" src=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4-1024x579.png\" alt=\" CI\/CD pipeline\" class=\"wp-image-4216\" style=\"width:579px;height:auto\" srcset=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4-1024x579.png 1024w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4-300x170.png 300w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4-768x434.png 768w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4-1536x869.png 1536w, https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/image-4.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The Migration Journey<\/strong><\/h2>\n\n\n\n<p>Elastic Beanstalk does its job well for a long time. It allows teams to ship fast, abstracts away infrastructure concerns, and reduces operational overhead during the early stages of a product. But as the system matures, those same abstractions begin working against us.<\/p>\n\n\n\n<p>At that stage, the goal is no longer just to \u201cleave Beanstalk.\u201d The objective becomes regaining control, predictability, and observability \u2014 while keeping downtime at zero.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Phase 1: Planning and Assessment (Week 1)<\/strong><\/h3>\n\n\n\n<p>Before changing anything, we export the full Elastic Beanstalk configuration:<\/p>\n\n\n\n<p>eb config save production &#8211;cfg production-snapshot<\/p>\n\n\n\n<p>We audit every .ebextensions file to understand how the environment is being built. These files contain embedded shell scripts responsible for installing Ruby, configuring nginx, defining services, and setting up system dependencies.<\/p>\n\n\n\n<p>This phase is less about implementation and more about eliminating unknowns. We document assumptions, identify hidden coupling, and map out potential risk scenarios.<\/p>\n\n\n\n<p>We define mitigation strategies early:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Blue\/green deployment to eliminate downtime<\/li>\n\n\n\n<li>Parallel environments for safe validation<\/li>\n\n\n\n<li>IAM roles and Secrets Manager for secure configuration<\/li>\n\n\n\n<li>Observability implemented before cutover<\/li>\n<\/ul>\n\n\n\n<p>Once everything becomes explicit, the migration stops being intimidating and becomes systematic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Phase 2: Building Custom Infrastructure (Weeks 2\u20133)<\/strong><\/h3>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Breaking the Monolith into Modular Setup Scripts<\/strong><\/h4>\n\n\n\n<p>Elastic Beanstalk relied on large .ebextensions files containing long Bash scripts embedded inside YAML. One of them contained more than 150 lines of Ruby installation logic which was difficult to read, impossible to test locally, and painful to debug.<\/p>\n\n\n\n<p>The first major architectural decision was to split this logic into focused, idempotent shell scripts. Each script had a single responsibility and could be executed safely multiple times.<\/p>\n\n\n\n<p>For example, Ruby installation was extracted into a standalone script that handled OpenSSL compatibility, used rbenv, and exited early if Ruby was already installed. This immediately improved readability, testability, and failure diagnostics. Instead of guessing what Beanstalk did, we now owned the runtime.<\/p>\n\n\n\n<p>The result was a set of nine scripts, executed in a strict order, each responsible for one concern: environment variables, packages, Redis, Node.js, Ruby, PDF tooling, directory layout, application deployment, and monitoring.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Moving Deployment Artifacts to S3<\/strong><\/h4>\n\n\n\n<p>One of the most subtle issues with our previous setup was artifact expiration. GitHub Actions artifacts expire after 90 days, which means that auto-scaling events could fail months after the last deployment.<\/p>\n\n\n\n<p>The solution was to decouple deployments from CI artifact retention entirely. GitHub Actions now builds the application once and uploads the deployment bundle to S3 with no expiration policy.<\/p>\n\n\n\n<p>aws s3 cp deploy.tar.gz \\<\/p>\n\n\n\n<p>&nbsp;&nbsp;s3:\/\/ignisgravitas\/deployments\/ig_ror\/deploy.tar.gz<\/p>\n\n\n\n<p>New EC2 instances download artifacts directly from S3 during bootstrap. This guarantees that scaling events are deterministic, regardless of when they happen.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Implementing Blue\/Green Deployments with ASG Instance Refresh<\/strong><\/h4>\n\n\n\n<p>Instead of restarting services in place, we switched to instance refresh\u2013based deployments using Auto Scaling Groups.<\/p>\n\n\n\n<p>A deployment now triggers a rolling instance refresh with a minimum healthy percentage of 90 percent. New instances boot, install dependencies, deploy the application, and pass health checks before traffic is shifted. Old instances are only terminated once the new ones are confirmed healthy.<\/p>\n\n\n\n<p>This change alone eliminated deployment-related downtime entirely.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Adding Real Observability with Grafana and Loki<\/strong><\/h4>\n\n\n\n<p>Elastic Beanstalk\u2019s built-in logging was sufficient for basic debugging but insufficient for real production observability.<\/p>\n\n\n\n<p>We introduced a self-hosted Grafana, Loki, and Promtail stack running in Docker on each instance. Application logs, nginx logs, and background job logs are all streamed and aggregated centrally. Logs are searchable in real time, retained for more than 30 days, and correlated across instances.<\/p>\n\n\n\n<p>For the first time, production debugging stopped feeling reactive.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Phase 3: Parallel Testing (Week 4)<\/strong><\/h3>\n\n\n\n<p>Rather than cutting over immediately, we ran both environments side by side.<\/p>\n\n\n\n<p>The primary domain continued serving traffic from Elastic Beanstalk, while a subdomain routed traffic to the new Auto Scaling Group. This allowed us to validate behavior under real conditions.<\/p>\n\n\n\n<p>We tested:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Deployments,\u00a0<\/li>\n\n\n\n<li>Load handling,\u00a0<\/li>\n\n\n\n<li>Auto scaling behavior, and\u00a0<\/li>\n\n\n\n<li>Monitoring visibility.\u00a0<\/li>\n<\/ul>\n\n\n\n<p>Several issues surfaced, including nginx caching dynamic JavaScript responses, Redis container restart races, Puma socket permission problems, and Secrets Manager edge cases. Each issue was fixed systematically before proceeding.<\/p>\n\n\n\n<p>By the end of this phase, the new environment behaved identically to production, but with better tooling and fewer unknowns.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Phase 4: Production Cutover (Week 5)<\/strong><\/h3>\n\n\n\n<p>The final transition uses Route 53 weighted routing. Traffic shifts gradually from 10% to 50%, and finally to 100%, with monitoring between each step.<\/p>\n\n\n\n<p>After 24 hours of stable operation, Elastic Beanstalk is permanently terminated. No downtime occurs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Implementation Highlights<\/strong><\/h2>\n\n\n\n<p>Certain implementation decisions had outsized impact.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Idempotent Setup Scripts<\/strong><\/h3>\n\n\n\n<p>Every bootstrap script:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fails fast<\/li>\n\n\n\n<li>Checks whether work is already done<\/li>\n\n\n\n<li>Performs a single responsibility<\/li>\n\n\n\n<li>Verifies success<\/li>\n<\/ul>\n\n\n\n<p>This made instance launches predictable and repeatable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Atomic Deployments<\/strong><\/h3>\n\n\n\n<p>Deployments use timestamped release directories and atomic symlink switches. Partial deployments never affect live traffic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Explicit Runtime Ownership<\/strong><\/h3>\n\n\n\n<p>Ruby 2.6.6 requires OpenSSL 1.1, while Amazon Linux 2023 ships with OpenSSL 3. Instead of fighting the OS, we compiled OpenSSL 1.1 from source and built Ruby against it.<\/p>\n\n\n\n<p>The runtime is now insulated from upstream OS changes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Results<\/strong><\/h2>\n\n\n\n<p>The migration delivered measurable improvements:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>15% lower infrastructure costs<\/li>\n\n\n\n<li>40% faster deployments<\/li>\n\n\n\n<li>Zero deployment downtime<\/li>\n\n\n\n<li>Centralized, real-time observability<\/li>\n\n\n\n<li>Complete infrastructure control<\/li>\n<\/ul>\n\n\n\n<p>More importantly, infrastructure became predictable again. And predictable systems are resilient systems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final Thoughts<\/strong><\/h2>\n\n\n\n<p>Migrating away from Elastic Beanstalk is not a small change. It forces a team to rethink how deployments work, how instances are built, how failures are handled, and how much operational ownership they are willing to assume. But when infrastructure maturity demands predictability and control, that shift becomes necessary.<\/p>\n\n\n\n<p>Running both environments in parallel is one of the most important decisions in the entire process. Parallel testing removes pressure from the migration and allows real validation under production traffic before committing fully. Confidence doesn\u2019t come from optimism \u2014 it comes from evidence.<\/p>\n\n\n\n<p>Breaking infrastructure logic into modular, idempotent scripts fundamentally changes how the system behaves. Debugging becomes faster. Failures become easier to reason about. Bootstrap behavior becomes explicit instead of implicit. Combined with S3-based deployment artifacts, this removes an entire class of failures tied to artifact expiration and historical CI state.<\/p>\n\n\n\n<p>Blue\/green deployments using Auto Scaling Group instance refreshes deliver what managed abstractions often promise but don\u2019t fully guarantee: predictable, repeatable, zero-downtime releases. With infrastructure-level health checks enforcing minimum healthy capacity, deployments become boring again \u2014 exactly what production systems require.<\/p>\n\n\n\n<p>Observability completes the picture. Centralized logging and real-time visibility transform incident response from reactive to proactive. Instead of waiting for user reports, the system exposes its own signals.<\/p>\n\n\n\n<p>If we distill the migration into core lessons, they are these:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run environments in parallel before cutting over<\/li>\n\n\n\n<li>Make bootstrap scripts idempotent and explicit<\/li>\n\n\n\n<li>Decouple deployments from CI artifact expiration<\/li>\n\n\n\n<li>Enforce health checks at the infrastructure level<\/li>\n\n\n\n<li>Centralize logs before you need them<br><\/li>\n<\/ul>\n\n\n\n<p>From a measurable standpoint, the migration delivers ~15% lower costs, ~40% faster deployments, and zero downtime during releases. But the most meaningful outcome isn\u2019t financial or performance-based.<\/p>\n\n\n\n<p>It\u2019s ownership.<\/p>\n\n\n\n<p>The infrastructure behaves exactly as defined \u2014 during deployments, scaling events, and incidents. And in production engineering, that level of predictability is far more valuable than convenience.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Why migrate from Elastic Beanstalk to EC2?<\/strong><\/h3>\n\n\n\n<p>Elastic Beanstalk is a strong choice for early-stage products because it abstracts infrastructure complexity. However, as systems mature, teams often require greater control over runtime configuration, deployment strategies, observability, and dependency management. Migrating to EC2 with Auto Scaling Groups allows full ownership of the instance lifecycle, deterministic deployments, and deeper production visibility.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Can you migrate from Elastic Beanstalk to EC2 without downtime?<\/strong><\/h3>\n\n\n\n<p>Yes&nbsp; but it requires careful planning. In this case, zero downtime is achieved by:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Running both environments in parallel<\/li>\n\n\n\n<li>Using blue\/green deployments with Auto Scaling Group instance refresh<\/li>\n\n\n\n<li>Enforcing infrastructure-level health checks<\/li>\n\n\n\n<li>Gradually shifting traffic with weighted routing<br><\/li>\n<\/ul>\n\n\n\n<p>When done correctly, traffic only reaches new instances after they are fully healthy.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Is migrating to EC2 cheaper than Elastic Beanstalk?<\/strong><\/h3>\n\n\n\n<p>It can be, but cost savings are usually not the primary driver. In this migration, infrastructure costs decrease by approximately 15%. The bigger benefit is eliminating abstraction overhead and gaining predictable infrastructure behavior. Cost reduction becomes a side effect of increased control.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What is the biggest risk during an Elastic Beanstalk migration?<\/strong><\/h3>\n\n\n\n<p>The biggest risk is insufficient validation before cutover. Skipping parallel testing or relying solely on theoretical correctness increases the chance of production incidents. Running both environments simultaneously under real traffic conditions is one of the most effective ways to reduce migration risk.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Elastic Beanstalk served us well for years. It allowed us to move fast, abstract infrastructure complexity, and focus on shipping product. But as the system matured, the very abstractions that once accelerated us started introducing operational friction. In this article, we\u2019ll walk through how we migrated a production Ruby on Rails application from AWS Elastic<a class=\"read_more_linkk\" href=\"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/\">&#8230;<\/a><\/p>\n","protected":false},"author":47,"featured_media":4219,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_sitemap_exclude":false,"_sitemap_priority":"","_sitemap_frequency":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[168],"tags":[329,428,429],"class_list":["post-4214","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tech-expertise-innovation","tag-aws","tag-ci-cd","tag-devops"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>From Elastic Beanstalk to EC2: Full Migration Journey | Blog<\/title>\n<meta name=\"description\" content=\"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"From Elastic Beanstalk to EC2: Full Migration Journey | Blog\" \/>\n<meta property=\"og:description\" content=\"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/\" \/>\n<meta property=\"og:site_name\" content=\"Software &amp; Tech Hiring Insights | BEON.tech Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-18T13:36:23+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-06T12:07:24+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2050\" \/>\n\t<meta property=\"og:image:height\" content=\"1367\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Julio Lugo\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@beontechok\" \/>\n<meta name=\"twitter:site\" content=\"@beontechok\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Julio Lugo\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/\"},\"author\":{\"name\":\"Julio Lugo\",\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/#\\\/schema\\\/person\\\/0e4d4e9639237b3b68f319e92475a6fb\"},\"headline\":\"From Elastic Beanstalk to EC2: Regaining Control in Production\",\"datePublished\":\"2026-02-18T13:36:23+00:00\",\"dateModified\":\"2026-04-06T12:07:24+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/\"},\"wordCount\":2448,\"image\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/BEON-FHD-DIA-1-177-scaled.jpg\",\"keywords\":[\"AWS\",\"CI\\\/CD\",\"DevOps\"],\"articleSection\":[\"Tech Expertise &amp; Innovation\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/\",\"url\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/\",\"name\":\"From Elastic Beanstalk to EC2: Full Migration Journey | Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/BEON-FHD-DIA-1-177-scaled.jpg\",\"datePublished\":\"2026-02-18T13:36:23+00:00\",\"dateModified\":\"2026-04-06T12:07:24+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/#\\\/schema\\\/person\\\/0e4d4e9639237b3b68f319e92475a6fb\"},\"description\":\"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#primaryimage\",\"url\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/BEON-FHD-DIA-1-177-scaled.jpg\",\"contentUrl\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/BEON-FHD-DIA-1-177-scaled.jpg\",\"width\":2050,\"height\":1367,\"caption\":\"Senior engineer working on infrastructure migration from Elastic Beanstalk to EC2, focused on performance and deployment reliability.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/beontech.wpengine.com\\\/from-elastic-beanstalk-to-ec2\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/beon.tech\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"From Elastic Beanstalk to EC2: Regaining Control in Production\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/beon.tech\\\/blog\\\/\",\"name\":\"Software &amp; Tech Hiring Insights | BEON.tech Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/beon.tech\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/#\\\/schema\\\/person\\\/0e4d4e9639237b3b68f319e92475a6fb\",\"name\":\"Julio Lugo\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/09\\\/1733507784249-500x500-2-96x96.jpeg\",\"url\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/09\\\/1733507784249-500x500-2-96x96.jpeg\",\"contentUrl\":\"https:\\\/\\\/beon.tech\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/09\\\/1733507784249-500x500-2-96x96.jpeg\",\"caption\":\"Julio Lugo\"},\"description\":\"Julio Lugo is a Software Engineer at BEON.tech, AWS Certified Solutions Architect, and a Georgia Tech OMSCS student. He specializes in frontend architecture and performance optimization, having led key initiatives to modernize build pipelines and improve application speed and reliability.\",\"url\":\"https:\\\/\\\/beon.tech\\\/blog\\\/author\\\/julio-lugo\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"From Elastic Beanstalk to EC2: Full Migration Journey | Blog","description":"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/","og_locale":"en_US","og_type":"article","og_title":"From Elastic Beanstalk to EC2: Full Migration Journey | Blog","og_description":"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.","og_url":"https:\/\/beon.tech\/blog\/from-elastic-beanstalk-to-ec2\/","og_site_name":"Software &amp; Tech Hiring Insights | BEON.tech Blog","article_published_time":"2026-02-18T13:36:23+00:00","article_modified_time":"2026-04-06T12:07:24+00:00","og_image":[{"width":2050,"height":1367,"url":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg","type":"image\/jpeg"}],"author":"Julio Lugo","twitter_card":"summary_large_image","twitter_creator":"@beontechok","twitter_site":"@beontechok","twitter_misc":{"Written by":"Julio Lugo","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#article","isPartOf":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/"},"author":{"name":"Julio Lugo","@id":"https:\/\/beon.tech\/blog\/#\/schema\/person\/0e4d4e9639237b3b68f319e92475a6fb"},"headline":"From Elastic Beanstalk to EC2: Regaining Control in Production","datePublished":"2026-02-18T13:36:23+00:00","dateModified":"2026-04-06T12:07:24+00:00","mainEntityOfPage":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/"},"wordCount":2448,"image":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#primaryimage"},"thumbnailUrl":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg","keywords":["AWS","CI\/CD","DevOps"],"articleSection":["Tech Expertise &amp; Innovation"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/","url":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/","name":"From Elastic Beanstalk to EC2: Full Migration Journey | Blog","isPartOf":{"@id":"https:\/\/beon.tech\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#primaryimage"},"image":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#primaryimage"},"thumbnailUrl":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg","datePublished":"2026-02-18T13:36:23+00:00","dateModified":"2026-04-06T12:07:24+00:00","author":{"@id":"https:\/\/beon.tech\/blog\/#\/schema\/person\/0e4d4e9639237b3b68f319e92475a6fb"},"description":"Read about migrating a production Rails app from Elastic Beanstalk to EC2 + Auto Scaling with zero downtime and full infrastructure control.","breadcrumb":{"@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#primaryimage","url":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg","contentUrl":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-scaled.jpg","width":2050,"height":1367,"caption":"Senior engineer working on infrastructure migration from Elastic Beanstalk to EC2, focused on performance and deployment reliability."},{"@type":"BreadcrumbList","@id":"https:\/\/beontech.wpengine.com\/from-elastic-beanstalk-to-ec2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/beon.tech\/blog\/"},{"@type":"ListItem","position":2,"name":"From Elastic Beanstalk to EC2: Regaining Control in Production"}]},{"@type":"WebSite","@id":"https:\/\/beon.tech\/blog\/#website","url":"https:\/\/beon.tech\/blog\/","name":"Software &amp; Tech Hiring Insights | BEON.tech Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/beon.tech\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/beon.tech\/blog\/#\/schema\/person\/0e4d4e9639237b3b68f319e92475a6fb","name":"Julio Lugo","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2025\/09\/1733507784249-500x500-2-96x96.jpeg","url":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2025\/09\/1733507784249-500x500-2-96x96.jpeg","contentUrl":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2025\/09\/1733507784249-500x500-2-96x96.jpeg","caption":"Julio Lugo"},"description":"Julio Lugo is a Software Engineer at BEON.tech, AWS Certified Solutions Architect, and a Georgia Tech OMSCS student. He specializes in frontend architecture and performance optimization, having led key initiatives to modernize build pipelines and improve application speed and reliability.","url":"https:\/\/beon.tech\/blog\/author\/julio-lugo\/"}]}},"featured_image_src":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-600x400.jpg","featured_image_src_square":"https:\/\/beon.tech\/blog\/wp-content\/uploads\/2026\/02\/BEON-FHD-DIA-1-177-600x600.jpg","author_info":{"display_name":"Julio Lugo","author_link":"https:\/\/beon.tech\/blog\/author\/julio-lugo\/"},"_links":{"self":[{"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/posts\/4214","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/users\/47"}],"replies":[{"embeddable":true,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/comments?post=4214"}],"version-history":[{"count":0,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/posts\/4214\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/media\/4219"}],"wp:attachment":[{"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/media?parent=4214"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/categories?post=4214"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/beon.tech\/blog\/wp-json\/wp\/v2\/tags?post=4214"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}