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 running on EC2 moved to 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 is based on a real Enekui engagement completed in Q1 2024. Client details are anonymised; technical facts are not.
The starting point
The client is a €9M/year industrial distributor in northern Spain. Their core ERP extension—a Java 11 Spring Boot monolith—ran on a single m5.xlarge EC2 instance (4 vCPU, 16GB RAM) behind an Application Load Balancer. PostgreSQL 13 on RDS. No autoscaling. Manual deployments via SSH + systemctl restart. AWS bill: €1,200/month (compute + RDS + data transfer).
The pain: deployments required 3-5 minutes of downtime during business hours. The ops team (two people) spent ~4 hours/month on patching and manual restarts. No CI/CD. The instance had been running the same AMI for 18 months.
Why containers, why now
The client didn't need Kubernetes. They needed: - Zero-downtime deployments. - Automatic rollback on failure. - Less manual SSH work. - A path to horizontal scaling (future requirement, not immediate).
ECS Fargate fit: managed container orchestration without managing nodes. No EKS complexity. No rewrite required—just containerise the existing JAR.
The migration plan (6 weeks)
Week 1-2: Containerisation + local testing - Wrote a multi-stage Dockerfile (Maven build + slim JRE runtime). - Externalised config (12-factor: env vars for DB connection, S3 buckets). - Tested locally with Docker Compose (app + PostgreSQL container). - Built CI pipeline in GitHub Actions: build image → push to ECR → tag with git SHA.
Week 3: ECS cluster setup
- Created ECS cluster (Fargate launch type).
- Task definition: 2 vCPU, 4GB RAM (we right-sized from the EC2 baseline).
- Service with ALB target group. Health checks on /actuator/health.
- Secrets Manager for DB credentials (previously hardcoded in a properties file).
Week 4: Parallel run - Deployed to ECS in a separate VPC subnet. - Routed 10% of traffic via ALB weighted target groups (90% EC2, 10% Fargate). - Monitored for a week: no errors, latency identical.
Week 5: Full cutover - Shifted 100% traffic to Fargate. - Kept EC2 instance running (read-only) for 48 hours as fallback. - No downtime. Deployment time: 4 minutes (ECS draining + health checks).
Week 6: Cleanup + documentation - Terminated EC2 instance. - Documented rollback procedure (revert ALB target group in Terraform). - Trained ops team on ECS console + CloudWatch Logs Insights.
Cost impact
Before (EC2):
- m5.xlarge reserved instance (1 year): €85/month.
- EBS gp3 100GB: €8/month.
- ALB: €20/month.
- RDS db.t3.medium: €60/month.
- Data transfer + misc: €27/month.
- Total: €200/month compute + load balancing (excluding RDS).
After (Fargate): - Fargate task (2 vCPU, 4GB): ~€50/month (730 hours at $0.04856/hour per vCPU + $0.00532/GB per AWS pricing). - ALB: €20/month (unchanged). - ECR storage: €2/month (10 images, 5GB total). - Total: €72/month compute + load balancing.
Saving: €128/month (64%) on the compute layer. RDS cost unchanged. The saving comes from right-sizing (we were over-provisioned on EC2) and paying only for what we use (Fargate charges per-second).
One-time migration cost: 80 hours engineering (Enekui) + 20 hours client ops team. Paid back in ~8 months.
What broke (and how we fixed it)
-
File uploads to local disk: The app wrote PDFs to
/tmpand served them via a servlet. Fargate tasks are ephemeral—files vanished on redeploy. Fix: Switched to S3 with presigned URLs. Took 6 hours to refactor. -
Health check false positives: Spring Boot's
/actuator/healthreturned 503 during the first 15 seconds of startup (DB connection pool initialising). ECS killed the task before it was ready. Fix: AddedstartPeriod: 30to the ECS task definition health check. Documented here. -
CloudWatch Logs cost surprise: We enabled
awslogsdriver with no retention policy. First month: €18 in Logs storage (the app is chatty). Fix: Set 7-day retention for INFO logs, 30 days for ERROR. Cost dropped to €4/month.
What this means for your SMB
If you're running a monolith on EC2 and you're tired of SSH deployments, this is the lowest-risk path to modern ops. You don't need to rewrite to microservices. You don't need Kubernetes. Fargate gives you:
- Immutable deployments (no more "it works on my machine").
- Automatic rollback (ECS circuit breaker stops bad deploys).
- Logs in CloudWatch (no more grepping /var/log over SSH).
The gotchas: you must externalise state (no local disk writes), and you need to right-size your task (don't just copy your EC2 specs). Budget 4-8 weeks for a monolith of this size.
How we'd apply it at Enekui
We template this with Terraform: ECS cluster, task definition, service, ALB target group, ECR repo, IAM roles. The Dockerfile is standardised (multi-stage Maven/Gradle build). CI/CD is GitHub Actions (build → ECR → ECS deploy via aws ecs update-service). We use AWS Secrets Manager for all credentials and Parameter Store for non-sensitive config.
For a client with multiple environments (dev/staging/prod), we use Terraform workspaces and separate ECS clusters. The task definition is the same; only env vars change.
If you're on a single EC2 instance today and you want this setup, we can deliver it in 4-6 weeks depending on app complexity. The migration itself (cutover) is typically a weekend.
Want help moving your monolith to containers? We offer a 2-week cloud diagnosis (€2,900) that includes a containerisation feasibility report and cost projection. Get in touch.