GitLab CI/CD components let you define reusable pipeline steps, jobs, or entire workflows that can be shared across projects, saving you from duplicating configurations and promoting consistency.
Imagine you have a standard set of linting and testing jobs that you want to run on every project in your organization. Instead of copying and pasting these jobs into each .gitlab-ci.yml file, you can define them once as a component.
Here’s a simple example of a reusable component that runs a shell script:
# .gitlab-ci.yml
include:
- component: my-gitlab.com/my-group/my-project/my-component@1.0.0
inputs:
script_to_run: "echo 'Hello from the reusable component!'"
And here’s the content of my-component/.gitlab-ci.yml (or wherever you’ve defined your component):
# my-component/.gitlab-ci.yml
---
name: My Reusable Script Component
description: A component that runs a customizable script.
inputs:
script_to_run:
type: string
description: The shell script to execute.
jobs:
run_script:
stage: build
script:
- |
${inputs.script_to_run}
When this pipeline runs, GitLab fetches the my-component from the specified URL and executes the run_script job, passing the script_to_run input. The output in the pipeline job would show:
Hello from the reusable component!
Components are stored in GitLab projects and can be versioned using Git tags. This allows you to maintain different versions of your reusable logic and upgrade them independently. When you reference a component in your .gitlab-ci.yml, you can specify a tag (e.g., @1.0.0) or a branch (e.g., @main) to control which version is used.
The power of components comes from their ability to encapsulate complex logic. You can define not just simple jobs but entire stages, including dependencies, rules, and environment configurations. This allows you to create standardized build, test, deploy, or security scanning workflows that can be easily integrated into any project.
Consider a more complex scenario: a component for building Docker images.
# .gitlab-ci.yml
include:
- component: my-gitlab.com/my-group/docker-builder@2.1.0
inputs:
dockerfile_path: "Dockerfile.api"
image_name: "my-registry/api-service"
tags:
- "latest"
- ${CI_COMMIT_SHA::8}
The docker-builder component would contain jobs to build the Docker image, tag it, and push it to a registry. It would abstract away the details of docker build, docker tag, and docker push, allowing developers to focus on their application code.
# docker-builder/.gitlab-ci.yml
---
name: Docker Image Builder
description: Builds and pushes Docker images.
inputs:
dockerfile_path:
type: string
description: Path to the Dockerfile.
default: "Dockerfile"
image_name:
type: string
description: Name of the Docker image (e.g., registry/image:tag).
tags:
type: array
description: List of tags to apply to the image.
default: ["latest"]
outputs:
image_digest:
type: string
description: The digest of the pushed image.
jobs:
build_and_push_docker_image:
stage: build
image: docker:24.0.2
services:
- docker:24.0.2-dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
before_script:
- echo "Logging into Docker registry..."
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- echo "Building Docker image: ${inputs.image_name}"
- docker build -t "${inputs.image_name}" -f "${inputs.dockerfile_path}" .
- |
for tag in "${inputs.tags[@]}"; do
full_image_name="${inputs.image_name}:${tag}"
echo "Tagging image: ${full_image_name}"
docker tag "${inputs.image_name}" "${full_image_name}"
echo "Pushing image: ${full_image_name}"
docker push "${full_image_name}"
done
- IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "${inputs.image_name}")
- echo "Image digest: $IMAGE_DIGEST"
after_script:
- echo "Logging out from Docker registry..."
- docker logout "$CI_REGISTRY"
artifacts:
reports:
dotenv: report.env
script:
- echo "IMAGE_DIGEST=$IMAGE_DIGEST" > report.env
This component defines inputs for the Dockerfile path, image name, and tags. It also has an output for the image digest. The before_script and after_script handle Docker registry authentication, and the main script performs the build and push operations. The artifacts:reports:dotenv section makes the IMAGE_DIGEST available to subsequent jobs in the pipeline.
One of the most powerful, yet often overlooked, aspects of components is their ability to define dependencies between themselves. You can create a base component that sets up common build tools or environment variables, and then other components can include this base component, inheriting its setup without explicit duplication. This forms a dependency graph of reusable pipeline logic, allowing for sophisticated and maintainable CI/CD architectures.
When you create a component, you’re essentially creating a mini-project that acts as a source for pipeline configurations. This project needs to be accessible within your GitLab instance, either publicly or privately. For private components, ensure that the GitLab projects consuming them have the necessary permissions to access the component’s repository.
The next step in mastering CI/CD is exploring how to use these components to orchestrate complex, multi-stage deployments, potentially involving external systems.