Multi-Account Terraform CI/CD Platform
Scaffold is an open-source CLI tool written in Go that bootstraps production-grade, multi-account Terraform CI/CD pipelines using GitHub Actions, centralized S3 remote state, and IAM OIDC authentication
- Centralized State Backend — One S3 + DynamoDB + KMS backend, many spoke accounts
- IAM OIDC Authentication — No long-lived credentials. GitHub Actions assumes roles via federated identity
- SCP-Compliant — Uses inline IAM policies to work in restricted AWS Organizations environments
- Multi-Account — Dynamically grants cross-account access when you add environments
- Operational Visibility —
scaffold statusshows resource inventory, workflow history, and lock status - Idempotent — Safe to re-run. Imports existing resources instead of failing
# 1. Install
curl -fsSL https://raw.githubusercontent.com/Abhiram-Rakesh/Scaffold/main/scripts/install.sh | bash
# 2. Navigate to your infrastructure repo
cd your-infra-repo
# 3. Bootstrap the state backend (runs in your platform account)
scaffold init
# 4. Create your first environment (runs in a spoke account)
scaffold create staging
# 5. Add the GitHub secret shown by scaffold create, then push:
git add . && git commit -m "feat: add staging environment"
git push origin developTerraform runs automatically via GitHub Actions on every push.
curl -fsSL https://raw.githubusercontent.com/Abhiram-Rakesh/Scaffold/main/scripts/install.sh | bashDownload the latest binary for your platform from GitHub Releases.
# Linux amd64
VERSION="$(curl -sSf https://api.github.com/repos/Abhiram-Rakesh/Scaffold/releases/latest | grep '\"tag_name\"' | sed -E 's/.*\"v([^\"]+)\".*/\\1/')"
curl -sSfL "https://github.com/Abhiram-Rakesh/Scaffold/releases/download/v${VERSION}/scaffold_${VERSION}_linux_amd64.tar.gz" | tar -xz
sudo install -m 0755 scaffold /usr/local/bin/scaffoldgit clone https://github.com/scaffold-tool/scaffold
cd scaffold
make installRequirements: Go 1.21+, AWS CLI, Terraform 1.7+
┌─────────────────────────────────────────────────────────────┐
│ GitHub Repository │
│ │
│ .github/workflows/ │
│ ├── terraform-staging.yaml (auto-generated) │
│ ├── terraform-production.yaml (auto-generated) │
│ └── terraform-dev.yaml (auto-generated) │
│ │
│ .scaffold/config.json (tracks configuration) │
└────────────────────────┬────────────────────────────────────┘
│ GitHub Actions (OIDC)
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Staging │ │ Production │ │ Dev │
│ Account │ │ Account │ │ Account │
│ 222222222 │ │ 333333333 │ │ 444444444 │
│ │ │ │ │ │
│ IAM Role: │ │ IAM Role: │ │ IAM Role: │
│ gh-actions- │ │ gh-actions- │ │ gh-actions- │
│ staging │ │ production │ │ dev │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
│ Cross-account S3/DynamoDB/KMS access
▼
┌───────────────────┐
│ Platform Account │
│ 111111111111 │
│ │
│ S3: tf-state-* │ ← Centralized state
│ DDB: tf-lock-* │ ← State locking
│ KMS: Key │ ← Encryption
└───────────────────┘
Bootstrap the centralized Terraform state backend in your platform account.
scaffold initCreates:
- S3 bucket with versioning, KMS encryption, public access block, lifecycle policies
- DynamoDB table with on-demand billing and PITR
- KMS key with rotation enabled
.scaffold/config.json
Create a new environment with a dedicated GitHub Actions workflow and cross-account IAM access.
scaffold create staging
scaffold create production
scaffold create devCreates:
.github/workflows/terraform-<env>.yaml<watch-dir>/providers.tf- IAM OIDC provider in target account (idempotent)
- IAM role with inline policies in target account
- Cross-account S3/KMS/DynamoDB policy updates
Display environment status, resource inventory, and recent workflow runs.
scaffold status staging
scaffold status --all
scaffold status staging --json
scaffold status staging --watch # Live refresh every 5sDestroy all Terraform-managed infrastructure in an environment.
scaffold destroy staging
scaffold destroy staging --auto-approve # Skip confirmation (CI use)Remove environment workflow and config (does NOT destroy infrastructure).
scaffold remove staging # Fails if active resources exist
scaffold remove staging --force # Remove even with active resourcesRemove all Scaffold resources including the state backend.
scaffold uninstall # Requires all environments to be empty
scaffold uninstall --force # Extremely dangerous — orphans resources--verbose, -v Enable verbose logging
--quiet, -q Suppress non-error output
--help, -h Show command help
Scaffold supports three credential methods:
| Method | Description |
|---|---|
| AWS CLI Profile | ~/.aws/credentials named profile |
| Environment Variables | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
| AWS SSO | Named SSO session from ~/.aws/config |
Credentials are never stored in .scaffold/config.json. They're used only for the duration of each command.
- Platform account — Hosts S3 + DynamoDB + KMS backend. You need admin access here once for
scaffold init. - Spoke accounts — Each has one IAM role per environment.
scaffold createhandles policy setup automatically.
When you run scaffold create staging:
- Platform account credentials → update S3 bucket policy to allow spoke account
- Platform account credentials → update KMS key policy
- Platform account credentials → update DynamoDB resource policy
- Spoke account credentials → create OIDC provider (once per account)
- Spoke account credentials → create IAM role with inline policies
See docs/multi-account.md for detailed guidance.
.scaffold/config.json is committed to your repository and tracks all Scaffold state.
{
"version": "1.0",
"backend": {
"account_id": "111111111111",
"region": "us-east-1",
"s3_bucket": "tf-state-myapp-a1b2c3d4",
"dynamodb_table": "tf-lock-myapp-a1b2c3d4",
"kms_key_id": "arn:aws:kms:us-east-1:111111111111:key/..."
},
"repository": {
"org": "my-org",
"name": "my-app",
"default_branch": "main"
},
"environments": [
{
"name": "staging",
"account_id": "222222222222",
"region": "us-east-1",
"watch_directory": "infra/staging",
"trigger_branch": "develop",
"iam_role_arn": "arn:aws:iam::222222222222:role/github-actions-staging",
"state_key": "staging/terraform.tfstate",
"workflow_file": ".github/workflows/terraform-staging.yaml",
"policy_mode": "inline"
}
]
}The OIDC trust policy doesn't match the GitHub sub claim. Check the Debug OIDC Token step in your workflow — the sub claim must match one of:
repo:<org>/<repo>:ref:refs/heads/*repo:<org>/<repo>:environment:*
Ensure your backend account credentials have s3:GetBucketPolicy and s3:PutBucketPolicy permissions.
Use scaffold destroy which offers interactive lock removal, or:
aws dynamodb delete-item \
--table-name <table> \
--key '{"LockID": {"S": "<bucket>/<state-key>"}}'See docs/troubleshooting.md for more.
# Clone
git clone https://github.com/scaffold-tool/scaffold
cd scaffold
# Install dependencies
go mod download
# Build
make build
# Test
make test
# Lint
make lint- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes with tests
- Run
make test lint - Submit a pull request
Please read CONTRIBUTING.md for code style and PR guidelines.