GitLab + Magento Deployment Cheatsheet: Variables, Jobs, Commands, Order
AI generated
CI/CD
.yml
GitLab · Magento · CI/CD · Cheatsheet
GitLab + Magento Deployment Cheatsheet
Variables, Jobs, Commands, Order

Which variables need to be set? How should jobs be structured? In what order do Magento commands need to run? This cheatsheet answers these questions compactly and completely, as a reference for everyday deployment work.

18 min read Variables · Job structure · Magento CLI · Order · Rollback GitLab 16+ · Magento 2.4.8 · PHP 8.4

1. Required variables for every Magento pipeline

The CI/CD variables of a GitLab pipeline are the configuration contract between repository governance and the actual deployment. For Magento projects there is a minimum set of variables without which no pipeline can run safely and reproducibly. SSH_PRIVATE_KEY and SSH_KNOWN_HOSTS enable secure access to the target server. DEPLOY_HOST, DEPLOY_USER and DEPLOY_PATH define where the deployment ends up. COMPOSER_AUTH holds the credentials for private Composer repositories such as Adobe Commerce. APP_ENV controls which Magento configuration is active.

All production secrets, meaning SSH keys, database passwords, API keys, must be marked as Protected and Masked and have an Environment Scope that restricts them to the relevant environment. Variables defined directly in .gitlab-ci.yml should never contain secrets, only non critical configuration values such as cache directories or retention values. This separation of configuration and secrets runs through the whole cheatsheet.

# Required CI/CD variables: configure in Settings > CI/CD > Variables
# SSH access
# SSH_PRIVATE_KEY         : Protected, Masked, Scope: production
# SSH_KNOWN_HOSTS         : Protected, Scope: production

# Server target
# DEPLOY_HOST             : Protected, Scope: production (e.g. web01.mironsoft.de)
# DEPLOY_USER             : Protected, Scope: * (e.g. deploy)
# DEPLOY_PATH             : Protected, Scope: production (e.g. /var/www/magento)

# Magento configuration
# MAGENTO_ENV_FILE        : Protected, Masked, Scope: production
# COMPOSER_AUTH           : Protected, Masked, Scope: *
# APP_ENV                 : Not protected, Scope: production, Value: production

# Pipeline behavior
variables:
  GIT_STRATEGY: fetch
  GIT_DEPTH: "10"
  COMPOSER_CACHE_DIR: .cache/composer
  NPM_CONFIG_CACHE: .cache/npm
  RELEASE_RETENTION: "5"
  DEPLOY_TIMEOUT: "300"

2. Stages and what they mean

The stage definition of a pipeline is not cosmetic, it is the functional order of the deployment process. Six stages have proven themselves for Magento projects: build, test, package, deploy, verify and rollback. The build stage produces the artifact: installing Composer dependencies, building the frontend, compiling DI. The test stage runs PHPStan, PHPUnit and lint checks. In package the artifact is prepared for transfer. In deploy it lands on the server and the symlinks are set. In verify, health checks and smoke tests run. The rollback stage contains a manual job that can be triggered when something goes wrong.

The correct dependency chain matters: no deployment without a successful build and passing tests. No verify without a completed deployment. No automatic rollback, that is always a manual decision. This order is reflected in GitLab through the stage sequence and the job conditions when: on_success and when: manual. Any deviation from this must be deliberate and justified.

3. The build job: what it has to accomplish

The build job is the most critical job in the pipeline. It must produce a complete, reproducible artifact that can be deployed to the target server without any further dependencies. For Magento that means concretely: composer install --no-dev --prefer-dist --no-interaction installs all PHP dependencies. npm ci installs Node dependencies reproducibly from package-lock.json. The Tailwind build produces the compiled CSS files. bin/magento setup:di:compile generates the PHP code for dependency injection. These four steps must run in exactly this order, because each step needs the output of the previous one.

The resulting artifact covers the directories vendor/, generated/ and pub/static/. It should be configured with a short lifetime of one day so old artifacts are cleaned up automatically. The build job must not contain any server specific information, it has to run in an isolated environment and produce the same result no matter how often it is executed.

build:magento:
  stage: build
  image: php:8.4-cli
  cache:
    key: composer-$CI_COMMIT_REF_SLUG
    paths:
      - .cache/composer/
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip nodejs npm libzip-dev
    - docker-php-ext-install zip
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    # Install PHP dependencies from lock file
    - composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
    # Install Node dependencies reproducibly
    - npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
    # Build Tailwind CSS
    - npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
    # Compile Dependency Injection (requires vendor/)
    - php bin/magento setup:di:compile
  artifacts:
    paths:
      - vendor/
      - generated/
      - pub/static/
    expire_in: 1 day
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'

4. The deploy job: getting the artifact onto the server

The deploy job transfers the build artifact to the target server and switches the active release symlink. The transfer happens via rsync over SSH, because rsync only transfers changed files and is therefore noticeably faster than a full scp archive. The symlink switch with ln -sfn is an atomic operation, there is no moment for the web server where the current link points at no valid directory.

After the symlink switch, the Magento specific steps run on the server: linking shared files, deploying static content and flushing the cache. These steps run within one SSH session, so that a single failure aborts the entire operation. The deploy job passes the RELEASE_ID on as an artifact, so the rollback job knows which release to switch back to if something goes wrong.

deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://shop.mironsoft.de
  dependencies:
    - build:magento
  before_script:
    - apk add --no-cache openssh-client rsync
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - RELEASE_ID="$(date +%Y%m%d-%H%M%S)"
    - RELEASE_PATH="${DEPLOY_PATH}/releases/${RELEASE_ID}"
    # Create release directory
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p ${RELEASE_PATH}"
    # Transfer build artifact
    - rsync -az --delete --exclude='.git' --exclude='var/' ./ "${DEPLOY_USER}@${DEPLOY_HOST}:${RELEASE_PATH}/"
    # Link shared files and switch symlink
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        ln -sfn ${DEPLOY_PATH}/shared/app/etc/env.php ${RELEASE_PATH}/app/etc/env.php &&
        ln -sfn ${DEPLOY_PATH}/shared/pub/media ${RELEASE_PATH}/pub/media &&
        ln -sfn ${DEPLOY_PATH}/shared/var ${RELEASE_PATH}/var &&
        ln -sfn ${RELEASE_PATH} ${DEPLOY_PATH}/current
      "
    - echo "RELEASE_ID=${RELEASE_ID}" >> deploy.env
    # Cleanup old releases
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        ls -1dt ${DEPLOY_PATH}/releases/*/ | tail -n +$((${RELEASE_RETENTION}+1)) | xargs rm -rf
      "
  artifacts:
    reports:
      dotenv: deploy.env
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
      when: manual

5. Magento commands in the correct order

The order of the Magento CLI commands after the symlink switch is critical and must not be changed arbitrarily. First, the database migrations run with bin/magento setup:upgrade, and that has to happen before the static content deployment, because migrations can add new layout blocks and configuration values. Next comes bin/magento setup:static-content:deploy with the correct locale parameter. Static content must be deployed before the cache is flushed, so the new cache is rebuilt with fresh assets.

bin/magento cache:flush at the end clears the entire cache, including block cache, config cache and layout cache. For zero downtime deployments, setup:upgrade should only run when there actually are new database migrations, which can be checked with bin/magento setup:db:status. Maintenance mode should be enabled only when incompatible database changes are being deployed that would affect requests running at the same time.

# Magento post-deploy commands: executed on server via SSH
# Run in this exact order to ensure correctness

# Step 1: Check if DB upgrade is needed
# bin/magento setup:db:status
# Exit code 0 = no upgrade needed, exit code 2 = upgrade needed

# Step 2: Conditional DB upgrade (only if needed)
# bin/magento setup:upgrade --keep-generated

# Step 3: Deploy static content for all locales
# bin/magento setup:static-content:deploy de_DE en_US -t Mironsoft/default -f

# Step 4: Flush cache (after static content is ready)
# bin/magento cache:flush

# Step 5: Warm up page cache for critical pages (optional)
# curl -s https://shop.mironsoft.de/ > /dev/null
# curl -s https://shop.mironsoft.de/customer/account/login > /dev/null

# Full sequence as SSH heredoc:
# ssh "$DEPLOY_USER@$DEPLOY_HOST" <<'SSH'
#   set -euo pipefail
#   cd "$DEPLOY_PATH/current"
#   STATUS=$(bin/magento setup:db:status; echo $?)
#   [[ "$STATUS" == "2" ]] && bin/magento setup:upgrade --keep-generated
#   bin/magento setup:static-content:deploy de_DE -t Mironsoft/default -f
#   bin/magento cache:flush
# SSH

6. Verify job: confirming the deployment

A deployment without verification is technically finished but functionally incomplete. The verify job gives the pipeline a clear endpoint: either the shop responds correctly, or the pipeline fails and the error is immediately visible. Smoke tests for Magento should cover at least three checks: HTTP 200 on the homepage, HTTP 200 on the login page and a cache status check via the Magento CLI. In addition, a health endpoint can be checked that tests the database connection, Redis and Elasticsearch/OpenSearch.

The verify job runs automatically after the deploy job with the condition when: on_success. If it fails, the pipeline has failed and the team gets notified. The rollback job, defined afterward in the rollback stage, can then be triggered manually. This pattern, automatic verify combined with manual rollback, is the recommended approach for Magento production systems, because a rollback should always be a deliberate decision and must never be triggered automatically.

7. Rollback: the rehearsed way back

A rollback job that only gets configured once things have already gone wrong is not a real rollback, it is a plan invented under pressure. The rollback job has to exist in the pipeline from the start, be tested regularly in staging environments and be clearly documented. It receives the RELEASE_ID of the previous release as an environment variable from the deploy job's artifacts and switches the current symlink back to that release. The cache is then flushed.

What the rollback job explicitly does not do: it does not undo database migrations. If a migration ran that is not compatible with the old code, a file rollback alone will not solve the problem. That is why database migrations must be developed following the expand contract pattern: new columns and tables are added without removing the old ones, so the old code keeps working. Only after several successful deployments with the new code are outdated database structures removed.

8. Common mistakes and their causes

The most common mistake in Magento CI/CD pipelines is running bin/magento setup:di:compile on the production server instead of in the build job. That causes several problems at once: the compile process takes several minutes, during which the shop is either in maintenance mode or running on old code. On top of that, the result may not be reproducible, because the server environment can differ from the build environment. DI compilation belongs exclusively in the build job on the runner.

Another frequent mistake concerns pub/static/: anyone who forgets to clean the static content directory before deployment risks mixing old CSS and JS files with new PHP templates. The correct pattern is: clean the directory in the build job, deploy static content and transfer the result as an artifact. Never run setup:static-content:deploy on the server if the directory has already been populated by the build artifact, that would overwrite the artifact's contents.

Mistake Symptom Cause Correct solution
DI compile on server Long outage, race conditions setup:di:compile in the deploy script Run only in the build job
Stale static content JS/CSS does not match PHP templates pub/static/ not cleaned Clean in the build job, use the artifact
Missing env.php Magento does not start Shared link not set Always link env.php from shared/
No rollback test Rollback fails when it actually matters Never rehearsed in staging Test rollback monthly in staging
Too many releases Disk full on the server RELEASE_RETENTION not set Run cleanup after deployment

9. Jobs compared: common variants

In practice, the same task often has several variants across different pipelines. Choosing the right variant depends on the concrete requirements: security, speed, reproducibility and maintainability. For Magento projects there are a few clear recommendations that follow directly from Magento's characteristics.

The most important principle remains: whatever has to run on the production server should be kept to a minimum. Every command that runs on the server extends the deployment window and increases the risk of failure. The goal is for the server to only switch the symlink, link the shared files and flush the cache. Everything else, Composer, npm, DI compile, static content, belongs in the build job on the runner.

10. Summary

The GitLab Magento deployment cheatsheet covers the key aspects of a safe and reproducible pipeline: variables with correct scopes and flags, a clear stage structure, a complete build job, a clean deploy job with symlink switching, the correct order of Magento commands, a verify job and a rehearsed rollback path. These building blocks are not optional, they are the minimum requirement for a production ready deployment system.

The most common mistakes do not come from not knowing the individual commands, but from a lack of clarity about the order and the split between the build job and server operations. Anyone who consistently maintains this separation ends up with a deployment process that is reproducible, traceable and quickly correctable if something fails. The rollback test in staging is just as important as the deploy test on production, it is the proof that the way back actually works.

GitLab Magento Deployment Cheatsheet: the essentials at a glance

Variables

SSH_PRIVATE_KEY, DEPLOY_HOST, COMPOSER_AUTH as Protected + Masked + Scoped. Non critical values directly in .gitlab-ci.yml.

Build job

Composer, npm, the Tailwind build and setup:di:compile exclusively in the build job. Never on the production server.

Deploy order

Transfer artifact → link shared files → switch symlink → run Magento commands → flush cache.

Rollback

Always manual, never automatic. RELEASE_ID from deploy artifacts, switch the symlink back, flush the cache. Test regularly in staging.

11. FAQ: GitLab Magento Deployment Cheatsheet

1Which variables are required?
SSH_PRIVATE_KEY, SSH_KNOWN_HOSTS, DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH, COMPOSER_AUTH and APP_ENV. All secrets Protected + Masked + Scoped.
2Why not DI compile on the server?
Takes minutes, not reproducible on the server. Run exclusively in the build job on the runner and transfer as an artifact.
3Order of Magento commands?
1. setup:upgrade (only if needed), 2. setup:static-content:deploy, 3. cache:flush. Static content before the cache flush, not the other way around.
4When to enable maintenance mode?
Only for incompatible DB changes. Not necessary for expand contract migrations. Use it as rarely as possible.
5How does the rollback work?
RELEASE_ID from deploy artifacts, switch the symlink back, flush the cache. Always triggered manually, never automatically. Test regularly in staging.
6What belongs in the build artifact?
vendor/, generated/, pub/static/. Everything from Composer, npm and setup:di:compile. Lifetime: 1 day, so old artifacts get cleaned up automatically.
7cache:flush vs. cache:clean?
cache:flush clears all backends completely. cache:clean invalidates specific types. Always use cache:flush after deployment for a clean restart.
8How many releases to keep?
At least 3, 5 recommended. Enough for rollbacks, not so many that disk space becomes a problem. The RELEASE_RETENTION variable controls the cleanup.
9What does the verify job check?
HTTP 200 on the homepage and login page, cache status via the Magento CLI. Optional: health endpoint for DB, Redis and Elasticsearch.
10static-content:deploy after the symlink switch?
Only if pub/static/ has not already been populated by the build artifact. Recommendation: deploy static content in the build job and transfer it as an artifact, faster and more reproducible.