End-To-End Solutions At The Forefront Of Technological Innovation

Best Practices for GitLab CI/CD Pipelines

Gitlab CI

Introduction

It is part of our philosophy, that we strive to automate every aspect of the job, that is worth automating and will leave us more time to spend in creative and exploratory activities. Since we use Gitlab as main DevOps platform we make sure to use its CI capabilities. Here is a practical example of such use case, that might be useful to others.

GitLab CI/CD pipelines provide a powerful mechanism for automating and orchestrating various stages of software development, from building and testing to deployment. However, when working with multi-project pipelines and downstream dependencies, certain best practices should be considered to avoid common pitfalls. Below are some key considerations (written in blood):

Best Practices

1. Global Variables

  • Issue: Global variables defined in the upstream pipeline will overwrite downstream global variables.
  • Consideration: Variables set at the global level in the upstream pipeline are accessible by downstream pipelines, potentially overwriting their values.
  • Best Practice: Use job-level variables or conditionally set global variables based on specific rules to avoid unintentional overwriting.

Let’s illustrate the scenario with a global variable example:

# Upstream pipeline (in project: upstream_project)
variables:
  DOCKER_CORE_IMAGE_URI: "$CI_REGISTRY/core_project/image:dev"

upstream_job:
  script:
    - echo "Building and generating artifacts..."
    # Your build and generation commands here
  artifacts:
    paths:
      - path/to/artifact   # Specify the path to the artifact you want to pass downstream

Now, let’s consider the downstream pipeline:

# Downstream pipeline (in project: downstream_project)
variables:
  DOCKER_CORE_IMAGE_URI: "$CI_REGISTRY/core_project/image"

downstream_job:
  script:
    - echo "Running downstream job..."
    - echo "Using the downstream Docker image URI: $DOCKER_CORE_IMAGE_URI"
    # Your downstream job commands here
  needs:
    - project: upstream_project
      job: upstream_job
      ref: main  # Specify the branch or commit ref of the upstream job
      artifacts: true

The global variable $DOCKER_CORE_IMAGE_URI‘s value from the upstream pipeline will be used in the downstream pipeline!

You could disable global variables inheritance explicitly it in the upstream pipeline, so that downstream won’t receive any upstream global variables:

upstream_job:
  ...
  inherit:
    variables: false # Disable the inheritance of all global variables
  trigger: # Define the details for triggering downstream pipeline
    ...
    strategy: depend # This job depends on the success of the triggered pipeline

2. Access Control for Downstream

  • Issue: Downstream pipelines require appropriate access rights (at least Maintainer) for triggering. Otherwise, you will get an error “no permission to trigger the downstream pipeline”.

    Detailed description on Stack Overflow
  • Consideration: Only Maintainers or users with sufficient permissions can trigger downstream pipelines.
  • Best Practice: Verify and set access levels accordingly to ensure proper pipeline execution.

3. Check if Pipeline has been Triggered

If you want to determine whether a job has been triggered as part of a downstream pipeline, you can check the predefined environment variable $CI_PIPELINE_SOURCE. The value will indicate the source of the pipeline.

your_job:
  ...
  rules:
    - if: $CI_PIPELINE_SOURCE == "pipeline"
  ...

4. Passing Downstream Artifacts

Suppose you have an upstream pipeline with the following job:

# Upstream pipeline (in project: upstream_project)
upstream_job:
  script:
    - echo "Building and generating artifacts..."
    # Your build and generation commands here
  artifacts:
    paths:
      - path/to/artifact   # Specify the path to the artifact you want to pass downstream

Now, you have a downstream pipeline in another project downstream_project that needs the artifacts from the upstream job. You can express this dependency using the needs keyword:

# Downstream pipeline (in project: downstream_project)
downstream_job:
  script:
    - echo "Running downstream job..."
    # Your downstream job commands here
  needs:
    - project: upstream_project
      job: upstream_job
      ref: main  # Specify the branch or commit ref of the upstream job
      artifacts: true

5. Important assumptions

The above examples were implemented based on Gitlab version 15.x and still operates on 16.x version premium setup we use. Gitlab are constantly trying to improve their CI functionality, and specifics might change. But in our opinion Gitlab CI is definitely their top competitive advantage we believe stuff can only get better :).

6. Feedback

If you have questions on this or similar use cases regarding Gitlab CI or general CI/CD workflow, practices and procedures, we are always open to answer questions or help. So don’t hesitate to write us!

Good Reads

  • Home Office Secure Terminal (HOST)
    The HOST system was developed because of two main reasons, we believe motivate any great product: tough circumstances, no working alternatives and the need to scratch our own itch.
  • Best Practices for GitLab CI/CD Pipelines
    It is part of our philosophy, that we strive to automate every aspect of the job, that is worth automating and will leave us more time to spend in creative and exploratory activities.
  • Product Developement Workflow
    General workflow overview For the sake of practicality here is a short summary of how we organize our work, using Gitlab issues, merge requests(MRs), and operations on… Read more: Product Developement Workflow