Skip to Content
EngineeringGitLab CI/CDGitlab 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.

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

Contact Martin

Fill in your details below to get in touch.

Last updated on