Terraform CI pipelines tend to collect tools. You start with terraform and tflint, add terragrunt for environment layering, then trivy for scanning generated plans, infracost for cost diffs on PRs, maybe aws-cli to seed secrets, az to rotate a client secret, gcloud for an IAM change. A year in, your Dockerfile is a 300-line artifact nobody wants to touch.
The oorabona/terraform image packs these into curated flavors, each with a pinned version set and a signed SBOM. You pick the flavor that matches your cloud — no more if aws then apk add awscli conditionals.
The flavors
| Flavor | Compressed size (amd64) | Includes |
|---|---|---|
base |
205 MB | terraform, tflint, terragrunt, trivy, terraform-docs, infracost, github-cli |
aws |
226 MB | base + aws-cli v2 |
azure |
— | base + az-cli |
gcp |
471 MB | base + Google Cloud SDK (the SDK is heavy) |
full |
593 MB | base + AWS + Azure + GCP |
Base already has the tools any pipeline needs. Flavors just add the cloud-specific CLI.
Why not hashicorp/terraform
The official image is terraform alone. It’s ~90 MB, pristine, versioned. Great as a starting point — not a pipeline. Every tool you add on top via RUN apk add... or curl | install is a thing you maintain, scan, and update.
This image does that for you:
- Pinned versions in
variants.yaml— known-good combinations, not “latest of everything on tuesday” - Daily upstream checks — GitHub releases for each tool; minor/patch auto-merges, majors reviewed
- SBOM per build — SPDX format, Sigstore-attested, covers every bundled binary
- CVE scanning in the pipeline — Trivy runs on the image itself before publishing (advisory mode, 15-min timeout for the full flavor since GCP SDK is huge)
Typical CI usage
GitLab CI, base flavor:
validate:
image: ghcr.io/oorabona/terraform:base
script:
- terraform fmt -check -recursive
- tflint --recursive
- terraform init -backend=false
- terraform validate
- trivy config --format sarif --output trivy.sarif .
artifacts:
reports:
sast: trivy.sarif
GitHub Actions, AWS flavor with OIDC (no access keys):
jobs:
plan:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
container:
image: ghcr.io/oorabona/terraform:aws
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: eu-west-3
- run: terragrunt run-all plan
env:
TF_IN_AUTOMATION: "true"
- run: infracost diff --path=.
env:
INFRACOST_API_KEY: $
Full flavor for a meta-orchestration pipeline (managing deployments across all three clouds):
deploy:
image: ghcr.io/oorabona/terraform:full
script:
- ./scripts/login-aws.sh
- ./scripts/login-azure.sh
- ./scripts/login-gcp.sh
- terragrunt run-all apply --terragrunt-non-interactive
The full flavor is 593 MB — not small. But pulling once per pipeline and caching via buildkit is cheaper than maintaining three separate images.
What’s actually in there
docker run --rm ghcr.io/oorabona/terraform:base sh -c '
for t in terraform tflint terragrunt trivy terraform-docs infracost gh; do
echo "$t: $($t --version 2>&1 | head -1)"
done
'
Output example (versions depend on your pull date):
terraform: Terraform v1.14.9
tflint: TFLint version 0.60.3
terragrunt: terragrunt version 0.99.5
trivy: Version: 0.77.0
terraform-docs: terraform-docs version v0.21.1
infracost: Infracost v0.10.44
gh: gh version 2.84.1
All reproducible — the versions live in terraform/config.yaml, the upstream monitor bumps them, each build is tagged with the full matrix.
Reproducibility & provenance
Every push to GHCR and Docker Hub is signed with a Sigstore attestation referencing the SBOM. To verify:
gh attestation verify \
oci://ghcr.io/oorabona/terraform:1.14.9-aws \
--repo oorabona/docker-containers
The SBOM (SPDX JSON) lists every APK package, Go module, and bundled binary with its version and license. Works with syft, grype, and your preferred CVE scanner.
Gotchas
- GCP SDK pulls a lot — the
gcpandfullflavors take longer to pull on cold CI runners. Cache the pull layer if your pipeline runs often; it’s dominated by the SDK (~270 MB of the 471 MB). TF_IN_AUTOMATION=true— set this in CI. Terraform suppresses interactive prompts and some help text, which is what you want.TF_DATA_DIR— if you runterragrunt run-all, pointTF_DATA_DIRat a shared path so downstream jobs can reuse plugins.- OIDC beats static credentials — every major CI platform supports OIDC with the three clouds. Don’t bake
AWS_ACCESS_KEY_IDinto CI variables. - Version pin in your
versions.tfmatters more than the image tag — the image just provides the CLI. Your plan files are the source of truth.
TL;DR
docker pull ghcr.io/oorabona/terraform:base # 205 MB — no cloud CLI
docker pull ghcr.io/oorabona/terraform:aws # 226 MB — base + awscli v2
docker pull ghcr.io/oorabona/terraform:azure # base + az
docker pull ghcr.io/oorabona/terraform:gcp # 471 MB — base + gcloud
docker pull ghcr.io/oorabona/terraform:full # 593 MB — all three
All flavors: container dashboard.
If you use this in a pipeline, ⭐ the repo. It helps us prioritise what to add next.