From ClickOps to Code: Our Journey Migrating AWS to Terraform and Terragrunt

13 oktober 2025 - Brian Marting

For a long time, our AWS infrastructure lived entirely inside the console. Every change — from spinning up EC2 instances to tweaking security groups — was done manually. It worked fine in the early days, but as things grew, so did the risks. We had no version control, no clear history of changes, no rollback mechanism, and each environment began to drift slightly from the others. It was the classic “ClickOps” setup, and we knew it wouldn’t scale.

That’s when we decided to move everything to Infrastructure as Code (IaC) with Terraform and Terragrunt.

Terragrunt migration

Why Terraform?

Once we began mapping out our pain points, the benefits of IaC were obvious. Using Terraform meant that every infrastructure change could live in Git, giving us a full change history and the ability to review and revert updates safely. We could finally track who changed what and when, spin up identical environments with confidence, and apply a proper pull request workflow to infrastructure the same way we do with application code.

We chose Terraform as the foundation for defining our infrastructure and added Terragrunt on top to handle the complexity of managing multiple environments and shared modules. The combination gave us the balance between flexibility and structure that we needed.

Taking Inventory: Understanding Our Starting Point

Before writing a single Terraform file, we needed to know exactly what we already had. That meant diving deep into our existing AWS environment which was a mix of manual configurations and forgotten resources. We spent time scanning everything using the AWS CLI and double-checking against old Confluence documentation.

Each diagram was categorized and grouped by its purpose and reusability. The process wasn’t glamorous, but it gave us the clarity to start modeling our infrastructure in code with confidence and to validate the migration once it was complete.

Stop manual work, start smart automation - We make your cloud predicatable, scalable and easy to maintain Contact us

Structuring the Codebase

We decided on a hybrid repository structure that kept things modular while still integrated with our CI/CD pipelines. A global Terraform repository houses all shared infrastructure which are the VPCs, subnets, IAM roles, NAT gateways, and so on. While each application (frontend, backend, and lambdas) contains its own Terraform configuration within its existing code repository.

This structure made it easy for our pipelines to access the right dependencies: frontend assets, backend JARs, or Lambda function code during deployments without losing separation of concerns.

Managing Multiple Environments

One of our main goals was to cleanly separate environments like dev, test, and prod. Using Terragrunt this became straightforward. Each environment now has its own isolated remote state and custom configuration, with dependencies properly managed between layers.

This ensures that changes in one environment don’t leak into another, and that deployments follow the same predictable rules everywhere. It also allows teams to experiment safely in development without worrying about impacting production systems.

The Migration Itself

When it became time to migrate, preparation was everything. We created a detailed checklist that covered syncing S3 buckets, ensuring all users and roles existed in the new setup, and verifying that every dependency was in place.

Once we were confident, we began the switch by updating our Route53 records to point clients toward the new stack. It was a tense but exciting moment and after verifying with our customer that everything worked as expected, we were officially running on Terraform-managed infrastructure. There were a few minor hiccups, but nothing we couldn’t fix quickly.

Lessons and Challenges

No migration is perfect, and ours had its fair share of learning moments:

  • Terraform resource limitations: Some AWS resources, like S3 bucket policies, can only be defined once, making dependency management tricky.
  • Hidden configurations: We stumbled upon legacy settings that only surfaced via the AWS CLI.
  • Instance type inconsistencies: Some older EC2 types weren’t available in Terraform.
  • Outdated reserved instances: These didn’t transfer neatly to the new setup.

Each issue pushed us to learn more about AWS’s quirks and Terraform’s constraints and helped us refine our IaC patterns.

Building Good Habits

Over time, we refined our process into something reliable and repeatable. Terraform now runs automatically through Bitbucket Pipelines, with each repository using its own least-privileged IAM role. We store remote state in a dedicated S3 bucket with DynamoDB locking, scoped per project and environment.

All infrastructure changes go through pull requests and code review. We modularized (= abstracted) the code where it was duplicated, using both our own modules and those from the Terraform Registry. And to make onboarding smoother, we documented the entire process and held a few internal sessions to help the team get comfortable with the new workflow.

Looking Back

The migration turned out to be more than just a technical change. It reshaped how we manage and think about infrastructure. Along the way, we discovered unused and orphaned resources that had quietly been running in the background. Now, with Terraform in place, we can forecast costs more accurately, enforce tagging standards, and even spin up short-lived test environments with ease.

Most importantly, we gained confidence. Our infrastructure is now versioned, auditable, and reproducible. What once felt fragile and dependent on memory or documentation is now structured, reviewable, and safe to evolve.

Final Thoughts

Moving from ClickOps to Terraform and Terragrunt was one of those rare projects that immediately paid off. The migration forced us to document, automate, and rethink how we work with cloud infrastructure.

If you’re considering making the same move, start by mapping your current setup and planning carefully. It takes time, but the outcome — a stable, traceable, and scalable infrastructure — is absolutely worth it.

Need help migrating your cloud infrastructure to IaC with Terragrunt? Feel free to reach out!