Combining Deployer with GitLab CI for Magento the Right Way
AI generated
CI/CD
.yml
GitLab · Deployer PHP · Magento · CI/CD
Combining Deployer with GitLab CI for Magento the Right Way
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.

15 min read Deployer · GitLab CI · release structure · rollback Magento 2.4 · PHP 8.4 · GitLab 17+

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.

11. FAQ: Deployer with GitLab CI for Magento

1Can I use Deployer without GitLab CI?
Yes, Deployer can be started directly from the command line. Combining it with GitLab brings pipeline control, artifact management and team friendly approval processes.
2What happens to database migrations during a rollback?
Deployer only rolls back files. Migrations that are not backward compatible must be handled separately. The expand/contract pattern is recommended.
3How many releases should I keep?
At least five for production systems. Allows several rollback steps in a row. More than ten only with sufficient storage space.
4Does the GitLab runner need PHP installed?
Yes, Deployer is a PHP tool. A Docker image with PHP and Deployer preinstalled is the cleanest solution for the deploy runner.
5How do I manage env.php with Deployer?
Configured as a shared file in deploy.php. Exists once under shared/app/etc/env.php, mounted into every release via symlink.
6Can Deployer deploy to several servers at once?
Yes. Deployer supports multiple hosts and parallel deployments. Each host can have its own or a shared task sequence.
7Avoiding redeploying static content?
Build it as a CI artifact and sync it with the release. No need to run setup:static-content:deploy again on the server.
8dep deploy versus dep rollback: what is the difference?
dep deploy builds a new release with all tasks. dep rollback only switches the current symlink to the previous release, without any build steps.
9Where does the Deployer command actually run?
On the GitLab runner, not on the target server. Deployer connects to the target server via SSH and runs server side commands there.
10Testing Deployer changes before production?
With a dedicated staging host in Deployer and a separate pipeline stage. Success on staging unlocks the manual approval for production.