from artifact to atomic release
Deployer PHP and GitLab CI/CD complement each other perfectly for Magento deployments: GitLab builds and tests, Deployer takes care of the release structure, the symlink switch and the rollback path. Teams that combine both tools correctly end up with a reproducible, team ready deployment process without maintenance window roulette.
Table of Contents
- 1. Why Deployer and GitLab CI belong together
- 2. Division of labor: what GitLab does, what Deployer does
- 3. The GitLab pipeline as the entry point
- 4. Deployer configuration for Magento
- 5. Release structure and shared directories
- 6. Magento specific deploy steps
- 7. Verify job after the symlink switch
- 8. Rollback strategy with Deployer
- 9. Direct tool comparison
- 10. Summary
- 11. FAQ
1. Why Deployer and GitLab CI belong together
Anyone who wants to run Magento deployments professionally quickly runs into a dividing line: GitLab CI/CD excels at running builds, kicking off tests and producing artifacts. For the actual server orchestration, creating release directories, switching symlinks atomically, managing shared paths and rolling back within seconds, a specialized deploy tool such as Deployer is a better fit. Combining both tools creates a clean separation of responsibilities and makes every part of the process replaceable without breaking the other.
In practice you often see either GitLab CI alone with long Bash blocks in the YAML, or Deployer alone, triggered locally from a developer machine. Both approaches have weaknesses: the first becomes unreadable and hard to maintain, the second is not reproducible and escapes pipeline control. Combining them brings the best of both worlds together and creates a process that works for the whole team.
For Magento this approach is especially valuable because the system requires many steps that must run in exactly the right order: Composer installation, DI compilation, static content deployment, symlink operations for shared files, setup upgrade and cache flush. Deployer knows this order, logs every step, and can fall back to the previous release on failure before any user notices anything.
2. Division of labor: what GitLab does, what Deployer does
The division of labor between GitLab CI and Deployer is clear and should not be blurred. GitLab CI/CD takes care of everything tied to the repository state: checking out code, installing Composer packages, loading NPM packages, building frontend assets with Tailwind, triggering DI compilation, running PHPStan and PHPUnit, and storing the result as an artifact. At the end of the CI phase there is a complete, built Magento directory that no longer needs to be changed.
Deployer takes over from that point: it receives the artifact, creates a new release directory on the target server, syncs the build there, sets up the shared symlinks for env.php, pub/media and var/log, runs setup:upgrade, deploys static content if needed, flushes the cache and switches the symlink to the new release. This symlink switch is atomic from the web server's point of view, which is what makes it the core of the zero downtime model.
3. The GitLab pipeline as the entry point
The GitLab pipeline controls when, and for which branches or tags, a deploy is triggered. For production systems the deploy job should run exclusively on protected tags or the main branch, and it should require manual approval. That prevents every merge from automatically landing on production. The pipeline configuration passes all necessary variables to the Deployer call as environment variables that were previously stored in the GitLab CI/CD settings.
# .gitlab-ci.yml - Build and deploy Magento via Deployer
stages:
- build
- test
- deploy
- verify
variables:
COMPOSER_CACHE_DIR: ".cache/composer"
NPM_CONFIG_CACHE: ".cache/npm"
build:magento:
stage: build
image: php:8.4-cli
cache:
key: composer-${CI_COMMIT_REF_SLUG}
paths:
- .cache/composer/
script:
- apt-get update -qq && apt-get install -y -qq git unzip nodejs npm
- composer install --no-dev --prefer-dist --no-interaction --quiet
- npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
- npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
- php bin/magento setup:di:compile
artifacts:
paths:
- vendor/
- generated/
- pub/static/
expire_in: 2 hours
deploy:production:
stage: deploy
image: php:8.4-cli
when: manual
only:
- tags
environment:
name: production
url: https://shop.example.com
script:
- apt-get update -qq && apt-get install -y -qq openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- vendor/bin/dep deploy production --no-interaction
4. Deployer configuration for Magento
The Deployer configuration file deploy.php at the repository root describes the target server, the release settings and the task order. Deployer ships a default configuration for Magento that can be extended through recipes. In day to day project work it pays off to maintain a dedicated base configuration that names the Magento specific steps clearly and leaves nothing implicit. Particularly important: the list of shared files and directories that are shared between releases.
The number of retained releases should be at least five for production systems, so that several steps can be rolled back in case of failure. Deployer automatically deletes older releases after a successful deployment, keeping disk usage under control.
# deploy.php excerpt - Deployer configuration for Magento 2
# Keep 5 releases for rollback safety
set('keep_releases', 5);
set('shared_files', ['app/etc/env.php', 'app/etc/config.php']);
set('shared_dirs', ['pub/media', 'var/log', 'var/session', 'var/cache']);
set('writable_dirs', ['pub/media', 'var', 'pub/static', 'generated']);
# Host definition - reads secrets from environment variables
host('production')
->setHostname(getenv('DEPLOY_HOST'))
->setRemoteUser(getenv('DEPLOY_USER'))
->setDeployPath(getenv('DEPLOY_PATH'))
->set('branch', getenv('CI_COMMIT_TAG') ?: 'main');
# Custom task: flush Magento cache after symlink switch
task('magento:cache:flush', function () {
run('cd {{release_path}} && php bin/magento cache:flush');
});
# Define the deployment sequence
after('deploy:symlink', 'magento:cache:flush');
after('deploy:failed', 'deploy:unlock');
5. Release structure and shared directories
The release structure that Deployer creates on the server is the heart of the zero downtime model. Every deployment produces a new directory under releases/, named with a timestamp. Only after all deploy steps have completed successfully is the current symlink switched to the new release. The web server always points to current and never notices the switch as downtime, as long as no long synchronous database migrations block the process.
The shared directories live under shared/ and are mounted into every release as a symlink. That means app/etc/env.php exists exactly once on each server and is never overwritten or recreated with every release. The same applies to pub/media, var/log and var/session. This separation between release specific and shared data is one of the most important design decisions in the entire deployment process.
6. Magento specific deploy steps
After syncing the build, Magento requires several steps that must run in the correct order. Setup upgrade may only run once the shared symlinks are in place, because the command depends on app/etc/env.php. Static content deployment should happen during the build step of the CI pipeline, with the output carried along as an artifact, so this time consuming step does not extend the actual deploy phase. Cache flush must happen after the symlink switch so the new release becomes visible.
For teams that want to avoid maintenance mode, the order is decisive: first prepare the new release, then switch the symlink atomically, then flush the cache. Setup upgrade with database migrations is the only phase that potentially requires a brief maintenance mode, but only if the migrations are not backward compatible. This point can be explored further in the section on database strategies (expand/contract).
7. Verify job after the symlink switch
A deployment without verification is a bet. The verify job in the GitLab pipeline checks immediately after the symlink switch whether the new release is reachable and functioning. At minimum, an HTTP health check and a Magento cache status check should run. Teams that need more confidence add smoke tests for critical paths such as the homepage, category page and checkout. The verify job runs with when: on_success after the deploy job and decides whether the deployment counts as successful or whether a rollback should be triggered.
# Verify job - runs immediately after successful deploy
verify:production:
stage: verify
image: alpine:3.19
when: on_success
needs: ["deploy:production"]
script:
- apk add --no-cache curl openssh-client
# HTTP health check - expects 200 OK
- curl --fail --silent --max-time 15 https://shop.example.com/health
# Homepage must return HTTP 200
- curl --fail --silent --max-time 20 --output /dev/null https://shop.example.com/
# Check Magento cache status via SSH
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- ssh "$DEPLOY_USER@$DEPLOY_HOST"
"cd $DEPLOY_PATH/current && php bin/magento cache:status"
allow_failure: false
8. Rollback strategy with Deployer
Deployer makes rollback trivial: the command dep rollback production switches the current symlink back to the last successful release. That takes seconds instead of minutes because no files need to be copied. In case of failure, when the verify job fails, the rollback job should run automatically as a downstream job in the pipeline. That way rollback is not a manual emergency procedure but a defined step of the pipeline.
Important: Deployer only keeps the rollback path within the configured number of retained releases. If keep_releases is set to five and six deployments happen back to back, the oldest release has already been deleted. For production systems the value should be at least five, so that a stable starting point remains reachable even after several quick fixes. Database migrations that are not backward compatible must be treated separately, since a file rollback alone is not enough in that case.
9. Direct tool comparison
For teams still weighing different approaches, a direct comparison of the key dimensions is worthwhile. The decision depends heavily on the team context, the server infrastructure and the desired degree of automation.
| Criterion | GitLab CI only (Bash) | GitLab CI + Deployer | Advantage |
|---|---|---|---|
| Release management | Manual in Bash blocks | Automatic via Deployer | Fewer error sources, readable config |
| Rollback speed | Minutes (manual) | Seconds (dep rollback) | Atomic symlink switch |
| Shared path management | Custom symlink logic | Declarative in deploy.php | No forgotten shared links |
| Multi server support | Custom SSH loops | Parallel hosts in Deployer | Scales without duplicated code |
| Learning curve | Low (Bash only) | Moderate (PHP + Deployer API) | One time investment |
The combination of GitLab CI and Deployer is recommended for teams that run more than one server, deploy regularly and need a reliable rollback path. Teams that deploy to a single server with only a few deployments a month can start with a cleanly structured Bash solution and migrate to Deployer later once complexity grows.
Mironsoft
GitLab CI/CD, Deployer and Magento deployment infrastructure
Need Deployer and GitLab CI set up cleanly for Magento?
We help combine GitLab CI/CD and Deployer PHP so that build, release structure, verify jobs and rollback path for Magento come together as a reliable process.
Pipeline design
Build, test, deploy and verify as clear stages with clean handoffs
Deployer configuration
Shared paths, release rotation and Magento tasks defined cleanly
Rollback safety net
Automatic rollback on verify failure, without manual intervention
10. Summary
The combination of Deployer PHP and GitLab CI/CD gives Magento projects a deployment process that is reproducible, team friendly and rollback capable. GitLab builds the artifact and drives the pipeline logic. Deployer handles server side release management: creating release directories, setting shared symlinks, running Magento tasks and switching the symlink atomically. The verify job checks the result, and the rollback job can restore the previous state within seconds if something goes wrong.
The most important design principle remains: nothing gets built on the production server. The artifact is produced in a controlled way in the CI environment and transferred to the server as an immutable state. Deployer only handles switching between states, not creating them. This separation is the key to a deployment process that stays manageable even under time pressure.
Deployer + GitLab CI for Magento, the essentials at a glance
Separation of duties
GitLab builds and tests, Deployer manages releases and switches symlinks. No mixing of responsibilities.
Rollback in seconds
dep rollback production switches the current symlink to the last stable release. No copying, no waiting.
Shared paths
env.php, pub/media, var/log and var/session are declared in deploy.php and mounted into every release as symlinks.
Verify job is mandatory
HTTP health check and Magento cache status after the symlink switch. No deployment is complete without verification.