β¨ https://terramate.io β¨
π Terramate Docs | π Getting Started | π» Playground | π Join Us
This template provides a pre-configured Terramate project for Terraform (or OpenTofu) on AWS using best practices. It includes GitOps workflows in GitHub Actions, DRY code generation for stacks, and support for multiple environments. Use it to orchestrate Terraform, clone or create stacks, and automate plans and applies in Pull Requests.
- GitOps for Terraform with GitHub Actions: Pre-configured GitHub Action GitOps workflows using merge-and-apply strategy with drift detection and reconciliation.
- Recommended Project Structure: Best practice project structure with environment-based organization (stg, prd).
- Change Preview in Pull Requests: Preview and approval of plans in Pull Requests to review and approve changes before deploying.
- DRY Terraform Stacks: Generate Terraform provider and backend configuration in stacks via mixins and generators.
- OpenID Connect (OIDC): Allows GitHub Actions workflows to access AWS resources without storing long-lived GitHub secrets.
- Terraform S3 Remote State Backend: Terraform Remote State Storage and State Locking with AWS S3 and DynamoDB.
- Terramate Cloud Integration: Pushes data to Terramate Cloud for observability, asset management, drift management, and Slack notifications.
- Multi-Environment Support: Built-in support for staging and production environments with separate AWS accounts.
- Drift Detection and Reconciliation: Automated drift detection and reconciliation workflows to maintain infrastructure consistency.
This template creates a complete AWS infrastructure with the following components:
-
VPC (Virtual Private Cloud)
- A dedicated network space for your infrastructure
- Public and private subnets across multiple availability zones
- NAT Gateway for outbound internet access from private subnets
- Internet Gateway for inbound internet access to public subnets
-
EKS (Elastic Kubernetes Service)
- A managed Kubernetes cluster in auto-mode
- Node groups for running your containerized applications
- Auto-scaling capabilities based on workload demand
- Integration with AWS IAM for authentication and authorization
-
Sample Applications
- Two sample web applications deployed in the EKS cluster
- Applications are accessible via LoadBalancer services
- Demonstrates best practices for Kubernetes deployments
The infrastructure is designed to be highly available across multiple availability zones, secure with proper network isolation, and scalable with clear separation of concerns.
The project is organized as follows:
terramate-quickstart-aws/
βββ config.tm.hcl # Root: Terraform version, backend, providers, OIDC
βββ imports.tm.hcl # Imports mixins and generators
βββ terramate.tm.hcl # Project config: cloud, git, experiments
βββ workflows.tm.hcl # Root-level Terramate scripts
βββ imports/
β βββ mixins/ # Shared code-gen: backend, providers, Kubernetes auth
β β βββ backend.tm.hcl
β β βββ terraform.tm.hcl
β β βββ kubernetes.tm.hcl
β βββ generators/ # Versioned code-gen templates
β βββ v1/ # Current generator version
β βββ generate_vpc.tm.hcl
β βββ generate_eks.tm.hcl
β βββ generate_apps_namespace.tm.hcl
β βββ generate_app.tm.hcl
βββ stacks/
β βββ terraform/
β β βββ workflows.tm.hcl # init, preview, deploy, drift scripts
β β βββ envs/
β β βββ stg/ # Staging: config.tm.hcl, vpc/, eks/, eks/apps/
β β βββ prd/ # Production (same structure)
β βββ opentofu/ # OpenTofu example stacks
β βββ ...
βββ _bootstrap/ # One-time: state bucket, OIDC provider
βββ terraform-state-bucket/
βββ oidc-aws-github/
Key directories:
config.tm.hcl(root) β Global defaults: generator version, Terraform version, S3 backend, AWS/Kubernetes providers, OIDC GitHub repos.imports.tm.hclβ Declares imports forimports/mixins/*.tm.hcland the active generator version (e.g.imports/generators/v1/*.tm.hcl).imports/mixins/β Generatebackend.tf,terraform.tf, and (for stacks withkubernetestag)kubernetes.tfin every stack.imports/generators/v1/β Versioned code-gen templates for VPC, EKS, namespace, and apps. Each generator uses aconditiononglobal.generators.versionso multiple versions can coexist (see How Code Generation Works and Upgrading Generator Templates).stacks/terraform/envs/{stg,prd}/β Environment-specific stacks. Each env has aconfig.tm.hcl(env name, VPC CIDR, EKS cluster name, etc.) and sub-stacks:vpc/,eks/,eks/apps/,eks/apps/app1/,eks/apps/app2/.stacks/opentofu/β Example OpenTofu stacks with their own version and backend key instacks/opentofu/config.tm.hcl._bootstrap/β Bootstrap stacks for the S3 state bucket and GitHub OIDC; use local state initially, then migrate to S3.workflows.tm.hcl(root) andstacks/terraform/workflows.tm.hclβ Terramate script definitions.
Stacks are deployed in a fixed order: VPC β EKS cluster β apps namespace β application stacks. EKS stacks use after = ["tag:vpc"] so the VPC is applied first.
flowchart LR
VPC --> EKS --> Namespace --> App1
Namespace --> App2
This repo keeps stacks DRY by generating Terraform files from Terramate config and globals. You define stack metadata and globals; Terramate generates the actual .tf (and some _*.tf) files.
Two mechanisms:
-
Generators (
imports/generators/v1/*.tm.hcl) β Usegenerate_hclwithstack_filter.project_pathsto emit specific files only into stacks whose path matches. Each generator also has acondition = global.generators.version == "v1"so that multiple generator versions can coexist (see Upgrading Generator Templates). The current generators are:generate_vpc.tm.hclβ Targets**/envs/*/vpc, generatesmain.tf(VPC module) fromglobal.vpc.*.generate_eks.tm.hclβ Targets**/envs/*/eks, generatesmain.tfanddata.tffromglobal.eks.*.generate_apps_namespace.tm.hclβ Targets**/envs/*/eks/apps, generatesnamespace.tf.generate_app.tm.hclβ Targets**/envs/*/eks/apps/*, generatesmain.tf(Deployment + LoadBalancer Service) fromglobal.app.*(e.g.image,container_name,container_port).
-
Mixins (
imports/mixins/*.tm.hcl) β Generate shared plumbing in (almost) all stacks:backend.tm.hclβbackend.tf,terraform.tm.hclβterraform.tf, andkubernetes.tm.hclβkubernetes.tfonly for stacks with thekubernetestag.
Workflow:
- Run
terramate generateto (re)generate all such files. After changingconfig.tm.hcl, stack paths, or generator/mixin logic, run it again. - Do not edit generated files by hand β they are overwritten by
terramate generate. Edit onlystack.tm.hcl,config.tm.hcl, and the generator/mixin sources.
Example: adding a new app stack under stacks/terraform/envs/stg/eks/apps/app3/ with a config.tm.hcl that sets globals "app" { image = "..."; container_name = "..."; container_port = 8080; } will, after terramate generate, get main.tf from generate_app.tm.hcl and backend.tf / terraform.tf / kubernetes.tf from the mixins (because of the kubernetes tag).
Click Use this template on GitHub to create your own repository, then clone it.
-
Install asdf.
-
Install plugins and tools (versions are in
.tool-versions):asdf plugin add terramate && \ asdf plugin add terraform && \ asdf plugin add opentofu && \ asdf plugin add pre-commit && \ asdf install
-
AWS credentials (for bootstrapping and local runs): Use one of the AWS provider authentication methods. We recommend aws-vault.
-
Pre-commit (optional but recommended):
pre-commit install
Hooks keep Terramate and Terraform formatting and generation in sync when you commit.
-
Root config β In the repo root, edit
config.tm.hcl:- Set your S3 state bucket name and region under
globals "terraform" "backend". - Set your GitHub repo(s) under
globals "aws" "oidc" "github_repositories".
- Set your S3 state bucket name and region under
-
Generate and deploy bootstrap stacks:
terramate generate terramate run -C _bootstrap terraform init terramate run -C _bootstrap terraform apply
-
Migrate bootstrap state to S3: Remove
tags = ["no-backend"]from thestack.tm.hclin_bootstrap/oidc-aws-githuband_bootstrap/terraform-state-bucket, then:terramate generate terramate run -C _bootstrap terraform init
Terraform will migrate existing state into the new backend.
- Go to Terramate Cloud, sign up or log in, and create an organization.
- Set
terramate.config.cloudinterramate.tm.hclto your organization (and region if needed). - Optionally configure Slack under Integrations for notifications.
To create a new environment (e.g. dev) by copying an existing one (e.g. stg):
terramate clone stacks/terraform/envs/stg stacks/terraform/envs/devWhat this does: Duplicates the stack tree under stg into dev and assigns new stack IDs to the cloned stacks (so they are independent stacks with their own state).
Next steps:
-
Adjust environment config β Edit
stacks/terraform/envs/dev/config.tm.hcl:- Set
globals "terraform" { env = "dev" }. - Change VPC name, CIDR, subnets, EKS cluster name, namespace, etc. so they donβt conflict with stg/prd.
- Set
-
Regenerate code:
terramate generate
-
Commit and push (and add the new env to CI if you use a matrix over environments).
You can then run init/preview/deploy against stacks/terraform/envs/dev the same way as for stg or prd.
To add a new app (e.g. app3) in staging:
-
Create the stack (and give it a name, description, and the
kubernetestag so it gets the Kubernetes provider and app generator):terramate create stacks/terraform/envs/stg/eks/apps/app3 \ --name "my-new-app-stg" \ --description "My new application" \ --tags kubernetes
-
Add app configuration β Create
stacks/terraform/envs/stg/eks/apps/app3/config.tm.hcl:globals "app" { image = "your-registry/your-image:tag" container_name = "my-app" container_port = 8080 }
You can also set
replicas,resources.requests, etc. if your generator supports them. -
Generate Terraform files:
terramate generate
The generators and mixins will produce
main.tf(fromgenerate_app.tm.hcl),backend.tf,terraform.tf, andkubernetes.tfin the new stack. No need to copy these by hand. -
Plan and apply (locally or via CI) for the changed stacks, e.g.:
terramate script run -C stacks/terraform/envs/stg init terramate script run -C stacks/terraform/envs/stg preview terramate script run -C stacks/terraform/envs/stg deploy
When an underlying Terraform module changes (new version, new required attributes, restructured resources), you may need to update the code generation templates. Generators are versioned so you can roll out template changes to one environment at a time.
How it works: Each generator block has a condition = global.generators.version == "v1" guard. The active version is set via globals "generators" { version = "v1" } in the root config.tm.hcl and can be overridden per environment.
Step-by-step: rolling out a new generator version to staging first
-
Create the new version -- copy the current version directory:
cp -r imports/generators/v1 imports/generators/v2
-
Update the templates in
imports/generators/v2/-- change module versions, add/remove attributes, restructure resources as needed. Update theconditionin each block:generate_hcl "main.tf" { condition = global.generators.version == "v2" # ... updated template }
-
Import the new version -- add it to
imports.tm.hcl:import { source = "./imports/mixins/*.tm.hcl" } import { source = "./imports/generators/v1/*.tm.hcl" } import { source = "./imports/generators/v2/*.tm.hcl" }
Both versions are imported, but only the one matching each environment's
global.generators.versiongenerates output. -
Switch staging to v2 -- override the global in
stacks/terraform/envs/stg/config.tm.hcl:globals "generators" { version = "v2" }
-
Regenerate:
terramate generate
Only staging stacks get the new templates. Production stays on v1. Commit, open a PR, and CI plans only the changed (staging) stacks.
-
Roll out to production -- once staging is validated, either update the root default in
config.tm.hclto"v2"or override it instacks/terraform/envs/prd/config.tm.hcl. Runterramate generateagain. -
Clean up -- after all environments are on v2, remove the v1 directory, remove its import from
imports.tm.hcl, and remove the per-env overrides.
From the repo root:
# Initialize all stacks under an environment
terramate script run -C stacks/terraform/envs/stg init
# Preview (plan) changed stacks
terramate script run -C stacks/terraform/envs/stg preview
# Deploy (plan + apply) changed stacks
terramate script run -C stacks/terraform/envs/stg deployUse -C stacks/terraform/envs/prd (or dev) for another environment. Scripts are defined in stacks/terraform/workflows.tm.hcl.
Defined in stacks/terraform/workflows.tm.hcl. See Terramate Scripts.
| Script | Description |
|---|---|
init |
Terraform init (backend + providers) |
preview |
Terraform validate + plan; syncs preview to Terramate Cloud |
deploy |
Terraform validate + plan + apply; syncs deployment to Terramate Cloud |
drift detect |
Plan for drift and sync status to Terramate Cloud |
drift reconcile |
Apply drift.tfplan to reconcile drift |
terraform render |
Show Terraform plan output (e.g. for PR comments) |
Run with:
terramate script run -C <path> <script_name>
# e.g. terramate script run -C stacks/terraform/envs/stg deployWorkflows live in .github/workflows/. They use a matrix over environments (e.g. stg, prd) and AWS OIDC for authentication.
| Workflow | Trigger | Purpose |
|---|---|---|
Preview (preview.yml) |
Pull requests to main | Format checks, terramate generate, init + plan on changed stacks; syncs preview to Terramate Cloud |
Deploy (deploy.yml) |
Push to main | Init + deploy (plan + apply) for changed stacks; runs drift detect after apply |
Drift Detection (drift-detection.yml) |
Manual / schedule | Init all stacks, drift detect, optionally reconcile stacks with reconcile tag |
- Preview and Deploy scripts sync plan/deployment data to Terramate Cloud when
terramate.config.cloudis set interramate.tm.hcl. - Use Terramate Cloud for stack overview, drift status, and (optional) Slack notifications. Configure the organization and Slack under your Cloud account settings.
| File | Purpose |
|---|---|
config.tm.hcl (root) |
Generator version, Terraform version, backend, providers, OIDC repos |
imports.tm.hcl |
Import declarations for mixins and active generator version(s) |
terramate.tm.hcl |
Project config: required_version, cloud, git, run env, experiments |
workflows.tm.hcl (root) |
Root-level Terramate scripts |
stacks/terraform/workflows.tm.hcl |
init, preview, deploy, drift, terraform render scripts |
stacks/terraform/envs/stg/config.tm.hcl |
Staging env: terraform.env, vpc, eks, namespace globals |
stacks/terraform/envs/prd/config.tm.hcl |
Production env config |
stacks/opentofu/config.tm.hcl |
OpenTofu version and backend key overrides |
imports/generators/v1/*.tm.hcl |
Versioned code generators (VPC, EKS, namespace, app) |
imports/mixins/*.tm.hcl |
Backend, Terraform block, Kubernetes provider mixins |