GitLab CI/CDGitLab CI/CDMulti-Stage Pipelines

Article & Tutorial

GitLab CI/CD Multi-Stage Pipeline Examples: Build, Test, Package, and Deploy

Learn GitLab CI/CD multi-stage pipeline examples with build, test, package, deploy, artifacts, cache, needs, parallel jobs, manual deployments, and best practices.

Multi-stage CI/CD pipeline moving through build, test, package, and deployment gates
Multi-stage CI/CD pipeline moving through build, test, package, and deployment gates
Contents

GitLab CI/CD Multi-Stage Pipeline Examples

Learn how to design practical GitLab CI/CD pipelines with multiple stages such as build, test, security, package, and deploy. This guide includes beginner examples, artifact handoff, parallel jobs, needs , manual deployment, and real-world best practices.

Table of contents

  • Quick answer
  • What is a multi-stage pipeline?
  • Basic build-test-deploy example
  • Passing files between stages
  • Parallel jobs and needs
  • Node.js multi-stage example
  • Docker image pipeline example
  • Manual deployment example
  • Common mistakes
  • Best practices
  • Related Melochip guides

Quick answer

Stages define order

Use stages to define high-level flow such as build, test, package, and deploy.

Jobs do the work

Each job belongs to a stage and runs scripts using a GitLab Runner.

Artifacts pass output

Use artifacts when one stage creates files needed by another stage.

needs speeds pipelines

Use needs so jobs can start as soon as required jobs finish.

What is a GitLab multi-stage pipeline?

A multi-stage pipeline is a CI/CD workflow split into logical stages. Each stage groups related jobs. For example, a typical application pipeline might use:

  • build — compile code or prepare dependencies
  • test — run unit tests, lint checks, and security scans
  • package — create build output, artifacts, or Docker images
  • deploy — deploy to development, staging, or production

GitLab runs stages in the order you define. Jobs inside the same stage can run in parallel if runners are available.

Basic build-test-deploy example

stages: - build - test - deploy build_app: stage: build script: - echo "Building application" test_app: stage: test script: - echo "Running tests" deploy_app: stage: deploy script: - echo "Deploying application"

This is the simplest multi-stage structure. The deploy stage runs only after the build and test stages complete successfully.

Passing files between stages with artifacts

When a build job creates files that a later job needs, use artifacts. For example, a build job can create a dist/ folder, and the deploy job can use it later.

stages: - build - deploy build_app: stage: build script: - mkdir -p dist - echo "Build output" > dist/index.html artifacts: paths: - dist/ expire_in: 1 week deploy_app: stage: deploy script: - echo "Deploying files" - ls dist/

Parallel jobs and needs

Jobs in the same stage can run in parallel. This is useful when unit tests, lint checks, and security scans do not depend on each other.

stages: - build - test - package build_app: stage: build script: - echo "Building app" artifacts: paths: - dist/ unit_tests: stage: test script: - echo "Running unit tests" lint_code: stage: test script: - echo "Running lint" package_app: stage: package needs: - job: build_app artifacts: true - unit_tests - lint_code script: - echo "Packaging app" - ls dist/

The needs keyword lets GitLab understand the direct dependencies between jobs. This can make pipelines faster because jobs do not always need to wait for every job in a previous stage.

Node.js multi-stage pipeline example

image: node:20 stages: - install - test - build - deploy cache: key: files: - package-lock.json paths: - .npm/ install_dependencies: stage: install script: - npm ci --cache .npm --prefer-offline run_tests: stage: test script: - npm test lint_code: stage: test script: - npm run lint build_app: stage: build script: - npm run build artifacts: paths: - dist/ expire_in: 1 week deploy_dev: stage: deploy script: - echo "Deploying dist folder to dev" - ls dist/ environment: name: development rules: - if: '$CI_COMMIT_BRANCH == "develop"'

This example uses cache for npm dependencies and artifacts for the final build output.

Docker image pipeline example

For containerized applications, the package stage can build and push a Docker image.

stages: - test - build-image run_tests: stage: test image: node:20 script: - npm ci - npm test build_docker_image: stage: build-image image: docker:27 services: - docker:27-dind variables: DOCKER_TLS_CERTDIR: "/certs" IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin - docker build -t "$IMAGE_TAG" . - docker push "$IMAGE_TAG" rules: - if: '$CI_COMMIT_BRANCH == "main"'

This pattern is useful before deploying to Kubernetes or another container platform.

Manual deployment stage example

For staging or production, manual deployment gives humans control before making changes live.

deploy_production: stage: deploy script: - echo "Deploying to production" environment: name: production rules: - if: '$CI_COMMIT_BRANCH == "main"' when: manual - when: never

Common mistakes

MistakeProblemBetter approach
Too many stagesPipeline becomes slow and hard to read.Keep stages logical and simple.
No artifacts for build outputDeploy jobs cannot find required files.Use artifacts for output folders.
Using cache as artifactCache is not reliable for required job output.Use cache for dependencies only.
No manual production gateRisky code can deploy automatically.Use when: manual .
No clear job namesPipeline graph is confusing.Use readable names like run_tests .
Missing rulesJobs run on wrong branches or events.Add branch or merge request rules.

Best practices

  • Start with simple stages: build, test, package, deploy.
  • Use clear job names.
  • Run independent test jobs in parallel.
  • Use artifacts for files needed by later jobs.
  • Use cache for dependency speed.
  • Use needs to reduce pipeline waiting time.
  • Use manual jobs for production deployment.
  • Use rules to control branch, merge request, and scheduled pipeline behavior.
  • Keep secrets out of .gitlab-ci.yml .
  • Validate YAML before merging pipeline changes.

Screenshot checklist

You can add screenshots later from a safe sample project:

  1. Pipeline graph showing multiple stages.
  2. Parallel jobs running in the test stage.
  3. Artifact download from build job.
  4. Manual deployment button.
  5. Job dependency graph using needs .
  6. Successful pipeline after all stages complete.
  • GitLab CI/CD Learning Path
  • GitLab CI/CD guides for Beginners
  • GitLab CI/CD YAML Syntax Explained
  • GitLab Artifacts vs Cache Explained
  • GitLab CI/CD Rules, Only, Except, and Workflow Explained
  • GitLab CI/CD Docker Build Pipeline

Official references

  • GitLab CI/CD YAML syntax reference
  • GitLab job artifacts documentation
  • GitLab job control documentation

Conclusion

Multi-stage pipelines make GitLab CI/CD easier to understand and scale. Start with build, test, package, and deploy stages. Then add artifacts, cache, rules, manual jobs, and needs as your pipeline grows.

The best pipeline is not the most complex one. It is the one that is easy to read, fast enough for developers, safe for production, and reliable during release.

Did this help?

Yes No

Join the discussion

0 Comments

Ask questions, add context, or share what worked.

Sign in to join the discussion

No comments yet. Start the discussion.

More from Melochip Editorial

Related articles

Stay in the loop. Get new Melochip articles

Receive Melochip article updates when new articles are available. Explicit opt-in only.

No pre-checked boxes. Unsubscribe support is part of the subscriber foundation.