Designing Pipeline Stages That Make Sense: Build, Test, Package, Deploy, Verify, Rollback
AI generated
CI/CD
.yml
GitLab · CI/CD · Magento · Pipeline Design
Designing Pipeline Stages That Make Sense
Build, Test, Package, Deploy, Verify, Rollback

A GitLab pipeline made up of a single long script block is not a CI/CD process, it is automated chaos. Only once Build, Test, Package, Deploy, Verify and Rollback are modeled as distinct stages does a pipeline become traceable, rollback capable and workable for a whole team.

12 min read stages · needs · artifacts · when · rules GitLab 16+ · Magento 2.4 · PHP 8.4

1. Why pipeline stages are not cosmetic

The stages directive in a .gitlab-ci.yml file is the first place where a team makes the logic of its deployment process visible. Anyone who crams every step into a single job loses the ability to rerun individual phases, debug them in isolation, or run them in parallel. Stages are not organizational decoration, they are the backbone of a traceable release process.

For Magento projects, this separation matters even more. Building a Magento shop involves Composer, DI compilation, a Node build and static content, none of which have anything to do with the actual deployment to the target server. When these steps are separated from the deployment steps, the same artifact can be rolled out to staging and production without rebuilding it. That is the core idea behind reproducible deployments.

A well designed stage chain also makes it clear what should happen when a step fails. If the test job fails, no deploy runs. If the verify job fails, the rollback stage is ready to go. That logic is readable and extensible in a pipeline with explicit stages, whereas in a monolithic script block it stays hidden and fragile.

2. Build stage: the reproducible foundation

The build stage has exactly one job: turning the source code into a complete, deployable artifact. For Magento projects that means composer install --no-dev, setup:di:compile, installing Node packages and running the Tailwind build. The result is stored as a GitLab artifact and reused by every subsequent job, identically on staging and production.

The critical point is that the build stage needs no connection to the production server. It runs entirely inside the runner, with no SSH access, no database connection and no knowledge of the target environment. That makes it testable, repeatable and independent. Anyone who already touches the server during the build stage is mixing two phases that need to stay conceptually separate.

stages:
  - build
  - test
  - package
  - deploy
  - verify
  - rollback

variables:
  GIT_STRATEGY: fetch
  COMPOSER_CACHE_DIR: .cache/composer
  NPM_CONFIG_CACHE: .cache/npm

# Build stage: produce deployment artifact, no server access needed
build:magento:
  stage: build
  image: php:8.4-cli
  cache:
    key: composer-$CI_COMMIT_REF_SLUG
    paths:
      - .cache/composer/
      - .cache/npm/
  script:
    - composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
    - 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/frontend/
    expire_in: 2 hours
    when: on_success

3. Test stage: the quality gate before packaging

The test stage is the quality gate between build and deployment. This is where PHPStan, PHPUnit, PHPCS and security checks run, using the artifacts from the build stage as their basis. A job that fails in the test stage automatically prevents a broken artifact from ever being deployed. That is the value of a quality gate: not just finding problems, but blocking the process until they are fixed.

In Magento projects it pays off to split the test stage into parallel jobs: one for static analysis, one for unit tests, one for coding standards. With needs, these jobs can start at the same time as soon as the build job has finished. That shortens the overall pipeline runtime considerably without breaking the logic of the stages. Only once every test job succeeds is the package stage allowed to begin.

4. Package stage: bundling and signing artifacts

The package stage turns the built artifact into a transportable package. For Magento deployments using SSH and rsync, the package is typically the whole release directory, transferred to the target server via rsync. Alternatively, a tar.gz archive with a checksum can be produced, allowing the integrity of the package to be verified before it is unpacked on the server.

The package stage is also the right place for release metadata: the build ID, the commit hash, the branch name and the timestamp. This information is written into a release.json file that is deployed together with the package. That way the server always shows which pipeline run produced which release, without anyone having to look it up in GitLab.

5. Deploy stage: an atomic release switch

The deploy stage transfers the artifact to the target server and performs the symlink switch. In a clean release structure with releases/, current and shared/, the actual downtime critical moment is reduced to a single ln -sfn command. Everything before that, rsync, symlinks for shared files, Magento setup steps, happens inside the new release directory while the active current directory stays untouched.

For production deployments, the deploy stage should always be protected with when: manual or an explicit tag rule. That prevents an accidental merge into main from immediately triggering a production deploy. Staging deployments, by contrast, can run automatically on every merge into the develop branch, a pattern that enables fast feedback cycles without putting production at risk.

# Deploy stage: transfer artifact and switch symlink atomically
deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://shop.example.com
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
      when: manual
  before_script:
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
    - mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
  script:
    - RELEASE_ID="$(date +%Y%m%d-%H%M%S)-${CI_COMMIT_SHORT_SHA}"
    - 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}/"
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        set -euo pipefail &&
        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 &&
        cd ${RELEASE_PATH} &&
        php bin/magento setup:upgrade --keep-generated &&
        php bin/magento cache:flush &&
        ln -sfn ${RELEASE_PATH} ${DEPLOY_PATH}/current"

6. Verify stage: the functional close of the pipeline

The verify stage is the ending that pipelines most often forget. Technically, the deployment is finished once the symlink switch has happened, functionally it is not. A deployment is only truly successful once the health endpoint responds, the homepage loads, the cache status is correct and critical API routes react as expected. Moving these checks into a dedicated verify job makes them repeatable and logged.

A verify job that fails should automatically trigger the rollback stage, or at least raise an alert. That closes the gap between a technically deployed release and one that actually works. For Magento shops, these checks can be implemented with curl -f, bin/magento cache:status and simple HTTP assertions, with no external test frameworks required.

7. Rollback stage: a rehearsed way back

A rollback stage that only gets invented once something breaks is not a rollback, it is improvisation. Every production pipeline should have a rollback stage in place, even if it rarely runs. For Magento with a release structure, the mechanism is simple: the current symlink is pointed back at the previous release directory, the cache is cleared and the verify job runs again. That takes seconds and requires no rebuild.

In GitLab, the rollback stage can be implemented with when: manual and a variable for the target release. If something goes wrong, the team decides which release gets reactivated, and the pipeline carries out the switch. Important: the rollback job must assume the same SSH credentials and the same release structure as the deploy job in order to work reliably.

# Verify stage: confirm deployment is functionally complete
verify:production:
  stage: verify
  needs: ["deploy:production"]
  script:
    - curl -f --retry 5 --retry-delay 3 https://shop.example.com/health
    - curl -f https://shop.example.com/
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}"
        "cd ${DEPLOY_PATH}/current && php bin/magento cache:status | grep -v 'disabled'"
  when: on_success

# Rollback stage: reactivate previous release via symlink, no rebuild needed
rollback:production:
  stage: rollback
  rules:
    - if: '$CI_PIPELINE_SOURCE == "web"'
      when: manual
  script:
    - ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        set -euo pipefail &&
        PREV=\$(ls -1t ${DEPLOY_PATH}/releases/ | sed -n '2p') &&
        ln -sfn ${DEPLOY_PATH}/releases/\${PREV} ${DEPLOY_PATH}/current &&
        cd ${DEPLOY_PATH}/current &&
        php bin/magento cache:flush"

8. Controlling stage dependencies with needs

The needs keyword in GitLab CI makes it possible to break out of the strict sequential order of stages and define a DAG (Directed Acyclic Graph) instead. A test job can start right after the build job without waiting for other jobs in the same stage. That shortens the pipeline runtime considerably, because parallel work becomes possible without giving up the logical order.

A typical use case in Magento pipelines: PHPStan, PHPUnit and PHPCS all start at the same time as soon as the build artifact is available. The package stage only starts once all three test jobs have finished successfully. Using needs and dependencies, this graph can be modeled explicitly without giving up stage barriers. The result is faster pipelines without losing any control structure.

9. Stages compared side by side

The choice of stage design has a direct effect on the speed, traceability and safety of the deployment process. The table below compares common antipatterns with the recommended stage patterns.

Stage Decision Antipattern Recommended Pattern Benefit
Separate build and deploy composer install on the server Build in the runner, deploy the artifact Reproducible, no network access needed on production
Test blocking Tests after deploy Test stage before deploy Failures block the deployment path
Production protection Auto deploy on every push when: manual + tag rule No accidental production deploy
Verify step Pipeline ends after deploy Verify stage with curl + cache:status Functional close instead of a purely technical one
Rollback Improvised when something breaks Rollback stage with when: manual A rehearsed, documented way back

The antipatterns in the table are not theoretical constructs, they show up regularly in real Magento projects. The common denominator: they emerge when a pipeline gets thrown together quickly and never gets structurally revisited. The recommended stage design costs a bit more planning up front, but it pays off with every deployment, and even more so with every incident.

10. Summary

A pipeline with sensibly designed stages is not a luxury reserved for large teams, it is the minimum requirement for a professional Magento deployment process. Build, Test, Package, Deploy, Verify and Rollback are not arbitrary labels, they describe the logical order of a deployment in which every phase has a clear responsibility and can fail without damaging the phases that follow.

The single biggest win is separating build and deploy: an artifact gets built once and rolled out to multiple environments. The second biggest win is the verify stage: the pipeline does not end after the symlink switch, it only ends once functionality has been confirmed. And the rollback stage ensures that the way back is not an emergency improvisation, but a deliberately designed part of the process.

Designing Pipeline Stages That Make Sense: The Essentials At a Glance

Build vs. Deploy

Build inside the runner with no server access. Produce the artifact once, deploy it identically to staging and production.

Test Gate

Test stage before deploy. PHPStan, PHPUnit and PHPCS run in parallel with needs. If one job fails, no deploy happens.

Verify Close

Verify stage after deploy with curl and cache:status. The pipeline only counts as successful once the application actually responds.

Rollback Readiness

Rollback stage with when: manual. Symlink back to the previous release, no rebuild, no stress test during an incident.

11. FAQ: Designing Pipeline Stages That Make Sense

1How many stages does a Magento pipeline need at a minimum?
At least four: Build, Test, Deploy and Verify. Package and Rollback get added once multiple environments exist and a documented way back is needed.
2Why should build and deploy be separated?
An artifact built once can be deployed identically to staging and production. Builds on the server are slower, harder to reproduce and unnecessarily require network access on the production machine.
3What belongs in the verify stage?
An HTTP check against the health endpoint, a request to the homepage, a cache status check. Not full end to end tests, those belong in the test stage before deploy.
4How do I prevent accidental production deployments?
With rules and when: manual, or tag based rules. Limit production deploys to semantic tags and secure them further with environment scopes.
5What is the difference between stages and needs?
stages defines sequential phase barriers. needs defines fine grained job dependencies. With needs, jobs start as soon as their direct predecessors are done, regardless of stage boundaries.
6How long should artifacts be kept?
Build artifacts within the pipeline: 2 to 4 hours. Release packages for rollbacks: at least 7 days, or the last 5 releases kept as directories on the server.
7Can the rollback stage be triggered automatically on a verify failure?
Technically yes, with when: on_failure. But an automatic rollback is not always safe, an ongoing database migration can turn a rollback into an inconsistent state. Manual is often the safer choice.
8Should static content be generated in build or in deploy?
In build, as part of the artifact. Build it in advance for all known locales and carry it along as part of the artifact, which removes the time consuming static content deploy on the production server.
9How do I safely test pipeline changes?
Through a feature branch with a staging environment. Lock production jobs behind tag rules or environment scopes until the pipeline has been fully validated.
10What is the most common mistake in stage design?
Missing a verify stage. The pipeline ends after deploy and counts as successful, even though the application might not actually be responding.