Skip to content

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):

yaml
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):

yaml
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.

yaml
extensions:
  gitlab:
    stages_prefix: "deploy"  # Produces: deploy-0, deploy-1
    # stages_prefix: "terraform"  # Produces: terraform-0, terraform-1

Use 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:

yaml
extensions:
  gitlab:
    overwrites:
      - type: apply
        when: manual

Caching is controlled by cache.enabled. When enabled, each Terraform job will have a cache configuration:

yaml
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.

yaml
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:

yaml
cache:
  paths:
    - "{module_path}/.terraform/"

variables

Type: map[string]stringDefault: {}

Global pipeline variables.

yaml
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.

yaml
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: never

Generated output:

yaml
workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: always
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: always
    - when: never

Each rule can have:

  • if - Condition expression
  • when - When to run: always, never, on_success, manual, delayed
  • changes - 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 jobs
  • id_tokens - OIDC tokens for all jobs
  • secrets - Secrets for all jobs
  • before_script - Commands before each job
  • after_script - Commands after each job
  • artifacts - Artifacts configuration
  • tags - Runner tags
  • rules - Job-level rules
  • variables - Additional variables

Example: Common settings for all jobs

yaml
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: plan or apply
  • image - Override Docker image
  • id_tokens - Override OIDC tokens
  • secrets - Override secrets
  • before_script - Override before_script
  • after_script - Override after_script
  • artifacts - Override artifacts configuration
  • tags - Override runner tags
  • rules - Set job-level rules
  • variables - Override/add variables

Example: Different images for plan and apply

yaml
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
          - production

Example: Add job-level rules for apply jobs

yaml
extensions:
  gitlab:
    overwrites:
      - type: apply
        rules:
          - if: '$CI_COMMIT_BRANCH == "main"'
            when: manual
          - when: never

Example: Different secrets for different job types

yaml
extensions:
  gitlab:
    job_defaults:
      secrets:
        COMMON_SECRET:
          vault: common/secret@namespace

    overwrites:
      - type: apply
        secrets:
          DEPLOY_KEY:
            vault: deploy/key@namespace
            file: true

Example: job_defaults with overwrites

yaml
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: manual

Full Example

yaml
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: manual

Generated Output

With the above configuration, TerraCi generates:

yaml
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: manual

Per-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:

VariableDescriptionExample
TF_MODULE_PATHRelative path to moduleplatform/prod/us-east-1/vpc
TF_SERVICEService nameplatform
TF_ENVIRONMENTEnvironment nameprod
TF_REGIONRegion nameus-east-1
TF_MODULEModule namevpc

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

Released under the MIT License.