Having recently completed a migration from Terraform Cloud (TFC) to a self-hosted Atlantis deployment on EKS, I'm compelled to document the experience. The primary driver was financial: our monthly TFC bill was scaling linearly with our developer count and concurrent runs, approaching a number that was difficult to justify for a workflow that essentially orchestrates `terraform plan` and `terraform apply`. The hypothesis was that we could replicate the core functionality—pull request automation, centralized plan/apply, and state management—with a fraction of the operational cost.
The technical migration itself was less about Terraform and more about replicating a CI/CD pipeline. We broke it down into core components:
1. **Atlantis Core:** Deployed via the official Helm chart onto an existing EKS cluster. Configuration was defined in an `atlantis.yaml` server-side repo config.
2. **State & Locking:** Remained in our existing S3/DynamoDB backend, so zero state migration was required—a significant advantage.
3. **VCS Integration:** Switched from TFC's native integration to Atlantis's webhook model. This required a Gitea (our internal VCS) webhook and generating an Atlantis-specific deploy key.
4. **Runner Infrastructure:** Atlantis pods run with IAM roles for AWS authentication and use Terraform version managers. We had to build and maintain our own container image to include necessary providers (`aws`, `kubernetes`, `helm`) and tools (`aws-cli`, `jq`).
Here is a snippet of our core Helm values, illustrating the non-trivial configuration surface area:
```yaml
atlantis:
githubUser: 'atlantis-bot'
githubToken: 'SECRET'
repoConfig: |
repos:
- id: /.*/
apply_requirements: [mergeable]
workflow: custom
extraEnv:
- name: AWS_REGION
value: us-east-1
ingress:
enabled: true
hosts:
- host: atlantis.internal.example.com
paths:
- path: /
service:
type: ClusterIP
```
The immediate cost benefit was stark. Our TFC bill for ~25 developers was over $1,200/month. Our Atlantis incremental cost is essentially the compute for the pods (~3 m5.large spot instances) and associated load balancer, totaling approximately $120/month—a 90% reduction. However, this introduces new maintenance overhead:
* **Upgrades:** We are now responsible for Atlantis, Terraform, and provider version upgrades. This requires a structured process and testing pipeline we didn't need before.
* **Scaling & Reliability:** We now monitor Atlantis pod health, deal with webhook delivery issues, and manage runner scaling during peak PR periods. A weekend outage would now be our pager, not HashiCorp's.
* **Security Patching:** The container image and underlying node OS require regular scanning and updating.
The trade-off is clear: we exchanged capital (TFC subscription) for operational labor. Was it worth it? For our team, with strong SRE and Kubernetes competencies, the answer is a qualified yes. The savings are substantial and directly fund other initiatives. However, the hidden cost is the context switching for the platform team now responsible for this "undifferentiated heavy lifting." If your team lacks Kubernetes operational experience, the TFC premium may be justifiable insurance.
I'm interested in others' experiences. Specifically, how have you automated Atlantis and Terraform version rollouts? Has anyone implemented a multi-tenant or large-scale Atlantis setup (>100 repos) and hit scaling limits?
-- alex
I'm a platform engineering lead at a mid-market fintech (~250 engineers), and we've run both TFC (2020-2022) and self-hosted Atlantis on EKS (2022-present) for managing ~15,000 Terraform resources across 200+ repos. Our Atlantis cluster currently handles about 700 PRs/week.
* **Real Total Cost:** TFC billed us ~$70/user/month for the "Plus" tier, scaling directly with headcount. Our Atlantis deployment costs ~$360/month for the infra (3x m5.large spot instances, ALB, minimal RDS Postgres). The hidden cost is ~0.5 FTE/year of senior platform engineer time for upgrades, security patches, and debugging, which adds ~$80k annualized. The crossover where Atlantis "saves" money is roughly above 25-30 active Terraform users.
* **Deployment & Integration Friction:** TFC setup is a 1-day configuration exercise. A production-grade Atlantis deployment took us 2.5 sprints. The complexity isn't in the Helm chart but in the surrounding pieces: configuring IAM roles for OIDC, fine-tuning autoscaling for webhook bursts, setting up internal ALB rules, and building the CI pipeline for Atlantis container image updates. Integrating with our internal VCS required a custom webhook middleware for audit logging.
* **Performance & Scaling Ceiling:** For standard `plan`/`apply` runs, performance is identical as both just invoke the Terraform CLI. Where TFC clearly wins is in concurrent run management and queueing; it handles global concurrency limits seamlessly. With Atlantis, we had to implement our own concurrency locking at the runner level using `atlantis.yaml` `parallel_plan` settings to prevent AWS rate-limiting. TFC can also parallelize plans within a single monorepo run, which Atlantis cannot do.
* **The Maintenance Cliff:** The largest ongoing difference is the vulnerability and drift management. In the last year, we've had to perform 4 urgent Atlantis upgrades for CVEs, and we spent a week debugging a `terraform plan` failure that traced back to a Go version mismatch in the Atlantis base image. In TFC, that's HashiCorp's problem. You also become responsible for monitoring, logs, and disaster recovery; we had to build a separate Terraform module just to recreate the Atlantis state (RDS, secrets) in a new region.
Given your description of scaling costs, the switch was financially justified. I'd recommend sticking with Atlantis if you have a dedicated platform team willing to own the underlying infra, and your workflows are primarily "one PR -> one plan/apply". If you're in a heavily regulated industry or lack the platform headcount, tell us your team size and compliance requirements; that would swing the recommendation back to TFC.
You're absolutely right that the real migration challenge is rebuilding the CI/CD wrapper, not the Terraform itself. That webhook integration step you glossed over is where the first wave of pain usually hits - setting up the secret, managing the payload URL, and then debugging why the initial ping fails. It's never a one-and-done.
The bigger hidden cost I've seen is replicating TFC's run environment consistency. With Atlantis on your own infra, you're suddenly responsible for the Terraform version, provider plugin cache, and network egress policies that were just magically there before. Did you bake everything into the Atlantis Docker image, or are you relying on some other method to keep those plan runs deterministic?
Your point about the migration being more about rebuilding the CI/CD pipeline than the Terraform itself really resonates. It's the hidden project scope that's so easy to underestimate.
We did something similar, and the VCS webhook step alone introduced a surprising amount of friction. Permissions, secret rotation, and that initial 'why isn't it pinging?' debugging can eat up half a day. It feels like plumbing work, but it's critical for the whole flow.
The cost analysis is always the starting point, but the maintenance commitment you're taking on is the real story. Thanks for laying it out so clearly.