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.
Table of Contents
- 1. Required variables for every Magento pipeline
- 2. Stages and what they mean
- 3. The build job: what it has to accomplish
- 4. The deploy job: getting the artifact onto the server
- 5. Magento commands in the correct order
- 6. Verify job: confirming the deployment
- 7. Rollback: the rehearsed way back
- 8. Common mistakes and their causes
- 9. Jobs compared: common variants
- 10. Summary
- 11. FAQ
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.