DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Cicd Tutorial

Introduction to CI/CD
Getting Started with GitLab CI/CD
Stages, Jobs, Artifacts & Cache
Variables, Secrets and Environments in GitLab CI
GitLab Runners
GitLab CI Pipeline Rules
GitLab CI Environments
GitLab CI Docker Workflows
GitLab CI Testing
GitLab CI Security Scanning
GitLab CI Deploy to AWS
GitLab CI Multi-Project Pipelines
GitLab CI Best Practices

GitLab CI Artifacts

PreviousPrev
Next

Learn GitLab CI artifacts, how to define, use, and download build artifacts, pass files between jobs, set expiration times, and use artifacts for test reports and deployment packages.

Every GitLab job runs in a fresh environment. That clean isolation is great for reliability, but it creates a problem: files produced by one job do not automatically exist in the next job. If your build job creates a compiled app, your test job cannot see it unless you explicitly preserve and pass it forward. That is exactly what artifacts are for.

Artifacts are one of the most important GitLab CI/CD concepts because they let you move meaningful outputs through a pipeline and make them downloadable later from the GitLab UI.

What Are Artifacts?

An artifact is a file or directory saved from a job after the job finishes. GitLab stores those files for a configurable amount of time so they can be:

  • downloaded from the job or pipeline page
  • consumed by later jobs in the same pipeline
  • processed as structured reports such as JUnit test results

Typical artifacts include:

  • compiled binaries
  • dist/ folders from frontend builds
  • zipped deployment packages
  • JUnit XML test reports
  • coverage reports
  • generated documentation
  • logs collected for debugging failed jobs

A useful mental model is this:

  • Artifacts preserve outputs that matter for correctness
  • Cache preserves reusable inputs for speed

If a later job truly needs a file created earlier, artifacts are usually the right answer.

Defining Artifacts with artifacts: paths:

The most common way to create artifacts is with artifacts: paths:.

build-app:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

After the job succeeds, GitLab saves the dist/ directory as a job artifact.

What paths means

Each path is relative to the project directory in the job workspace. You can store files or folders.

artifacts:
  paths:
    - dist/
    - coverage/
    - reports/junit.xml

That tells GitLab to archive multiple outputs from the same job.

Why this matters

Suppose your build step compiles a Node.js app into dist/. If you want a later packaging or deployment job to use that compiled output, you should not rebuild it unless you have to. Artifacts allow the downstream job to use the exact build result produced earlier.

artifacts: expire_in: for Automatic Cleanup

Artifacts take storage space, so GitLab lets you define how long they should be kept.

build-app:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

After one week, GitLab automatically removes the stored artifact.

Common expiration choices

  • 1 hour for temporary build outputs
  • 1 day for frequent branch pipelines
  • 1 week for normal CI outputs
  • 1 month or longer for release bundles and reports you want to retain

Choose the shortest retention that still supports your team workflow. A quick feature-branch build probably does not need to live forever. A release package may need a much longer lifetime.

Why expiration is important

Without cleanup, artifact storage can grow quickly. Large projects build many branches, many merge requests, and many reports. expire_in keeps storage predictable and prevents old, unused pipeline files from piling up.

Downloading Artifacts from the GitLab UI

One of the easiest benefits of artifacts is visibility. When a job stores artifacts, GitLab exposes them in the UI.

From the pipeline page or job page, you can typically:

  • open the job details
  • find the artifact section
  • download the artifact archive
  • inspect test or report output if the artifact is a recognized report type

This is especially useful when you want to:

  • download a compiled application package
  • inspect a generated report after a CI run
  • retrieve a debug log from a failed pipeline
  • share a build output with teammates without rebuilding locally

For many teams, artifacts become the bridge between automation and human review.

Passing Files to Later Jobs

By default, later jobs in later stages can download artifacts from earlier jobs. That makes a simple build-then-test or build-then-deploy pipeline straightforward.

stages:
  - build
  - deploy

build-app:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

deploy-app:
  stage: deploy
  script:
    - test -d dist
    - ./deploy.sh

In this example, deploy-app can use the dist/ directory created by build-app because it is an artifact from an earlier stage.

That behavior is convenient, but in larger pipelines you often want tighter control over exactly which artifacts get downloaded.

Accessing Artifacts from Previous Jobs: dependencies vs needs

There are two related ideas here:

  • dependencies controls which earlier job artifacts a job downloads
  • needs creates an execution dependency and can also fetch artifacts sooner in DAG pipelines

Using dependencies

dependencies is useful when you want to limit artifact downloads to specific previous jobs.

package-app:
  stage: deploy
  script:
    - tar -czf release.tar.gz dist
  dependencies:
    - build-app

This tells GitLab to download artifacts only from build-app, not from every earlier job that might have published artifacts.

Why does that matter? Because downloading unnecessary artifacts wastes time and bandwidth. In a large pipeline, selective downloads can make jobs faster and cleaner.

Using needs

needs is about the job graph. It allows a job to start as soon as its required upstream jobs finish, instead of waiting for the whole previous stage.

package-app:
  stage: deploy
  needs:
    - job: build-app
      artifacts: true
  script:
    - tar -czf release.tar.gz dist

Here, package-app explicitly needs build-app and requests its artifacts. This is powerful because the job can start earlier in a DAG pipeline and still receive the required files.

When to use which

  • Use dependencies when you mainly want to control which earlier artifacts are downloaded.
  • Use needs when you want both faster scheduling and explicit dependency relationships.

In newer pipelines, needs is often the more flexible pattern because it improves both clarity and speed.

artifacts: reports: for Structured GitLab Reports

Not all artifacts are just downloadable archives. GitLab understands certain artifact types as reports and uses them to power UI features.

unit-tests:
  stage: test
  script:
    - npm ci
    - npm test -- --reporters=jest-junit
  artifacts:
    reports:
      junit: junit.xml

In this case, junit.xml is not only stored as an artifact. GitLab also interprets it as a JUnit test report and can show test results in the pipeline and merge request UI.

Common report artifact types

JUnit test reports

artifacts:
  reports:
    junit: junit.xml

Use this for unit test or integration test output in JUnit XML format.

Coverage reports

Coverage reporting often uses a specific format produced by your test tool.

artifacts:
  reports:
    coverage_report:
      coverage_format: cobertura
      path: coverage/cobertura-coverage.xml

This helps GitLab visualize code coverage information.

SAST and security reports

Security scanners can produce reports that GitLab understands.

artifacts:
  reports:
    sast: gl-sast-report.json

The same idea applies to dependency scanning and other security-focused report types in GitLab.

Why reports are special

A normal artifact is something you download. A report artifact is something GitLab can parse and surface directly in the product experience. That makes pipelines much more useful because results are visible where developers already work.

artifacts: when:

By default, artifacts are usually saved on successful jobs. Sometimes you want different behavior.

artifacts:
  when: always
  paths:
    - logs/

Common values are:

  • on_success
  • on_failure
  • always

on_success

Save artifacts only if the job succeeds. This is the normal choice for build outputs and deployment bundles.

on_failure

Save artifacts only if the job fails. This is great for debugging data.

integration-tests:
  stage: test
  script:
    - ./run-integration-tests.sh
  artifacts:
    when: on_failure
    paths:
      - logs/
      - screenshots/

If the test run fails, you can collect screenshots, logs, and traces for investigation.

always

Save artifacts regardless of result. This is useful when diagnostic output is valuable even on passing runs.

artifacts: untracked:

GitLab can also save untracked files automatically.

build-debug:
  stage: build
  script:
    - ./generate-debug-files.sh
  artifacts:
    untracked: true

This captures files created during the job that are not tracked by Git.

Why use it carefully

untracked: true is convenient, but it can also collect more than you expect. Large temporary files, generated directories, or accidental outputs may get stored. For predictable pipelines, paths: is usually clearer. Use untracked when you genuinely want all generated untracked files and understand the storage impact.

Real Example 1: Build Artifact for a Compiled Application

Imagine a frontend app built with Node.js.

stages:
  - build
  - deploy

build-web:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

deploy-web:
  stage: deploy
  image: alpine:3.20
  dependencies:
    - build-web
  script:
    - test -d dist
    - ./scripts/deploy-dist.sh

Here, the build output in dist/ is preserved and then consumed by deployment. This ensures the deployment job uses the exact files generated during the verified build.

Real Example 2: Test Report Artifact

Now imagine a test job that produces both human-readable output and a machine-readable JUnit report.

unit-tests:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm test -- --reporters=jest-junit
  artifacts:
    reports:
      junit: junit.xml
    paths:
      - junit.xml
    expire_in: 3 days

The file is stored as a normal artifact and also recognized as a report. That means:

  • developers can download it if needed
  • GitLab can show test results in the UI
  • merge requests get better visibility into failures

Real Example 3: Deployment Package Artifact

Suppose you want to package a Node.js application into a tarball for release.

stages:
  - build
  - package
  - deploy

build-app:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

create-package:
  stage: package
  image: alpine:3.20
  needs:
    - job: build-app
      artifacts: true
  script:
    - tar -czf release.tar.gz dist
  artifacts:
    paths:
      - release.tar.gz
    expire_in: 1 month

deploy-prod:
  stage: deploy
  image: alpine:3.20
  dependencies:
    - create-package
  script:
    - ls -lh release.tar.gz
    - ./deploy-release.sh release.tar.gz

This pattern is common in production pipelines. Build first, package once, then deploy the packaged result.

Good Artifact Hygiene

Artifacts are powerful, but they should be intentional.

Save what matters, not everything

Do not store entire workspaces unless you have a strong reason. Store only the files later jobs or humans actually need.

Set expiration times

A missing expire_in is not always wrong, but clear retention is healthier for storage management.

Use reports where possible

If GitLab can interpret a report, let it. Structured visibility is better than forcing people to download raw files.

Avoid rebuilding if a verified build output already exists

A downstream job should ideally consume the tested build artifact rather than repeating the build. That improves consistency.

Common Mistakes

  1. Expecting files to survive automatically between jobs Each job is isolated. Without artifacts, generated files disappear.

  2. Using cache instead of artifacts for pipeline outputs Cache is not a correctness mechanism. Artifacts are.

  3. Storing too many large files forever This creates unnecessary storage cost.

  4. Not narrowing downloads in large pipelines Use dependencies or needs to avoid pulling unrelated artifacts.

  5. Forgetting report syntax A file under paths: alone does not automatically become a GitLab test or coverage report.

Final Takeaway

Artifacts make pipelines practical. They preserve outputs, move files between isolated jobs, expose useful downloads in the GitLab UI, and power rich reporting features. Once you start treating artifacts as deliberate pipeline handoffs, your CI/CD design becomes more reliable and much easier to reason about.


Knowledge Check

Exercise

Question 1: Purpose of Artifacts

What is the primary reason to use artifacts in GitLab CI/CD?

Exercise

Question 2: Expiration

What does artifacts: expire_in: 1 week do?

Exercise

Question 3: Reports

Why would you use artifacts: reports: junit: junit.xml in a test job?

PreviousPrev
Next

Continue Learning

Getting Started with GitLab CI/CD

Create your first GitLab CI/CD pipeline from scratch, write a .gitlab-ci.yml file, understand GitLab Runners, trigger your first pipeline, and read the job output. Step-by-step tutorial for beginners.

14 min read·Easy

Stages, Jobs, Artifacts & Cache

Understand how GitLab pipelines are organized with stages and jobs, how artifacts move files between jobs, and how cache speeds up repeated work.

6 min read·Easy

GitLab CI Stages and Jobs

Master GitLab CI/CD stages and jobs, learn how to define pipeline stages, write job scripts, use rules and only/except conditions, control job ordering, and run parallel jobs.

12 min read·Easy

Explore Related Topics

Do

Docker Tutorials

Build and push Docker images in your pipelines

Gi

Git Tutorials

Trigger CI/CD pipelines from Git events

Try the Tool

YAML Validator

Validate GitHub Actions and CI/CD pipeline YAML files.

Cron Parser

Build and understand scheduled workflow cron expressions.

On This Page

What Are Artifacts?Defining Artifacts with `artifacts: paths:`What `paths` meansWhy this matters`artifacts: expire_in:` for Automatic CleanupCommon expiration choicesWhy expiration is importantDownloading Artifacts from the GitLab UIPassing Files to Later JobsAccessing Artifacts from Previous Jobs: `dependencies` vs `needs`Using `dependencies`Using `needs`When to use which`artifacts: reports:` for Structured GitLab ReportsCommon report artifact typesWhy reports are special`artifacts: when:``on_success``on_failure``always``artifacts: untracked:`Why use it carefullyReal Example 1: Build Artifact for a Compiled ApplicationReal Example 2: Test Report ArtifactReal Example 3: Deployment Package ArtifactGood Artifact HygieneSave what matters, not everythingSet expiration timesUse reports where possibleAvoid rebuilding if a verified build output already existsCommon MistakesFinal TakeawayKnowledge Check