Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

ClickOps & Cloud Setup

Terraform for the Whole Landing Zone: Layout, State, Pipelines and Handover

We write the landing zone as Terraform: organisation, accounts, SSO, guardrails, networking, security services and budgets. State is held remotely with locking, changes go through pull request plans and a pipeline using short-lived credentials, and the code lives in your repository so your team owns it.

Key takeaways

  • The landing zone is infrastructure that every other system depends on, so it deserves the most careful change process you have.
  • Split code and state by layer, such as organisation, security, network and workload baselines, to limit blast radius.
  • CI applies changes using OIDC federation, so no long-lived keys are stored anywhere.
  • Everything you can click in the console is represented in code, with a short list of documented exceptions.
  • The code is yours: your repository, your cloud account, your state, no lock-in to us.

Why the landing zone must be code

A landing zone built by clicking is another piece of ClickOps debt, only with more important consequences. You cannot review it, reproduce it or reliably tell what changed. Putting it in Terraform gives you a history, a review process and the ability to build a second environment, or recover from a mistake, by reading a repository.

This page describes how we structure that code, as part of our ClickOps and cloud setup service. For wider Terraform engagements, see Terraform and infrastructure as code, and for how existing console resources are brought across, our migration plan and the blog guide from ClickOps to Terraform on AWS.

What good looks like

  • All landing zone resources are defined in a repository you own.
  • State is remote, encrypted, versioned and locked.
  • Code is split into layers with clear dependencies.
  • Changes need a reviewed plan, and apply from a pipeline.
  • Provider and module versions are pinned.
  • The list of things that are not in code is short, written down and justified.

Repository and layer design

One giant root module is the usual first mistake. Plans become slow, every change risks everything, and permissions cannot be scoped. We separate the code by how often it changes and who is allowed to change it:

LayerContentsChange frequency
bootstrapState bucket, pipeline rolesRare
organisationOrganizations, OUs, accounts, SCPsOccasional
identityIdentity Center, permission sets, assignmentsOccasional
securityDelegated admins, GuardDuty, Security Hub, Config, trailsOccasional
networkVPCs, Transit Gateway, DNS zonesOccasional
account baselinePer-account budgets, alarms, default settingsPer new account
landing-zone/
  bootstrap/
  organisation/
  identity/
  security/
  network/
  baselines/
    prod/
    nonprod/
  modules/
    account-baseline/
    vpc/
  .github/workflows/

Each directory has its own state key, so a mistake in network cannot touch Organizations. Layers read each other’s outputs through explicit data sources or parameters, not by reaching into shared state.

How it works in practice

Bootstrapping state

State needs a home before Terraform can manage anything, which is a chicken-and-egg problem. We create the bucket with a small bootstrap, move its own state into it, and then protect it with versioning, encryption and a policy that denies deletion. A backend block then looks like this:

terraform {
  required_version = "~> 1.9"

  backend "s3" {
    bucket       = "acme-tfstate-shared"
    key          = "landing-zone/organisation/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true
  }
}

Locking options depend on your Terraform version, so we check the one you run. More on state hygiene is in our Managed DevOps state management notes.

Credentials without secrets

The pipeline authenticates with OpenID Connect. GitHub Actions presents a short-lived token, AWS exchanges it for a role session, and the role’s trust policy limits which repository and branch may assume it. No access keys sit in secrets storage. A workflow fragment:

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::111111111111:role/landing-zone-plan
      aws-region: eu-west-1
  - run: terraform plan -input=false -lock-timeout=5m

Plan jobs use a read-oriented role, and apply uses a stronger one reachable only from the protected main branch after approval. The account ID shown is a placeholder. The same pattern is described in our CI/CD pipeline work.

Checks on every pull request

terraform fmt -check, validate, tflint, a policy scanner such as Checkov and the plan itself run on every pull request. terraform-docs keeps module documentation current. We add a rule that plans touching IAM, SCPs or networking require a second reviewer, because those are the changes that hurt.

Dealing with the bits Terraform cannot do

Some steps stay manual: the very first login and root MFA (see root hardening), enabling certain organisation-level service access, and registrar settings. We record each, with who does it and when, so none is forgotten at the next account.

Creating accounts

New accounts are created from a module that applies the baseline in one step: SSO assignments, budget, security enrolment and tags. That removes the usual drift between the first account and the twentieth.

Decisions and trade-offs

Terragrunt or plain Terraform? Terragrunt reduces repetition across many accounts and regions but adds another tool to learn. Plain Terraform with a module per concern is enough for most startups.

Community modules or our own? Well-maintained community modules save time for VPCs. For organisation-level resources, small purpose-built modules are clearer and avoid upgrading someone else’s opinions.

Monorepo or repo per layer? One repository with directories is easier to review and keep consistent. Separate repositories help when different teams own different layers.

When not to codify. Throwaway experiments in a sandbox account need not be in code. The landing zone itself always should.

Common failure modes

  • Giant state that makes every plan slow and frightening.
  • Hand edits in the console leading to constant drift, which we catch with scheduled plans.
  • Secrets in state or variables files. We keep secrets in a secrets manager.
  • Unpinned providers changing behaviour on the next init.
  • Only one person understands it. Documentation and a handover session address this.

Delivery and handover

You receive the repository with a README per layer, architecture decision records, the pipeline definition and a recorded walkthrough. We pair with your engineers on at least one real change, such as adding an account, so the process is tried before we leave. Ongoing maintenance, if you want it, is covered under Managed DevOps and discussed in our engagement models.

How to tell it is working

  • Plans on a clean main branch show no changes.
  • Scheduled drift detection stays quiet.
  • A new account is a pull request that merges in an afternoon.
  • No access keys exist in the pipeline settings.
  • A new engineer can read the repository and explain the estate.

To scope a landing zone codebase for your environment, contact us.

References

How we deliver it

  1. 01

    Bootstrap the state

    A dedicated state bucket and locking are created first, by a small, documented bootstrap that is then itself brought under management.

  2. 02

    Lay out the repository

    Directories separate organisation, identity, security, networking and per-account baselines, with shared modules versioned.

  3. 03

    Write the layers

    Each layer is implemented and applied in order, validating with a clean plan before the next begins.

  4. 04

    Build the pipeline

    Pull requests run formatting, validation, linting, security scanning and plan; merge applies with an approval gate.

  5. 05

    Test in a scratch organisation

    Where possible, the whole stack is exercised in a throwaway environment before touching production accounts.

  6. 06

    Document and hand over

    A README per layer, a change process and a walkthrough let your engineers extend it confidently.

Tools and technologies

TerraformOpenTofuS3 backendGitHub ActionsOIDC federationtflintCheckovterraform-docsAtlantispre-commit

Frequently asked questions

Can the whole landing zone really be Terraform?
Almost all of it. Organizations, accounts, SCPs, Identity Center, networking, security services and budgets are all supported. A few steps remain manual or semi-manual, such as the first root login, enabling some service features, or registrar changes. We list these exceptions explicitly in the documentation.
Terraform or OpenTofu?
Both use the same configuration language and work for this job. Terraform has the broadest vendor support, while OpenTofu is open source under the Linux Foundation. We follow your existing choice and avoid switching without a clear reason, since the code is largely portable.
Where does the Terraform state live?
In a versioned, encrypted S3 bucket in a dedicated account (or the equivalent in Azure Storage), with locking enabled and access limited to the pipeline role and a few administrators. State can contain sensitive values, so bucket access is treated like secrets access.
How are changes applied safely?
Through pull requests. The pipeline posts the plan, a second engineer approves, and a protected job applies the exact plan using short-lived credentials obtained by OIDC. Laptops do not apply to production, except through a documented, logged break-glass process.
Can we use Control Tower and Terraform together?
Yes, and it is a common pattern. Control Tower creates the base landing zone, and Terraform manages everything built on it, including account customisations, networking and security services. AWS also provides ways to drive Account Factory from Terraform, so new accounts stay in code.

Talk to an engineer about terraform landing zone

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.