Engineering
GitLab
Gitlab CI Cheatsheet

GitLab CI Cheat-Sheet

This is a cheat-sheet that I've put together that summarizes some of the most useful commands that I personally use the most when I'm working with GitLab CI.

GitLab CI is a powerful tool that helps you implement Continuous Delivery. It uses .gitlab-ci.yml file as it's main configuration file. This file can be split into multiple configuration files, but the commands are still the same.

This file defines the whole continuous delivery and continuous integration (CI/CD) pipeline which you can then use enforce quality control on source code changes automatically.

In this article we will focus on this configuration file.

Configuration Sections

  • stages: Defines the stages in your pipeline (e.g., build, test, deploy).
  • image: Specifies the Docker image to use for the job environment.
  • variables: Defines variables globally for the pipeline.
  • cache: Configures caching between jobs to speed up pipeline execution.
  • before_script: Commands that run before each job's script.
  • script: The main set of commands executed by the job.
  • after_script: Commands that run after each job's script.
  • only/except: Controls when jobs should be executed based on branches or tags.

Here is an example of a gitlab pipeline configuration:

    stages:
      - build
      - test
      - deploy
 
    variables:
      PROJECT_NAME: "project-id"
 
    build:
      stage: build
      image: ubuntu:latest
      script:
        - echo "Building the application..."
        - make
 
    test:
      stage: test
      image: python:3.9
      script:
        - echo "Running tests..."
        - pytest
 
    deploy:
      stage: deploy
      image: alpine:latest
      script:
        - echo "Deploying application to production..."
      only:
        - main

Gitlab will execute all steps in each stage and then move on to the next stage. Through options you can override this behavior and build in any order (and specify dependencies between stages as well).

The full reference of gitlab ci file can be found here (opens in a new tab).

Pipeline Configuration

  • Define Stages: stages: [build, test, deploy] - Outlines the primary stages of the CI/CD pipeline.
  • Use Docker Image: image: docker:19.03.12 - Specifies the Docker image to use for all jobs.
  • Specify a Service: services: [docker:dind] - Uses Docker-in-Docker as a service for building containers.
  • Dependencies: Use dependencies to control which artifacts are passed between jobs.
  • Environment: Use environment to define the environment to which the job deploys artifacts.
  • Include: Use include to include external YAML files for reusable configurations.

Job Definitions

  • Run Only on Main Branch: only: [main] - Ensures the job runs only on the main branch.
  • Cache Dependencies: cache: { paths: [node_modules/] } - Caches directories between jobs to improve speed.
  • Define Artifacts: artifacts: { paths: [build/] } - Specifies files or directories to be saved as artifacts.
  • Parallel Execution: parallel: 4 - Runs the job in four parallel instances.
  • Job Artifacts: Use artifacts to specify files and directories that are created by a job and need to be saved or used by another job.

Script Execution

  • Custom Script Execution: script: echo "Hello, World!" - Executes a custom script command.
  • Before and After Scripts: before_script: [echo "Setup"] - Runs commands before the job's script.

Conditional Execution

  • Conditional Job Execution: rules: [{ if: '$CI_COMMIT_BRANCH == "main"', when: always }] - Executes a job based on a specific condition.
  • Skip Job Unless Manual Trigger: rules: [{ if: '$CI_PIPELINE_SOURCE == "push"', when: never }] - Prevents job execution unless manually triggered.

Environment and Deployment

  • Set Deployment Environment: environment: production - Marks the job as deploying to the production environment.
  • Dynamic Environment Names: environment: { name: review/$CI_COMMIT_REF_NAME } - Uses dynamic names for environments.

Advanced Features

  • Trigger Another Project: trigger: { project: my-group/my-project, branch: main } - Triggers a pipeline in another project.
  • Include External YAML: include: 'https://example.com/gitlab-ci.yml' - Includes an external CI/CD configuration.
  • Use Variables in Scripts: script: echo "Deploying version $VERSION" - Utilizes predefined or custom variables in scripts.
  • Manual Job Trigger: when: manual - Requires manual intervention to trigger the job.

Optimization and Efficiency

  • Artifact Expiry: artifacts: { expire_in: 1 week } - Sets artifacts to expire and be deleted after a week.
  • Cache Key Customization: cache: { key: "$CI_COMMIT_REF_SLUG", paths: [cache/] } - Customizes the cache key for uniqueness.
  • Specific Changes Trigger: only: { changes: [README.md] } - Runs the job only if specific files are changed.

Include

Use include to import shared CI/CD configurations, keeping your .gitlab-ci.yml clean and DRY (Don’t Repeat Yourself). This is particularly useful for large projects or when maintaining common standards across multiple projects.

    include:
      - project: 'my-group/my-project'
        file: '/templates/.common-ci-template.yml'

Multi Project Pipelines

  • trigger: Triggers a pipeline in another project. Useful for orchestrating larger workflows across multiple projects.
    deploy_downstream_job:
      stage: deploy
      script:
        - echo "Triggering downstream project pipeline"
      trigger:
        project: my-group/my-downstream-project
        branch: main

Dynamic Child Pipelines

  • trigger:include: Dynamically generates and includes pipeline configuration from a script output. Enables dynamic generation of pipeline configurations.
    create_child_pipeline:
      stage: build
      script: echo "Generating child pipeline configuration..."
      artifacts:
        paths:
          - dynamic-config.yml
      trigger:
        include: dynamic-config.yml
        strategy: depend

Environments

Deployment environments make gitlab automatically link to latest deployment from the merge request making it easy to review changes.

  • environment: Define deployment environments. Supports dynamic environments using variables for branch-specific deployments.
    deploy_to_production:
      stage: deploy
      script: echo "Deploying application..."
      environment:
        name: production
        url: https://production.example.com
      only:
        - main

Rules for jobs

Rules determine when a job runs. Rules are more powerful than gitlab only keyword.

  • rules: Defines conditions under which a job will be included or excluded from the pipeline, replacing the only/except syntax for more complex logic.
    test:
      stage: test
      script: echo "Running tests..."
      rules:
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
          when: always
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"
          when: manual

Rules can also include a job only if some files have been changed:

    workflow:
      rules:
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"
        - changes:
            - Dockerfile
            - "/src/**/*"

Another kind of conditional job execution:

    deploy_production:
      stage: deploy
      script:
        - make deploy
      when: manual
      only:
        # Only release branches
        - /^release-.*$/

Artifacts and caching

Artifacts are your primary way to reuse produced binaries between jobs. Use artifacts for compilation output and use cache for files that need to be downloaded but do not change very often.

    build:
      stage: build
      script:
        - make build
      artifacts:
        paths:
          - build/
      cache:
        paths:
          - .cache/
 
    test:
      stage: test
      script:
        - make test
      dependencies:
        - build
      cache:
        paths:
          - .cache/

Note that by default artifacts are saved only on success. If you want to save them regardless of pipeline status you should specify when: always under artifacts key.

If your jobs need to download a lot of dependencies (like pip or npm packages) then caching will help you reuse these artifacts across repeated pipeline runs. Use $CI_COMMIT_REF_SLUG to tag the cache using the branch name.

    cache:
      key: ${CI_COMMIT_REF_SLUG}
      paths:
        - .npm/
      policy: pull-push

Parallel jobs

Gitlab can create jobs based on a parameter matrix. This is useful when you want to execute many different combinations of builds and do not want to define exponentially large number of pipelines.

    test:
      stage: test
      script:
        - make test
      parallel:
        matrix:
          - VAR1: foo
            VAR2: ["variation1", "variation2"]
          - VAR1: bar
            VAR2: ["variation3", "variation4"]

This will automatically create jobs test:foo:variation1, test:foo:variation2, test: bar:variation3 and test:bar:variation4.

Another form of parallel job execution is using parallel is with a number.

    build:
      stage: build
      script: build.sh --output build-$CI_NODE_INDEX.zip
      parallel: 4
      artifacts:
        paths:
          - build-$CI_NODE_INDEX.zip

When using parallel jobs to generate artifacts, make sure each job produces a uniquely named artifact to prevent conflicts. You can use environment variables like $CI_JOB_ID or $CI_NODE_INDEX to differentiate them. $CI_NODE_TOTAL will be equal to 4 in the above configuration.

Custom Docker Images

I think the most powerful aspect of gitlab CI is the ability to run everything inside docker. We will not go into details of creating docker images in this article but in most cases you want to use your own docker image for jobs. The job will run inside the image and this way you can have a predictable starting point every single time. All build tools should be already included in the image.

    build:
      stage: build
      image: my-custom-image:latest
      script:
        - build script

Docker Services

Use docker services when you want to run your code against a server. This can even be used to end-to-end test a docker image that is built in the same pipeline.

Gitlab runner will start the services as separate docker containers that are part of the same network as the job.

    test:
      stage: test
      services:
        - name: postgres:latest
          alias: db
      variables:
        DATABASE_URL: "postgresql://postgres:@db:5432/my_database"
      script:
        - ./test.sh

alias is the name of the service machine so you can use it in place of a hostname when you need to communicate with the service.

Dynamic Environments

  • Dynamic Environments: Dynamically create environments for each branch or merge request, allowing stakeholders to review changes in a live environment.
    review:
      stage: deploy
      script: deploy_to_review_app.sh
      environment:
        name: review/$CI_COMMIT_REF_NAME
        url: https://$CI_COMMIT_REF_NAME.review.example.com
        on_stop: stop_review

Cleanup

Implement an after_script section in your .gitlab-ci.yml file. This ensures that cleanup commands run after the job's script section completes, regardless of whether the job succeeds or fails.

    test:
      script:
        - execute_tests.sh
      after_script:
        - cleanup_resources.sh

This is particularly useful for releasing resources, deleting temporary files, or shutting down services that were started during the job.

Environment Cleanup

For jobs that deploy to dynamic environments, use the on_stop keyword to specify a job that runs to clean up the environment when it's stopped manually from the GitLab UI.

    deploy_review_app:
      environment:
        name: review/$CI_COMMIT_REF_NAME
        on_stop: stop_review_app
 
    stop_review_app:
      stage: cleanup
      script:
        - echo "Cleaning up review app resources..."
      when: manual
      environment:
        name: review/$CI_COMMIT_REF_NAME
        action: stop

This method is particularly effective for cleaning up environments used for review apps or temporary deployments.

Conditional Cleanup

GitLab CI/CD variables like $CI_JOB_STATUS can be used to conditionally execute cleanup steps based on the outcome of previous jobs or stages.

    after_script:
      - if [ "$CI_JOB_STATUS" = "failed" ]; then echo "Running cleanup after failure"; fi

Or use custom checks

cleanup:
  script:
    - if ./check_resource_status.sh; then ./cleanup.sh; fi

For critical cleanup tasks, consider using the retry keyword to attempt the cleanup multiple times in case of failures. You can also set allow_failure: true to prevent the cleanup job from causing the entire pipeline to fail if it can't complete successfully.

    cleanup_resources:
      script:
        - robust_cleanup_command
      allow_failure: true
      retry:
        max: 3
        when: on_failure

Docker and Services for Test Environments

Use Docker containers to create consistent, isolated environments for your simulations and system tests. This ensures tests run in a controlled environment, mirroring production settings.

    test:
      stage: test
      image: docker:19.03.12
      services:
        - postgres:12
      script:
        - execute_system_tests.sh

Use artifacts for test results and debugging

Configure jobs to generate and store artifacts like logs, screenshots, or reports from your simulation or system tests. This is invaluable for debugging and historical analysis.

    simulation_test:
      stage: test
      script: run_simulation.sh
      artifacts:
        paths:
          - logs/
          - screenshots/
        expire_in: 1 week

Use retry for flaky tests

For tests that are known to be flaky and might fail intermittently due to external dependencies, use the retry keyword to automatically re-run failed tests.

    flaky_test:
      stage: test
      script: possibly_flaky_test.sh
      retry: 2

Schedule Full System Tests

Schedule full system tests to run during off-peak hours. This allows for thorough testing without impacting the development workflow.

    stages:
      - simulation
 
    nightly_simulation_test:
      stage: simulation
      script: run_full_simulation_suite.sh
      only:
        - schedules

Clear Pipeline Stages

  • Logical Grouping: Organize your pipeline into clear, logical stages such as build, test, deploy, etc. This structure enhances readability and understanding of the pipeline flow.
stages:
  - build
  - test
  - deploy

Use dependencies

Use the dependencies keyword to explicitly specify which artifacts a job should receive from previous stages. This minimizes unnecessary data transfer and speeds up job execution.

deploy:
  stage: deploy
  dependencies:
    - build
  script:
    - deploy_script.sh

Use needs

The needs keyword allows you to create non-linear pipelines (DAG), enabling jobs to start as soon as their dependencies are completed rather than waiting for the entire previous stage to finish. This can significantly reduce pipeline execution times.

    unit_test:
      stage: test
      script: run_unit_tests.sh
 
    integration_test:
      stage: test
      script: run_integration_tests.sh
      needs: ["unit_test"]

Use Anchors and Aliases

Avoid repetition by using YAML anchors (&) and aliases (*) to reuse configurations in your GitLab CI file. This is particularly useful for repeated script blocks or environment variables.

    .build_template: &build_definition
      script:
        - echo "Building project"
        - build_command
 
    build_job:
      <<: *build_definition
      stage: build

Use extends

Use the extends keyword to inherit from one or more templates. This allows for more flexibility compared to anchors and aliases, especially for overriding parts of the configuration.

    .template:
      script:
        - setup_command
 
    job1:
      extends: .template
      script:
        - additional_command # This will be executed after `setup_command`.

Use variables for Global and Local Scopes

Define global variables at the top of your .gitlab-ci.yml for use across all jobs, and override or extend them in individual jobs as needed.

    variables:
      GLOBAL_VAR: "global_value"
 
    job1:
      variables:
        LOCAL_VAR: "local_value"
      script:
        - echo $GLOBAL_VAR
        - echo $LOCAL_VAR

Store Secrets in Variables

Use CI/CD variables for sensitive data like tokens or passwords. Define them in the GitLab UI to keep them out of your .gitlab-ci.yml.

    deploy:
      stage: deploy
      script:
        - deploy_command --token $DEPLOY_TOKEN

Use comments in YAML

Use comments (#) liberally to explain complex configurations, the purpose of jobs, or why specific settings are used. This aids in maintainability.

# This job builds the project
build:
  stage: build
  script:
    - echo "Building"
    # Temporary workaround for XYZ issue
    - build_workaround_command
Martin SchröderMartin Schröder
16 years  of experience

About the author

Martin is a full-stack expert in embedded systems, data science, firmware development, TDD, BDD, and DevOps. Martin serves as owner and co-founder of Swedish Embedded Consulting.

Expertise

Embedded Firmware
Zephyr RTOS
Scrum
Continuous Delivery

Contact Martin

By completing signup, you are agreeing to Swedish Embedded's Terms of Service and Privacy Policy and that Swedish Embedded may use the supplied information to contact you until you choose to opt out.

Confirm your contact information

Thank you!

An email has been sent to you with a link to book a short call where we can discuss your project further. If you have any further questions, please send an email to info@swedishembedded.com