Gitlab: saving artifacts on protected branches only

When working with GitLab CI/CD, sometimes you want to save job artifacts only for protected branches or tags, like main or release/*, while ignoring them on feature branches. It seems simple at first, but as I found out, GitLab’s YAML rules and artifact system have some tricky limitations, especially if you need it while the pipeline is running (tests, image builds, etc).

The dynamic attempt

My first idea was straightforward: use the value of the CI_COMMIT_REF_PROTECTED to dynamically set the expire_in, set some minutes on feature branches, and a month on protected branches. Something like:

build:
script: echo "build"
artifacts:
paths:
- build/
expire_in: $EXPIRE
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
variables:
EXPIRE: "30 days"
- variables:
EXPIRE: "30 minutes"

But the Gitlab said:

This GitLab CI configuration is invalid: jobs:build:artifacts expire in should be a duration.

Turns out the expire_in cannot reference a variable – it must be a literal duration and it does not matter where do you try it: in jobs or as a default… the parser only allows literal duration at parse time.

The merge attempt

Then I tried a clever YAML trick, merging artifacts inside the rules:

.long_term_artifacts: &long_term_artifacts
  artifacts:
    paths:
      - build/
    expire_in: 30 days

build:
  script: echo "build"
  rules:
    - if: '$CI_COMMIT_REF_PROTECTED == "true"'
      <<: *long_term_artifacts

But the Gitlab said:

This GitLab CI configuration is invalid: jobs:build:rules:rule config contains unknown keys: artifacts.

Lesson learned: rules cannot modify artifact configuration.

The solution

After several other failed attempts, I cooked out the most robust solution without code duplication and too much mess:

  • Two jobs, one for protected branches with long term artifacts, one for non-protected branches with short term artifacts
  • No duplication of script by using a list reference

So, in the pipeline, split the current build job to two templates:

.build:
  script: echo "build"

.build_artifact_paths:
  - build/

Then create two job, based on the templates:

protected_build:
  extends: .build
  artifacts:
    expire_in: 30 days
    paths:
      !reference [ .build_artifact_paths ]
  rules:
    - if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_PIPELINE_SOURCE == "push"'

non_protected_build:
  extends: .build
  artifacts:
    expire_in: 30 minutes
    paths:
      !reference [ .build_artifact_paths ]
  rules:
    - if: '$CI_COMMIT_REF_PROTECTED == "false" && $CI_PIPELINE_SOURCE == "push"'

…and we’re still waiting for Gitlab’s solution, because it’s not very pretty, but it works.

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top