GitLab Artifacts: What Belongs in the Release Build and What Doesn't
AI generated
CI/CD
.yml
GitLab · CI/CD · Magento · Artifacts
GitLab Artifacts:
What Belongs in the Release Build, and What Doesn't

Anyone who packs directories into GitLab artifacts without a plan ends up with bloated packages, non reproducible deployments, and unnecessary server risk. Clean separation between build output, shared data, and server state is the core of a resilient release process for Magento.

12 min read vendor · generated · pub/static · shared · artifacts GitLab CI · Magento 2 · PHP 8.4

1. What a GitLab Artifact Really Is

A GitLab artifact is not a backup and not a server copy, it is the defined output package of a CI job that gets passed on to subsequent jobs in the pipeline or on to deployment. GitLab stores artifacts in the CI system and makes them available for download on demand. That sounds technically simple, but it has far reaching consequences for the entire deployment process: whatever ends up in the artifact must be reproducible from the source code. Whatever cannot be produced reproducibly does not belong in the package.

The decisive idea is the separation between build output and operational state. Build output arises from the repository through defined commands: Composer, npm, di:compile, SCD. Operational state, on the other hand, is the result of the running system: logs, sessions, uploaded files, configuration with real credentials. Anyone who mixes these two categories ends up with artifacts that are neither secure nor consistent. For Magento projects this separation is especially important because the framework has many layers that look like build output at first glance but are actually server specific.

2. Build Output vs. Server State: The Core Divide

The basic rule is: everything that can be deterministically generated from the repository is potential build output. Everything that is environment specific or arises at runtime is server state and has no business in the artifact. For Magento that concretely means: vendor/ is the result of composer install with a fixed composer.lock, deterministic, hence build output. The file app/etc/env.php, on the other hand, contains database passwords and differs per environment, that is server state, never goes into the artifact.

This separation has a direct effect on rollback capability. An artifact that contains no server specific data can be rolled back to an earlier release without endangering credentials or user data. An artifact that includes env.php carries production data through the CI system, a significant security risk. The clean dividing line is therefore not an academic question but has direct security and operational relevance for every Magento store.

3. Magento Directories Checked Against the Artifact Rule

Magento has a complex directory structure, and each directory has to be evaluated individually. vendor/ contains all Composer dependencies and is clearly build output. generated/ contains the compiled DI code and is likewise build output, provided setup:di:compile runs in the build job. pub/static/ contains the deployed frontend assets after setup:static-content:deploy and is build output. These three directories are the core components of a Magento release artifact.

On the other side are directories that must remain on the server: pub/media/ contains uploaded product images and customer photos. var/log/, var/session/, and var/cache/ are operational data. app/etc/env.php and app/etc/config.php carry environment specific configuration. These directories are linked in through the shared concept of the release model, symlinks from the release directory to shared paths outside the release structure.

4. vendor/ and generated/: Build or Deploy?

Whether vendor/ should be shipped in the artifact or reinstalled on the server is a question that comes up often in Magento teams. The clearer answer: build it in the CI system and deploy it as an artifact. That avoids having to run Composer on the production server, which brings network dependencies, auth.json secrets, and differing Composer versions into play. A vendor/ directory built once in the pipeline is reproducible and safe, the production server does not need Composer at all.

generated/ follows the same logic. bin/magento setup:di:compile runs in the build job, the result is packed into the artifact. That way no compile process runs on the production server. This is especially relevant because di:compile is CPU intensive and would have a significant impact on a web server under load. Together, both directories make the artifact complete: it contains the entire PHP code, all dependencies, and the compiled DI container.

build:magento:
  stage: build
  image: php:8.4-cli
  before_script:
    # Install system dependencies for Magento build
    - apt-get update -qq && apt-get install -y -qq git unzip libzip-dev libicu-dev
    - docker-php-ext-install zip intl bcmath sockets
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    # Install PHP dependencies, no dev packages, locked versions
    - composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
    # Compile Dependency Injection container
    - php bin/magento setup:di:compile
  artifacts:
    name: "magento-build-${CI_COMMIT_SHORT_SHA}"
    paths:
      - vendor/
      - generated/
    expire_in: 2 days
    when: on_success

5. pub/static and Frontend Assets in the Build

The directory pub/static/ is produced by bin/magento setup:static-content:deploy and contains all static frontend files: CSS, JavaScript, images, fonts, Hyva Tailwind output. It is clearly build output and belongs in the artifact, but with one important caveat: setup:static-content:deploy needs a full Magento installation including database access to read theme configuration. That makes this step difficult in an isolated CI container.

The pragmatic approach for Magento teams without database access in the build container: process Tailwind and node_modules in the build job, but run setup:static-content:deploy as the first step of the deploy job on the server, before the symlink is switched. In that case pub/static/ does not belong in the CI artifact at all, it is generated server side instead. That decision has to be made explicitly and documented by the team, the most common mistake is an implicit assumption that works on staging but fails on production under different paths.

6. Shared Data: What Never Belongs in the Artifact

Shared data are files and directories that remain unchanged across multiple releases and are not regenerated with every deployment. In Magento those are primarily: app/etc/env.php with the database connection and cache configuration, pub/media/ with all uploaded media files, var/log/ for application logs, and var/session/ for active user sessions. This data must never end up in the artifact.

Technically this is implemented through a shared/ directory outside the release structure and symlinks from every release directory to these shared paths. When a new release is deployed, the symlinks immediately point to the correct shared data, without anything needing to be copied. The artifact build stays clean: it contains exclusively versioned code and deterministically generated output, no credentials, no user data, no environment specific settings.

7. Configuring artifacts in .gitlab-ci.yml Correctly

The artifacts directive in GitLab CI has more options than most teams use. The paths array defines which directories and files are stored. expire_in sets when the artifact is automatically deleted, important for storage cost and data protection. The keyword when: on_success ensures artifacts are only stored when the job succeeds. With exclude, individual subdirectories can be excluded from paths, which is useful for test dependencies inside vendor/.

An often overlooked point: artifacts between jobs are downloaded automatically whenever dependencies is set or no explicit dependencies: [] is present. That can slow down pipelines if large artifacts get loaded into many jobs unnecessarily. For Magento it is recommended to point the deploy job explicitly only at the build artifacts and configure all other jobs with dependencies: [] to avoid unnecessary downloads.

package:release:
  stage: package
  # Only download artifacts from the build job
  dependencies:
    - build:magento
    - build:frontend
  script:
    # Create a timestamped release package
    - export RELEASE_ID="$(date +%Y%m%d-%H%M%S)-${CI_COMMIT_SHORT_SHA}"
    - mkdir -p "dist/${RELEASE_ID}"
    # Copy build outputs into release directory
    - rsync -a --exclude='.git' --exclude='var/' --exclude='pub/media/' ./ "dist/${RELEASE_ID}/"
    - tar -czf "release-${RELEASE_ID}.tar.gz" -C dist "${RELEASE_ID}"
  artifacts:
    name: "release-${CI_COMMIT_SHORT_SHA}"
    paths:
      - release-*.tar.gz
    expire_in: 7 days
    when: on_success

# Quality check job that does NOT need build artifacts
test:phpcs:
  stage: test
  dependencies: []
  script:
    - vendor/bin/phpcs --standard=Magento2 app/code/

8. Controlling Package Size: expire_in and exclude

Uncontrolled artifact sizes are a common problem in Magento pipelines. vendor/ can be 200 to 400 MB in size, and if this artifact is retained for 30 days in every pipeline run, that quickly adds up to gigabytes of GitLab storage. The solution is a short expire_in time for build artifacts, one to two days is sufficient for most workflows, since release packages can be stored separately with a longer retention period.

The exclude option lets you keep known large subdirectories out of the artifacts. For vendor/ it is worth excluding test directories inside the packages: vendor/*/*/Test/, vendor/*/*/tests/, and similar patterns. For Tailwind and npm, node_modules/ is almost always excluded entirely, the npm build output in the form of CSS files is the only thing that belongs in the artifact, not the entire node_modules directory with hundreds of megabytes.

9. Comparison: Bloated vs. Cleanly Separated

The difference between a poorly and a well configured artifact shows up directly in pipeline performance and deployment security.

Directory In the artifact? Reasoning Where instead?
vendor/ Yes Deterministic from composer.lock N/A
generated/ Yes DI compile output from the build job N/A
pub/static/ Depends on context Needs DB access for SCD First step in the deploy job
app/etc/env.php Never Credentials, environment specific shared/ on the server
pub/media/ Never User data, not versioned shared/ on the server
node_modules/ Never Hundreds of MB, only needed for the build GitLab Cache

The table shows the decision logic: deterministically producible from source code and needed for production means artifact. Environment specific or user data means a shared directory on the server. Only needed for the build process means GitLab Cache, not artifact. These three categories cover every Magento directory and give a clear answer for every individual case.

10. Summary

Deciding what belongs in a GitLab release artifact and what does not is one of the most fundamental design decisions in the deployment process. For Magento the rule is: vendor/ and generated/ are produced in the build job and passed on as an artifact. pub/static/ ideally belongs in the build too, if database access is available in CI, otherwise it is the first deployment step on the server. Everything environment specific and all user data stay in the shared area of the server.

The consequence of this clean separation is a deployment process that is reproducible, rollback capable, and secure. Reproducible, because the artifact always arises from the same inputs. Rollback capable, because an earlier release directory can be reactivated without touching credentials. Secure, because production data never flows through the CI system. Once a team has consistently introduced this separation, they will expect it as a matter of course in every future Magento project.

GitLab Artifacts for Magento: The Essentials at a Glance

Build output goes into the artifact

vendor/ and generated/ are deterministic build output and belong in the artifact. No Composer on production.

Server state stays on the server

env.php, pub/media/, and var/ are shared data and are never packed into the artifact.

Control package size

Keep expire_in short, exclude node_modules/, filter out test directories inside vendor/ with exclude.

dependencies: [] in non deploy jobs

Configure jobs that don't need an artifact with dependencies: [], it saves pipeline time and avoids unnecessary downloads.

11. FAQ: GitLab Artifacts for Magento

1Artifacts vs. cache in GitLab?
Artifacts are job output for pipeline handoff and download. Cache is reusable between pipeline runs. node_modules in cache, vendor/ as artifact.
2vendor/ in the artifact or Composer on the server?
The artifact is better: no network access on production, no auth.json management on the server, reproducible builds.
3Why must env.php never be in the artifact?
Contains database passwords and encryption keys. In a CI artifact these are visible to every pipeline user.
4Typical artifact size for Magento?
150 to 400 MB without pub/static. Above 500 MB suggests unwanted directories, check for node_modules/.
5Rollback when the artifact has expired?
Keep release directories on the server (5 to 10 versions). Rollback is a symlink switch, no new download needed.
6SCD without database access in CI?
Possible with the -f flag, but theme configuration from the DB is missing. Many teams run SCD as the first deploy step on the server.
7How to control artifact storage?
expire_in: 1 day for build artifacts, 7 days for release packages. Exclude node_modules/ and var/ as a general rule.
8Always recompile generated/?
Yes, when PHP classes or DI configuration change. Compile in the build job and ship as an artifact, no setup:di:compile on the server.
9Multiple themes in the artifact?
Build each theme separately in the build job, include all theme outputs in paths. SCD deploys all configured themes at once.
10Artifact straight to the server without rsync?
GitLab offers no native server deploy function. rsync via SSH with explicit exclude rules is the most robust method for Magento.