Skip to content

Repository files navigation

TerraCi

TerraCi

Terraform Pipeline Generator for GitLab CI & GitHub Actions
Analyze dependencies, estimate costs, enforce policies, and generate optimal pipelines for Terraform/OpenTofu monorepos

Release Build License Go Report

DocumentationInstallationQuick StartExamples


Why TerraCi?

Managing Terraform in a monorepo is painful:

  • Dependencies between modules must be respected — EKS can't deploy before VPC
  • Manual pipelines are tedious to write and a nightmare to maintain
  • Full deployments waste CI minutes when only one module changed
  • No visibility into what a plan will cost or whether it violates policies

TerraCi solves all of this. Point it at your repo, and it generates correct, dependency-aware CI pipelines — with cost estimates and policy checks baked in.

Features

Pipeline Generation

  • GitLab CI & GitHub Actions support
  • Dependency-aware topological ordering
  • Parallel execution of independent modules
  • Plan/apply jobs with configurable provider gates
  • Changed-only mode via git diff

Intelligence

  • AWS cost estimation on every plan
  • OPA policy enforcement (local, git, OCI sources)
  • MR/PR comments with plan summaries, costs & policy results
  • Dependency graph visualization (DOT/PlantUML)
  • Terraform dependency resolution with lock file synchronization

Flexibility

  • Terraform & OpenTofu support
  • Configurable directory patterns (any segment names)
  • Per-job overrides (image, secrets, tags, rules)
  • OIDC tokens & Vault secrets integration
  • Auto-detect CI provider from environment

Developer Experience

  • Interactive TUI wizard for initialization
  • Single binary, zero dependencies
  • JSON schema for IDE autocomplete
  • Shell completions (bash, zsh, fish)
  • Dry-run mode to preview changes
  • Custom plugins via xterraci build --with

Installation

# Homebrew
brew install edelwud/tap/terraci

# Go
go install github.com/edelwud/terraci/cmd/terraci@latest

# Docker
docker run --rm -v $(pwd):/workspace ghcr.io/edelwud/terraci:latest generate

# Binary — download from GitHub Releases
# https://github.com/edelwud/terraci/releases

Quick Start

# Interactive setup wizard
terraci init

# Non-interactive with GitHub Actions
terraci init --ci --provider github

# Validate structure & dependencies
terraci validate

# Generate pipeline
terraci generate -o .gitlab-ci.yml                      # GitLab
terraci generate -o .github/workflows/terraform.yml     # GitHub Actions

# Only changed modules (CI)
terraci generate --changed-only --base-ref main -o .gitlab-ci.yml

How It Works

 Your Repo                    TerraCi                          CI Pipeline
┌─────────────┐    ┌──────────────────────────┐    ┌──────────────────────────┐
│ platform/   │    │ 1. Discover modules      │    │                          │
│  prod/      │───>│ 2. Parse remote_state    │───>│  plan-vpc → apply-vpc    │
│   eu-west/  │    │ 3. Build dependency DAG  │    │       ↓           ↓      │
│    vpc/     │    │ 4. Topological sort      │    │  plan-eks   plan-rds     │
│    eks/     │    │ 5. Detect CI provider    │    │       ↓           ↓      │
│    rds/     │    │ 6. Generate YAML         │    │  apply-eks  apply-rds    │
└─────────────┘    └──────────────────────────┘    │                          │
                                                   │  Output: .gitlab-ci.yml  │
                                                   │     or   workflow.yml    │
                                                   └──────────────────────────┘

1. Module Discovery

Scans directories matching a configurable pattern (default: {service}/{environment}/{region}/{module}):

platform/prod/eu-central-1/vpc/           → Module: platform/prod/eu-central-1/vpc
platform/prod/eu-central-1/eks/           → Module: platform/prod/eu-central-1/eks
platform/prod/eu-central-1/ec2/rabbitmq/  → Submodule (depth 5)

The pattern is fully configurable — {team}/{project}/{component} works too.

2. Dependency Resolution

Dependencies are extracted from terraform_remote_state data sources. The key in the remote state config must mirror your directory structure (matching structure.pattern) — this is how TerraCi maps state file paths back to modules:

# eks/main.tf — key follows the same {service}/{env}/{region}/{module} pattern
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "my-state"
    key    = "platform/prod/eu-central-1/vpc/terraform.tfstate"
    #        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #        matches directory structure → TerraCi resolves: eks depends on vpc
    region = "eu-central-1"
  }
}

TerraCi evaluates Terraform functions statically (split, element, length, abspath, lookup, join, format, etc.) and resolves locals that depend on path.module. A common pattern that works out of the box:

locals {
  path_arr    = split("/", abspath(path.module))
  service     = local.path_arr[length(local.path_arr) - 4]
  environment = local.path_arr[length(local.path_arr) - 3]
  region      = local.path_arr[length(local.path_arr) - 2]
}

data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    key = "${local.service}/${local.environment}/${local.region}/vpc/terraform.tfstate"
  }
}

Limitations:

  • Static analysis only — TerraCi does not connect to remote backends or execute terraform init. Dependencies that rely on runtime values (e.g., data.terraform_remote_state.X.outputs.Y used as a key in another remote state) cannot be resolved. Derive your state keys from the filesystem path (abspath(path.module)) or explicit locals, not from other modules' outputs.
  • Backend-aware matching — When the key path alone is ambiguous (e.g., two modules with the same key in different buckets), TerraCi parses each module's terraform { backend "s3" { ... } } block and uses the bucket to disambiguate. This requires backend configuration to be defined in the module's .tf files (not solely via -backend-config CLI flags).

3. Pipeline Generation

Pipeline jobs are built as a dependency DAG. Independent modules can run in parallel, while dependent modules wait for the jobs that produce their inputs:

plan-vpc  -> apply-vpc
apply-vpc -> plan-eks  -> apply-eks
apply-vpc -> plan-rds  -> apply-rds

CI providers render the same DAG with their native mechanics: GitHub uses needs, while GitLab derives stages from topological job groups.

Configuration

# .terraci.yaml
structure:
  pattern: "{service}/{environment}/{region}/{module}"

exclude:
  - "*/test/*"
  - "*/sandbox/*"

execution:
  binary: "terraform"           # or "tofu"

extensions:
  # GitLab CI (omit for GitHub Actions)
  gitlab:
    image: { name: "hashicorp/terraform:1.6" }

  # GitHub Actions (omit for GitLab CI)
  # github:
  #   runs_on: "ubuntu-latest"

  # MR/PR summary comments (enabled by default)
  summary:
    on_changes_only: false
    include_details: true
    labels:
      - terraform
      - "{environment}"
      - "{module}"
      - "resource:{resource_type}"

  # AWS cost estimation
  # cost:
  #   providers:
  #     aws: { enabled: true }

  # OPA policy checks
  # policy:
  #   enabled: true
  #   sources:
  #     - type: path
  #       path: policies
  #   decisions:
  #     deny: block                      # block, warn, ignore
  #     warn: warn                       # block, warn, ignore

  # Dependency update checks
  # tfupdate:
  #   enabled: true
  #   target: all                        # all, modules, providers
  #   policy:
  #     bump: minor                      # patch, minor, major

Tip: Add # yaml-language-server: $schema=https://raw.githubusercontent.com/edelwud/terraci/main/terraci.schema.json at the top of your .terraci.yaml for IDE autocomplete. Or run terraci schema to generate the schema locally.

Commands

Command Description
terraci init Interactive TUI wizard to create .terraci.yaml
terraci validate Validate project structure and dependencies
terraci generate Generate CI pipeline (GitLab CI or GitHub Actions)
terraci graph Visualize dependency graph (DOT, PlantUML, levels)
terraci cost Estimate AWS costs from Terraform plan files
terraci summary Post plan/cost/policy summary to MR/PR (CI)
terraci policy pull Materialize policies from configured sources
terraci policy check Evaluate plans against OPA policies
terraci schema Generate JSON schema for config validation
terraci tfupdate Resolve Terraform dependency versions and sync lock files
terraci version Show version and embedded OPA version
CI integration patterns

GitLab CI supports generative pipelines — TerraCi runs as a job inside a parent pipeline and generates a child pipeline that GitLab picks up automatically:

# .gitlab-ci.yml (parent pipeline)
generate:
  stage: generate
  image: ghcr.io/edelwud/terraci:latest
  script:
    - terraci generate --changed-only --base-ref $CI_MERGE_REQUEST_DIFF_BASE_SHA -o generated.yml
  artifacts:
    paths: [generated.yml]

deploy:
  stage: deploy
  trigger:
    include:
      - artifact: generated.yml
        job: generate

GitHub Actions does not support dynamic workflow generation at runtime. Use a pre-commit hook to regenerate the workflow file and commit it alongside your changes:

# .husky/pre-commit or .git/hooks/pre-commit
terraci generate --changed-only --base-ref main -o .github/workflows/terraform.yml
git add .github/workflows/terraform.yml
Common usage examples
# Changed modules only (for MR/PR pipelines)
terraci generate --changed-only --base-ref main -o .gitlab-ci.yml

# Filter by any segment name
terraci generate --filter environment=prod --filter service=platform

# Plan-only mode (no apply jobs)
terraci generate --plan-only

# Exclude patterns
terraci generate --exclude "*/sandbox/*" --exclude "*/test/*"

# Dependency graph as DOT
terraci graph --format dot -o deps.dot

# Show execution levels
terraci graph --format levels

# Show what depends on a module
terraci graph --module platform/prod/eu-central-1/vpc --dependents

# Policy check a specific module
terraci policy check --module platform/prod/eu-central-1/vpc --format json

# Dry run
terraci generate --dry-run

# Estimate AWS costs from plan.json files
terraci cost

# Cost for a single module
terraci cost --module platform/prod/eu-central-1/rds

# Cost as JSON
terraci cost --output json

JSON output now uses explicit resource status values:

  • exact — fully priced at plan time
  • usage_estimated — partly estimated from configured capacity
  • usage_unknown — still unknown at plan time and needs runtime telemetry
  • unsupported / failed — not priced, with optional failure_kind and status_detail

Custom Plugins

Build a custom TerraCi binary with additional (or fewer) plugins using xterraci:

# Install xterraci
go install github.com/edelwud/terraci/cmd/xterraci@latest

# Add an external plugin
xterraci build --with github.com/myorg/terraci-plugin-slack

# Use a local plugin during development
xterraci build --with github.com/myorg/plugin=../my-plugin

# Remove a built-in plugin
xterraci build --without cost

# List available built-in plugins
xterraci list-plugins

See examples/external-plugin/ for a minimal plugin example.

Documentation

Full documentation is available at edelwud.github.io/terraci.

Topic Link
Getting Started guide/getting-started
Project Structure guide/project-structure
Dependencies guide/dependencies
Pipeline Generation guide/pipeline-generation
Configuration Reference config/
Summary Comments config/summary
Cost Estimation config/cost
Policy Checks config/policy
CLI Reference cli/

Contributing

Contributions are welcome! Please open an issue or pull request.

License

MIT

About

Terraform/Tofu pipeline generator with smart dependency resolution for your CI

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages