The Complete GitLab Workflow for Magento
Repository governance, build artifacts, release directories, symlink switching, verify jobs and rollback as one closed, traceable process, with every building block explained in context.
Table of Contents
- 1. Why seeing the whole picture helps so much
- 2. Repository foundation: branches, tags and protection rules
- 3. CI/CD variables as a configuration contract
- 4. Pipeline stages: order that carries meaning
- 5. Build stage: artifacts instead of server state
- 6. Release structure on the target server
- 7. Deploy: transfer the artifact, switch the symlink
- 8. Verify: health checks after the switch
- 9. Improvised versus reproducible: a direct comparison
- 10. Summary
- 11. FAQ
1. Why seeing the whole picture helps so much
Magento deployments rarely fail because of one single missing step. They fail because the individual steps are known but were never thought through as one connected system. One team knows the build job, another knows the release folders, and nobody has ever really rehearsed the rollback script. The complete GitLab workflow for Magento therefore needs to be presented as a whole, not as a collection of isolated configuration snippets.
The whole picture starts with repository governance: which branches are protected? Who is allowed to deploy to production? How are tags assigned for releases? Only once these questions are answered can a pipeline be built on top of them. The pipeline itself is then not an end in itself, but the mechanism that translates a defined approval into a reproducible deployment. Every step, build, package, deploy, verify, rollback, has a clearly defined responsibility. This chapter shows how those responsibilities work together.
The strength of the GitLab workflow for Magento does not lie in complex individual configurations, but in the consistency of the overall process. A deployment that was rehearsed on staging should run on production with the same variables, the same scripts and the same release structure. That is zero downtime not as a technical feature, but as organizational discipline.
2. Repository foundation: branches, tags and protection rules
The first building block of the complete workflow is the repository itself. Without protected branches, every pipeline runs on shaky ground. The main branch must be protected: no direct pushes, all changes go through merge requests, and approval is required from at least one other team member. The release/* branch is used for release candidates whose pipeline must be fully green before the deploy is approved.
Tags follow the v* scheme and are also protected. A production deploy is triggered exclusively by a signed, approved tag, never by a branch push. That prevents unintended commits from reaching production directly. The relationship between branch, tag, pipeline and deployment target must be explicitly documented in the project so new team members can follow it.
3. CI/CD variables as a configuration contract
Variables are the configuration contract of the pipeline. They separate what the code does from where it gets deployed. The most important variables for a Magento GitLab workflow are SSH_PRIVATE_KEY, SSH_KNOWN_HOSTS, DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH and COMPOSER_AUTH. Each of these variables must carry the correct environment scope, so staging secrets never end up in production jobs and vice versa.
In addition to the deployment secrets there are process variables such as RELEASE_RETENTION, which controls how many old release directories are kept on the server. A value of 5 is sufficient for most projects and allows a quick rollback to the last five releases. Pipeline level variables such as GIT_STRATEGY: fetch and COMPOSER_CACHE_DIR control the behavior of the GitLab runner and Composer caching globally and do not need to be set individually in every job.
# .gitlab-ci.yml: Global pipeline configuration for Magento deployment
stages:
- build
- test
- package
- deploy
- verify
- rollback
variables:
GIT_STRATEGY: fetch
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm"
RELEASE_RETENTION: "5"
# Cache Composer and npm dependencies across pipeline runs
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .cache/composer/
- .cache/npm/
4. Pipeline stages: order that carries meaning
The stages of a Magento pipeline are not cosmetic, they are the functional order of the deployment process. Build produces the artifact: Composer dependencies, frontend assets, DI compilation. Test checks the artifact for correctness: PHPStan, PHPCS, unit tests. Package bundles the verified artifact and prepares it for transfer. Deploy transfers the artifact to the server and switches the symlink. Verify checks whether the new release runs correctly. Rollback exists as a manual job for emergencies.
Every stage may only start once the previous one has completed successfully. That is the basic rule of the pipeline cascade. A deploy that happens without a preceding green test run is no longer a controlled deploy. The stages enforce this order mechanically and take the burden of manual discipline off the team. That is the difference between a pipeline as a tool and a pipeline as a guarantee of process.
5. Build stage: artifacts instead of server state
The build job is the most critical step in the entire workflow. It has to resolve all dependencies, run the frontend build and perform Magento's DI compilation step, all inside an isolated environment with no access to the production server. The result is an artifact that contains the complete, reproducible state of the release.
Artifacts are declared in GitLab using the artifacts directive. The paths vendor/, generated/ and pub/static/ are the minimum requirements for a Magento artifact. The artifact's expiry time should be set to at least one day, so the deploy job, which runs in a later stage, still has access to it. A build job that runs on the production server is no longer a build job, it is a maintenance step with uncontrolled side effects.
# Build job: produces the Magento release artifact
build:magento:
stage: build
image: php:8.4-cli
before_script:
- apt-get update -qq && apt-get install -y -qq git unzip curl nodejs npm
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
script:
# Install PHP dependencies without dev packages
- composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
# Install and build frontend assets
- npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
- npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
# Compile Magento dependency injection
- php bin/magento setup:di:compile
# Deploy static content for English locale
- php bin/magento setup:static-content:deploy en_US -f --jobs=4
artifacts:
name: "magento-release-$CI_COMMIT_SHORT_SHA"
paths:
- vendor/
- generated/
- pub/static/
- app/etc/config.php
expire_in: 2 days
only:
- tags
- main
6. Release structure on the target server
The release structure on the target server is the foundation of zero downtime. It consists of three directories: releases/ holds all versioned releases as complete directories. shared/ holds files that are shared across releases: app/etc/env.php, pub/media/, var/log/, var/session/. current is a symlink pointing to the active release.
The atomic symlink switch is the core of the zero downtime approach. With ln -sfn /releases/NEW_RELEASE /current, the active code changes in a single, atomic system call. Running PHP-FPM processes keep their file handles on the old files, while new requests are served from the new release immediately. This principle only works if the release structure has been set up cleanly in advance and the shared paths are linked correctly. A server without this structure cannot perform a safe symlink switch.
7. Deploy: transfer the artifact, switch the symlink
The deploy job transfers the built artifact to the target server and runs the Magento specific release steps. The transfer uses rsync, which only transfers changed files and so minimizes network traffic. After the transfer, the shared paths are linked, the Magento configuration is pulled in from the shared/ directory, and the symlink is pointed at the new release.
The order of the Magento steps within the deploy is not arbitrary. setup:upgrade must run before cache:flush, since the upgrade invalidates the cache. Maintenance mode should be kept as short as possible, ideally only for the duration of the setup:upgrade call. For deployments without database migrations, maintenance mode can be skipped entirely, which maximizes the zero downtime approach. The symlink switch itself is the only step that runs with absolutely no downtime.
# Deploy job: transfers artifact and switches symlink on production server
deploy:production:
stage: deploy
environment:
name: production
url: https://shop.mironsoft.de
script:
# Setup SSH authentication
- 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
# Create release directory and transfer artifact
- RELEASE_ID=$(date +%Y%m%d-%H%M%S)
- RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p $RELEASE_PATH"
- rsync -az --delete --exclude='.git' ./ "$DEPLOY_USER@$DEPLOY_HOST:$RELEASE_PATH/"
# Link shared files and switch symlink
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -s << 'SSH'
set -euo pipefail
RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
# Link environment-specific files from shared directory
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"
# Switch symlink atomically
ln -sfn "$RELEASE_PATH" "$DEPLOY_PATH/current"
# Post-deploy Magento steps
cd "$DEPLOY_PATH/current"
bin/magento cache:flush
# Cleanup old releases, keep last 5
ls -dt "$DEPLOY_PATH/releases"/*/ | tail -n +6 | xargs rm -rf
SSH
when: manual
only:
- tags
8. Verify: health checks after the switch
A deployment without verification is not a finished pipeline. The verify job checks, after the symlink switch, whether the new release is responding correctly. An HTTP health check against the /health endpoint confirms that the web server is answering. A smoke test against the homepage checks that Magento is not returning a 500 error. A cache status check over SSH makes sure the cache was rebuilt correctly after the flush.
If the verify job fails, the pipeline automatically triggers the rollback path. Rollback is not an unplanned extra step, it is part of the normal pipeline design. It has to be maintained just as carefully as the deploy job itself: tested scripts, a defined order, clear output. A rollback that gets executed for the first time under pressure is not a real rollback. The pipeline forces it to be prepared and tested in advance.
9. Improvised versus reproducible: a direct comparison
The difference between an improvised and a reproducible deployment process shows up in every incident. Improvised processes depend on undocumented assumptions about the server: which PHP version is installed? Which Composer version? Which Node version? A reproducible process answers these questions through the build job itself, which runs in a controlled Docker container with explicitly defined tool versions.
The table below shows the key differences between the two approaches at a glance. It makes clear why investing in a complete workflow, even though it requires more upfront effort, fundamentally improves operational stability.
| Aspect | Improvised | Reproducible (GitLab workflow) | Benefit |
|---|---|---|---|
| Build location | Directly on production | CI container, isolated | No side effects on the live system |
| Rollback | Manual, improvised | Symlink to old release | Seconds instead of hours |
| Secrets | Hardcoded or handled manually | GitLab variables with scope | Separated by environment, auditable |
| Verification | None or manual | Automatic verify job | Errors caught immediately |
| Downtime | Uncontrolled, variable | Only during DB migrations | Plannable maintenance window |
In quiet periods, an improvised process often looks exactly like a reproducible one. The difference shows up during an incident: when the wrong commit was deployed, when the backup is missing, when there is no documented rollback path. A complete GitLab workflow for Magento prepares the team for exactly these situations before they happen.
10. Summary
The complete GitLab workflow for Magento, from zero to zero downtime, is not a single feature, it is the result of consistently well thought out individual decisions: protected branches prevent uncontrolled deploys. Build jobs in isolated containers produce reproducible artifacts. Release directories with symlink switching allow atomic cutovers. Verify jobs check the new release immediately after the switch. Rollback scripts are prepared and tested, not improvised.
Zero downtime is the natural result of this process, not its goal. Anyone who implements every building block correctly automatically ends up with a deployment infrastructure that minimizes incidents, enables rollbacks, and gives teams the confidence to act in a controlled way even under pressure.
GitLab Workflow for Magento: The Essentials at a Glance
Repository foundation
Protected branches, protected tags, mandatory merge requests. Without this governance, every pipeline is only half secured.
Build artifact
Composer, DI compilation, frontend build in the CI container, never on production. The artifact is built once and deployed many times.
Symlink switch
Release directories with an atomic symlink switch are the basis of zero downtime and fast rollback.
Verify & rollback
No deployment without an automatic health check afterward. Rollback is not a special case, it is part of the normal process design.
11. FAQ: GitLab Workflow for Magento
1What is the most important step?
ln -sfn /releases/NEW /current. It is the only step with no downtime and forms the basis for a fast rollback.2Do I always need maintenance mode?
3How many releases should I keep?
4What belongs in shared/?
5How do I keep staging secrets away from production?
6When does the verify job trigger a rollback?
when: on_failure so it can run immediately.