Environments

Learn how to configure and manage multiple environments with isolated state and independent deployments.


Overview

The starter kit supports multiple isolated environments (e.g., staging, production) with independent state management and deployment workflows. Each environment lives in its own directory under environments/.

Note: All environment files and GitHub Actions workflows discussed on this page are automatically generated by the make setup command. You don't need to create these files manually. For installation instructions, see the Install guide.

Environment structure

Each environment directory contains:

environments/
└── staging/
    ├── backend.tf           # Terraform and S3 backend configuration
    ├── main.tf             # Provider and module configuration
    ├── variables.tf        # Input variable definitions
    └── outputs.tf          # Output values from the OIDC module

backend.tf

Configures Terraform version requirements, required providers, and the S3 backend for state storage:

terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "terraform-state-123456789012-eu-west-1"
    key            = "environments/staging/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

Key features:

  • State encryption: encrypt = true enables AES-256 server-side encryption
  • State locking: DynamoDB table prevents concurrent modifications
  • Unique state key: Each environment uses its own path (e.g., environments/staging/terraform.tfstate)

main.tf

Configures the AWS provider with default tags and calls the OIDC provider module:

# Configure the AWS Provider
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = "staging"
      ManagedBy   = "terraform"
      Repository  = "towardsthecloud/aws-terraform-starter-kit-demo"
    }
  }
}

# OIDC Provider Module
module "oidc_provider" {
  source = "../../modules/oidc-provider"

  use_existing_oidc_provider = var.use_existing_oidc_provider
  github_repo                = var.github_repo
  role_name                  = var.role_name
  managed_policy_arns        = var.managed_policy_arns
}

Key features:

  • Default tags: Automatically applied to all resources created by the provider
  • Module-based: Uses the oidc-provider module from modules/ directory
  • Configurable OIDC: Can create or reuse existing OIDC providers

variables.tf

Defines all input variables with defaults:

variable "aws_region" {
  description = "AWS region for resources"
  type        = string
  default     = "eu-west-1"
}

variable "use_existing_oidc_provider" {
  description = "Whether to use an existing OIDC provider or create a new one"
  type        = bool
  default     = true
}

variable "github_repo" {
  description = "GitHub repository name (format: owner/repo)"
  type        = string
  default     = "towardsthecloud/aws-terraform-starter-kit-demo"
}

variable "role_name" {
  description = "Name of the IAM role for GitHub Actions"
  type        = string
  default     = "GitHubActionsServiceRole-Terraform"
}

variable "managed_policy_arns" {
  description = "List of IAM policy ARNs to attach to the role"
  type        = list(string)
  default = [
    "arn:aws:iam::aws:policy/AdministratorAccess"
  ]
}

Variables explained:

  • aws_region: AWS region for deploying resources
  • use_existing_oidc_provider: Set to true when deploying multiple environments in the same AWS account (OIDC provider can only exist once per account)
  • github_repo: Your GitHub repository in owner/repo format
  • role_name: IAM role name for GitHub Actions authentication
  • managed_policy_arns: AWS managed policies attached to the IAM role (default is AdministratorAccess for full permissions)

outputs.tf

Exports values from the OIDC module:

output "oidc_provider_arn" {
  description = "ARN of the GitHub OIDC provider"
  value       = module.oidc_provider.oidc_provider_arn
}

output "role_arn" {
  description = "ARN of the GitHub Actions IAM role"
  value       = module.oidc_provider.role_arn
}

output "role_name" {
  description = "Name of the GitHub Actions IAM role"
  value       = module.oidc_provider.role_name
}

These outputs are used to configure GitHub Actions workflows with the correct IAM role ARN.

GitHub Actions workflow

Each environment gets its own dedicated GitHub Actions workflow file that is automatically generated during setup. For the staging environment, this is .github/workflows/terraform-deploy-staging.yml.

The workflow handles the complete deployment lifecycle:

Workflow triggers:

  • Pushes to main branch affecting staging files
  • Pull requests targeting main
  • Manual workflow dispatch

Pipeline stages:

  1. TFLint Scan: Lints Terraform code for syntax and best practices
  2. Checkov Security Scan: Scans for security misconfigurations
  3. Terraform Check: Validates formatting, initialization, and configuration
  4. Terraform Plan: (on PRs) Generates and posts plan to PR comments
  5. Terraform Apply: (on push to main) Deploys changes to AWS

Key features:

  • OIDC authentication: Uses the IAM role created by the OIDC module (no long-lived credentials)
  • Environment protection: Uses GitHub's environment: staging for approval workflows
  • State management: Configures S3 backend dynamically from environment variables
  • Plan artifacts: Saves and uploads plans for review

Environment variables in the workflow:

env:
  AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID || '123456789012' }}
  AWS_REGION: ${{ vars.AWS_REGION || 'eu-west-1' }}
  GITHUB_ACTIONS_ROLE_NAME: ${{ vars.GITHUB_ACTIONS_ROLE_NAME || 'GitHubActionsServiceRole-Terraform' }}
  ENVIRONMENT: staging
  TF_WORKING_DIR: environments/staging
  TF_STATE_BUCKET: ${{ vars.TF_STATE_BUCKET || 'terraform-state-123456789012-eu-west-1' }}
  TF_STATE_LOCK_TABLE: ${{ vars.TF_STATE_LOCK_TABLE || 'terraform-state-lock' }}

These can be customized via GitHub repository variables or use the provided defaults.

Adding a new environment

To add a new environment (e.g., production), use the setup wizard:

make setup

For detailed setup options and non-interactive usage, see the Makefile reference.

The wizard will:

  1. Detect if you already have a backend (reuses existing S3 bucket and DynamoDB table)
  2. Prompt you to select the new environment name
  3. Generate all necessary files:
    • environments/production/backend.tf
    • environments/production/main.tf
    • environments/production/variables.tf
    • environments/production/outputs.tf
    • .github/workflows/terraform-deploy-production.yml
  4. Check for existing OIDC provider in your AWS account
  5. Deploy the OIDC infrastructure for the new environment
  6. Display the IAM role ARN to use in GitHub Actions

Multi-account setup: If deploying to a different AWS account, switch your AWS credentials before running make setup:

# Switch to production account
assume production-account

# Run setup wizard for production environment
make setup  # Select: production

For detailed setup instructions and options, see the Install guide.

Multi-account vs. single-account deployments

Single-account deployment

When deploying multiple environments to the same AWS account, remember that the OIDC provider can only be created once per account:

  • First environment: Set use_existing_oidc_provider = false to create the OIDC provider
  • Additional environments: Set use_existing_oidc_provider = true to reuse the existing provider

Multi-account deployment

When deploying to separate AWS accounts, each account needs its own OIDC provider:

  • Each environment: Set use_existing_oidc_provider = false

This provides complete isolation between environments with separate IAM configurations.

Default tags

The default_tags block in main.tf automatically applies tags to all resources created by Terraform:

default_tags {
  tags = {
    Environment = "staging"
    ManagedBy   = "terraform"
    Repository  = "towardsthecloud/aws-terraform-starter-kit-demo"
  }
}

This ensures consistent tagging across all AWS resources without repeating tag blocks on each resource.

IAM role permissions

The managed_policy_arns variable controls what permissions the GitHub Actions IAM role has. The default is AdministratorAccess for full permissions:

managed_policy_arns = [
  "arn:aws:iam::aws:policy/AdministratorAccess"
]

Next steps