Lift-and-shift to ECS Fargate: 6-week migration of a Java monolith for an industrial SMB
An industrial SMB with a 12-year-old Java monolith moved to AWS ECS Fargate in six weeks without rewriting code. We document the cost impact, downtime, and what we'd do differently next time.
This case study comes from a real Enekui engagement in Q1 2024. The client: a €9M revenue industrial distributor in northern Spain, running a custom Java 11 monolith on two bare-metal servers (one production, one staging). The ask: move to AWS with minimal disruption, no budget for a rewrite, and a hard deadline of six weeks to free up the on-premise rack space.
We chose ECS Fargate over EC2 or Lambda. This post explains why, what the migration looked like in practice, and the cost/performance outcome.
The starting point
Stack before migration: - Java 11 Spring Boot monolith (~80k LoC). - PostgreSQL 12 on the same bare-metal server. - Nginx reverse proxy. - Manual deployments via SSH + shell scripts. - No CI/CD. No infrastructure as code. - ~200 concurrent users during business hours (8am-6pm CET). - Average response time: 180ms (p95: 450ms).
Constraints: - Budget: €12k for migration + first 3 months of AWS spend. - Timeline: 6 weeks from kickoff to production cutover. - Downtime tolerance: 2 hours maximum for final cutover. - No code changes allowed (business logic frozen for regulatory audit).
Why ECS Fargate over alternatives
We evaluated three paths:
-
EC2 instances (t3.medium): Closest to the existing setup. Rejected because it still requires OS patching, SSH access management, and doesn't solve the "infrastructure as cattle" problem. The client wanted to stop thinking about servers.
-
Lambda + API Gateway: Attractive for cost (pay-per-request), but the monolith has 4-second cold starts and some endpoints run 15+ seconds. Lambda's 15-minute timeout would work, but cold starts would kill user experience. Refactoring to split the monolith wasn't in scope.
-
ECS Fargate: Container orchestration without managing EC2. We can lift the existing JAR into a Docker image, point it at RDS PostgreSQL, and scale horizontally if needed. Winner.
Decision: ECS Fargate with Application Load Balancer, RDS PostgreSQL (db.t3.medium), and Terraform for all infrastructure.
Migration steps (week by week)
Week 1: Containerisation + local testing
- Wrote a
Dockerfilefor the Java app (OpenJDK 11 base image, ~180MB final size). - Externalised all config to environment variables (database URL, SMTP credentials, etc.).
- Tested locally with Docker Compose (app + PostgreSQL container).
- Set up AWS account structure: separate VPCs for staging and production.
Week 2: Terraform infrastructure
- Defined ECS cluster, task definition (1 vCPU, 2GB RAM), ALB, target group.
- Created RDS instance (db.t3.medium, Multi-AZ disabled for staging, enabled for production).
- Set up CloudWatch log groups for container stdout/stderr.
- Deployed to staging environment, pointed test domain.
Week 3-4: Data migration + parallel run
- Dumped production PostgreSQL with
pg_dump(18GB uncompressed). - Restored to RDS staging instance (took 4 hours).
- Ran staging environment in parallel with production for two weeks.
- Identified issues: connection pool exhaustion (fixed by tuning HikariCP settings), missing indexes on two tables (added).
Week 5: Load testing + cutover prep
- Load tested with Gatling: 500 concurrent users, sustained for 30 minutes. ECS auto-scaled from 2 to 4 tasks. Average response time: 165ms (p95: 380ms). Improvement over bare-metal.
- Prepared cutover runbook: DNS change, database final sync, smoke tests.
- Scheduled cutover for Saturday 6am (lowest traffic window).
Week 6: Production cutover
- 6:00am: Enabled read-only mode on old production database.
- 6:15am: Final
pg_dump+ restore to RDS production (incremental, 2.3GB delta, took 35 minutes). - 6:50am: Updated DNS A record to ALB.
- 7:10am: Smoke tests passed. Opened to users.
- Total downtime: 1 hour 10 minutes.
No rollback needed. First business day (Monday) ran without incidents.
Cost comparison: before vs after
Before (on-premise, monthly): - Server lease: €320/month (2 servers). - Electricity + cooling: ~€80/month. - Sysadmin time (10% FTE): ~€600/month. - Total: ~€1,000/month.
After (AWS, monthly average over 3 months): - ECS Fargate (2 tasks 24/7, 4 tasks during business hours): €145/month. - RDS db.t3.medium Multi-AZ: €180/month. - ALB: €25/month. - Data transfer + CloudWatch logs: €18/month. - Total: €368/month.
Saving: €632/month (63% reduction). The client also freed up rack space worth €200/month in colocation fees, bringing the real saving to €832/month.
Break-even on the €12k migration cost: 14 months.
What this means for your SMB
If you're running a monolith on bare-metal or a single VM, this migration pattern works when: - Your app is already stateless (or can be made stateless by externalising sessions to Redis/DynamoDB). - You don't need sub-50ms response times (Fargate cold starts are ~2 seconds, but with 2+ tasks always warm, users don't hit cold starts). - Your database fits in RDS (up to a few hundred GB; beyond that, you'd look at Aurora or self-managed on EC2).
The key win isn't just cost. It's operational simplicity: no SSH access, no OS patching, no "the server is down" calls at 3am. ECS handles task health checks and restarts. RDS handles backups and minor version upgrades.
The gotcha: you need to externalise everything. If your app writes logs to /var/log or stores uploads in /tmp, you'll need to refactor to CloudWatch Logs and S3. This client's app was already stateless (uploads went to an external FTP, logs to syslog), so we had no blockers.
How we'd apply it at Enekui
For a similar engagement today, we'd make two changes:
-
Use Terraform modules from day one. We wrote raw Terraform for this project; now we'd use our internal ECS module (standardises task definitions, ALB rules, auto-scaling policies). Saves ~30% of infrastructure code.
-
Add a staging slot in production. ECS supports blue/green deployments via CodeDeploy. We'd set this up so the client can deploy to a "staging" task set in production, run smoke tests, then shift traffic. Zero-downtime deploys.
We'd keep the same stack: ECS Fargate, RDS, ALB, Terraform. It's the right tool for monoliths that aren't ready (or don't need) to become microservices.
Want help with a similar migration? We offer a 2-week cloud readiness diagnosis (€2,900) that includes containerisation feasibility, cost projection, and a migration runbook. Get in touch.