GitLab CI Configuration
The gitlab section configures the generated GitLab CI pipeline. This section is used when the resolved provider is gitlab, selected via TERRACI_PROVIDER, auto-detected from GITLAB_CI or CI_SERVER_URL, or inferred as the single active provider. When the provider is github, this section is omitted and the github section is used instead. See GitHub Actions Configuration for the GitHub equivalent.
Options
Execution settings
binary, init_enabled, parallelism, and Terraform job env live under the top-level execution: section, not under extensions.gitlab. Plan jobs are derived from apply intent and resource requests such as plan.json consumers.
image
Type: string or objectDefault: derived from execution.binary (hashicorp/terraform:1.6 for Terraform, ghcr.io/opentofu/opentofu:1.6 for OpenTofu) Required: No
Optional Docker image override for Terraform jobs (in default section). Supports both simple string format and object format with entrypoint override.
String format (simple):
extensions:
gitlab:
# Terraform
image: "hashicorp/terraform:1.6"
# OpenTofu
image: "ghcr.io/opentofu/opentofu:1.6"
# Custom image
image: "registry.example.com/terraform:1.6"Object format (with entrypoint):
extensions:
gitlab:
# OpenTofu minimal image requires entrypoint override
image:
name: "ghcr.io/opentofu/opentofu:1.9-minimal"
entrypoint: [""]
# Custom image with specific entrypoint
image:
name: "registry.example.com/terraform:1.6"
entrypoint: ["/bin/sh", "-c"]OpenTofu Minimal Images
OpenTofu minimal images (e.g., opentofu:1.9-minimal) have a non-shell entrypoint. Use the object format with entrypoint: [""] to override it for GitLab CI compatibility.
stages_prefix
Type: stringDefault: "deploy"
Prefix for generated stage names.
extensions:
gitlab:
stages_prefix: "deploy" # Produces: deploy-0, deploy-1
# stages_prefix: "terraform" # Produces: terraform-0, terraform-1Use terraci generate --plan-only for read-only generation. There is no provider-level plan toggle; plan jobs and detailed plan artifacts are derived from request apply intent and resource requests.
Apply scheduling is controlled through job_defaults or overwrites. For example, make apply jobs manual:
extensions:
gitlab:
overwrites:
- type: apply
when: manualCaching is controlled by cache.enabled. When enabled, each Terraform job will have a cache configuration:
plan-platform-prod-vpc:
cache:
key: platform-prod-us-east-1-vpc
paths:
- platform/prod/us-east-1/vpc/.terraform/The cache key is derived from the module path with slashes replaced by dashes.
cache
Type: objectDefault: null
Advanced cache settings for generated GitLab jobs.
extensions:
gitlab:
cache:
enabled: true
key: "terraform-{service}-{environment}-{module}"
policy: pull-push
paths:
- "{module_path}/.terraform/"
- "{module_path}/.terraform.lock.hcl"Supported placeholders for cache.key and cache.paths:
{module_path}{service}{environment}{region}{module}
If cache.paths is omitted, TerraCi keeps the default module-local cache path:
cache:
paths:
- "{module_path}/.terraform/"variables
Type: map[string]stringDefault: {}
Global pipeline variables.
extensions:
gitlab:
variables:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
AWS_DEFAULT_REGION: "us-east-1"rules
Type: arrayDefault: []
Workflow rules for conditional pipeline execution. Controls when pipelines are created.
extensions:
gitlab:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- if: '$CI_COMMIT_TAG'
when: never
- when: neverGenerated output:
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: neverEach rule can have:
if- Condition expressionwhen- When to run:always,never,on_success,manual,delayedchanges- File patterns that trigger the rule
job_defaults
Type: objectDefault: null
Default settings applied to all generated jobs (both plan and apply). These are applied before overwrites, so overwrites can override job_defaults.
Available fields:
image- Docker image for all jobsid_tokens- OIDC tokens for all jobssecrets- Secrets for all jobsbefore_script- Commands before each jobafter_script- Commands after each jobartifacts- Artifacts configurationtags- Runner tagsrules- Job-level rulesvariables- Additional variables
Example: Common settings for all jobs
extensions:
gitlab:
job_defaults:
tags:
- terraform
- docker
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
variables:
CUSTOM_VAR: "value"overwrites
Type: arrayDefault: []
Job-level overrides for plan or apply jobs. Allows customizing specific job types with different settings. Applied after job_defaults.
Each overwrite has:
type- Which jobs to override:planorapplyimage- Override Docker imageid_tokens- Override OIDC tokenssecrets- Override secretsbefore_script- Override before_scriptafter_script- Override after_scriptartifacts- Override artifacts configurationtags- Override runner tagsrules- Set job-level rulesvariables- Override/add variables
Example: Different images for plan and apply
extensions:
gitlab:
image: "hashicorp/terraform:1.6"
overwrites:
- type: plan
image: "custom/terraform-plan:1.6"
tags:
- plan-runner
- type: apply
image: "custom/terraform-apply:1.6"
tags:
- apply-runner
- productionExample: Add job-level rules for apply jobs
extensions:
gitlab:
overwrites:
- type: apply
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
- when: neverExample: Different secrets for different job types
extensions:
gitlab:
job_defaults:
secrets:
COMMON_SECRET:
vault: common/secret@namespace
overwrites:
- type: apply
secrets:
DEPLOY_KEY:
vault: deploy/key@namespace
file: trueExample: job_defaults with overwrites
extensions:
gitlab:
# Common settings for all jobs
job_defaults:
tags:
- terraform
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
# Override for apply jobs only
overwrites:
- type: apply
tags:
- terraform
- production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manualFull Example
execution:
binary: terraform
init_enabled: true
extensions:
gitlab:
# Optional image override; defaults are derived from execution.binary
image: "hashicorp/terraform:1.6"
# Pipeline structure
stages_prefix: "deploy"
cache:
enabled: true
# Pipeline variables
variables:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
# Workflow rules
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
# Job defaults (applied to all jobs)
job_defaults:
tags:
- terraform
- docker
before_script:
- aws sts get-caller-identity
after_script:
- echo "Job completed"
id_tokens:
AWS_OIDC_TOKEN:
aud: "https://gitlab.example.com"
secrets:
CREDENTIALS:
vault: ci/terraform/credentials@namespace
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
# Job overwrites (override job_defaults for specific job types)
overwrites:
- type: apply
tags:
- production
- secure
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manualGenerated Output
With the above configuration, TerraCi generates:
variables:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
default:
image: hashicorp/terraform:1.6
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
stages:
- deploy-0
- deploy-1
plan-platform-prod-vpc:
stage: deploy-0
script:
- cd platform/prod/us-east-1/vpc
- terraform init
- terraform plan -out=plan.tfplan
variables:
TF_MODULE: vpc
# ...
tags:
- terraform
- docker
before_script:
- aws sts get-caller-identity
after_script:
- echo "Job completed"
id_tokens:
AWS_OIDC_TOKEN:
aud: "https://gitlab.example.com"
secrets:
CREDENTIALS:
vault: ci/terraform/credentials@namespace
artifacts:
paths:
- platform/prod/us-east-1/vpc/plan.tfplan
expire_in: 1 day
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
cache:
key: platform-prod-us-east-1-vpc
paths:
- platform/prod/us-east-1/vpc/.terraform/
apply-platform-prod-vpc:
stage: deploy-1
script:
- cd platform/prod/us-east-1/vpc
- terraform init
- terraform apply plan.tfplan
needs:
- plan-platform-prod-vpc
tags:
- production
- secure
before_script:
- aws sts get-caller-identity
after_script:
- echo "Job completed"
id_tokens:
AWS_OIDC_TOKEN:
aud: "https://gitlab.example.com"
secrets:
CREDENTIALS:
vault: ci/terraform/credentials@namespace
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manualPer-Job Variables
Each job receives environment variables that are dynamically generated from the segment names defined in your structure.pattern. For the default pattern {service}/{environment}/{region}/{module}, the variables are:
| Variable | Description | Example |
|---|---|---|
TF_MODULE_PATH | Relative path to module | platform/prod/us-east-1/vpc |
TF_SERVICE | Service name | platform |
TF_ENVIRONMENT | Environment name | prod |
TF_REGION | Region name | us-east-1 |
TF_MODULE | Module name | vpc |
If you use a custom pattern like {team}/{stack}/{datacenter}/{component}, the variables would instead be TF_TEAM, TF_STACK, TF_DATACENTER, and TF_COMPONENT. The variable names are always derived by uppercasing the segment name and prefixing with TF_.
See Also
- Summary Configuration — MR/PR comments with plan summaries and plugin reports
- GitHub Actions Configuration — the equivalent configuration for GitHub Actions
- Pipeline Generation Guide — end-to-end guide for generating CI pipelines