What are artifacts in GitLab CI/CD and how do you pass files between jobs?
Quick Answer
Artifacts are files generated by a job that are uploaded to the GitLab server and can be downloaded by subsequent jobs, the GitLab UI, or the API. They are the primary mechanism for passing build outputs, test results, and compiled binaries between pipeline stages since each job runs in an isolated environment.
Detailed Answer
Think of artifacts like the handoff baton in a relay race. Runner one (the build job) sprints their leg and passes the baton (the compiled binary) to runner two (the test job). Without the baton, the next runner has nothing to work with and the race fails. In GitLab, each job runs in its own isolated lane, so artifacts are the only reliable way to pass the baton from one job to the next.
Artifacts are defined within a job using the artifacts keyword. The paths property specifies which files or directories to upload after the job completes. GitLab compresses these files and stores them on the server (or in configured object storage like S3 or GCS). When a downstream job in a later stage starts, GitLab automatically downloads artifacts from all successful jobs in previous stages, extracting them into the job's working directory. You can control artifact retention with expire_in (e.g., '1 week', '30 days') to prevent storage from growing indefinitely. The when property determines under which conditions artifacts are uploaded: on_success (default, only when the job passes), on_failure (only when the job fails, useful for uploading debug logs), or always (regardless of outcome).
Internally, when a job finishes its script execution, the GitLab Runner scans the working directory for files matching the artifacts paths patterns. It creates a zip archive of the matched files and uploads it to the GitLab server via the API. The server stores the archive and associates it with the specific job and pipeline. When a subsequent job starts, the Runner sends a request to the GitLab server asking for artifacts from dependent jobs. The server responds with download URLs, the Runner fetches and extracts the archives into the working directory, and the job's script can then access those files as if they were created locally. For large artifacts, GitLab supports direct upload to object storage (AWS S3, Google Cloud Storage, Azure Blob Storage) to reduce load on the GitLab server itself.
In production, artifacts serve multiple purposes beyond just passing files between jobs. Teams use the reports keyword to upload structured test results (JUnit XML), code coverage data, SAST and DAST security scan results, and dependency scanning reports. GitLab parses these report artifacts and displays them directly in the merge request interface: test failures appear as annotations on the merge request, code coverage percentages are shown on the diff view, and security vulnerabilities are listed in a dedicated security tab. A typical production pipeline for inventory-management-api would have the build job produce a Docker image reference as an artifact, the test job produce JUnit XML reports, and the security-scan job produce a SAST report. All of these integrate seamlessly into the merge request review experience.
A key gotcha is artifact size and retention. By default, artifacts never expire in many GitLab installations, which can consume enormous amounts of disk space over time. Always set expire_in on your artifacts to a reasonable duration: compiled binaries might expire in one hour since they are only needed for the current pipeline, while test reports might be kept for 30 days for debugging purposes. Another common mistake is defining overly broad paths like '.' which uploads the entire repository including the .git directory and node_modules. Be specific about which files you need, and use the exclude keyword to filter out unnecessary files.