Practice: retain, publish, and version build outputs

Artifact Management

A job artifact transfers a build output between jobs. A versioned package gives consumers a specific release to download after temporary job artifacts expire.

Build and retain a small output

Use a separate, cloned practice project with the Package Registry enabled, permission to push tags, and a Docker or Kubernetes runner. This exercise publishes and downloads packages within that project. Replace its .gitlab-ci.yml with:

stages:
  - build
  - test
  - publish

build:
  stage: build
  image: alpine:3.22
  script:
    - mkdir -p build dist
    - printf '%s\n' "$CI_COMMIT_SHA" > build/commit.txt
    - tar -czf dist/build.tar.gz -C build commit.txt
    - cd dist && sha256sum build.tar.gz > SHA256SUMS
  artifacts:
    name: "build-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
    paths:
      - dist/
    expire_in: 1 day
    access: developer

verify:
  stage: test
  image: alpine:3.22
  needs:
    - job: build
      artifacts: true
  script:
    - cd dist && sha256sum -c SHA256SUMS
    - tar -xzf build.tar.gz
    - test "$(cat commit.txt)" = "$CI_COMMIT_SHA"

publish:
  stage: publish
  image: alpine:3.22
  needs:
    - job: build
      artifacts: true
    - job: verify
      artifacts: false
  rules:
    - if: '$CI_COMMIT_TAG'
  before_script:
    - apk add --no-cache curl
  script:
    - |
      for file in build.tar.gz SHA256SUMS; do
        curl --fail-with-body \
          --header "JOB-TOKEN: $CI_JOB_TOKEN" \
          --upload-file "dist/$file" \
          "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/course-build/$CI_COMMIT_TAG/$file"
      done
  1. Commit and push the configuration on a branch. Check that build and verify succeed and that publish is absent.
  2. Download the build job’s artifact archive. Locate dist/build.tar.gz and dist/SHA256SUMS. The verify job checks the checksum and the source commit inside the tarball.
  3. Read the expiry information on the job page. Check Settings > CI/CD > Artifacts for the keep-latest setting. It can preserve artifacts beyond expire_in, as can selecting Keep on a job.

Expected result: a named artifact with an expiry policy and a successful checksum check. Inspect the policy now; deletion happens later and runs asynchronously.

Publish and retrieve two versions

From the project clone, tag the verified commit and push the tag:

git tag v0.1.0
git push origin v0.1.0
  1. Open the tag pipeline and check that all three jobs succeed.
  2. Open Deploy > Package registry, then course-build, version v0.1.0.
  3. Download build.tar.gz and SHA256SUMS through the package page into a new empty directory. In that directory, run:
sha256sum -c SHA256SUMS
tar -xzf build.tar.gz
cat commit.txt

On macOS, use shasum -a 256 -c SHA256SUMS for the first command. Expect build.tar.gz: OK. Compare commit.txt with the full commit SHA shown for v0.1.0 under Code > Tags.

Return to the project clone. Create a second commit without changing the application, then publish it under a new tag:

git commit --allow-empty -m "chore: prepare v0.2.0"
git push
git tag v0.2.0
git push origin v0.2.0

Repeat the download and verification for v0.2.0 in another empty directory. Both versions must remain available, pass checksum verification, and contain different commit SHAs matching their tags.

The upload uses the predefined CI_JOB_TOKEN. Use each version tag for one release. Package cleanup policies are separate from job artifact expiration.

See GitLab’s job artifact documentation and Generic Package Registry documentation for retention and authentication details.

Use job artifacts for temporary pipeline transfers. Publish outputs that need a stable version with their source commit and checksum.

Check

Suggested answer
The keep-latest setting or a user’s Keep selection can preserve it. Cleanup also runs asynchronously. The expiry period alone does not determine when the artifact disappears.
Suggested answer
No. It remains subject to job artifact retention. The publish job uploads files to a versioned Package Registry path with its own lifecycle.