Skip to main content

Initial Concepts

The first thing we need to understand is how code organization works within the GitLab platform.

Groups and Projects​

In GitLab, the code organization structure is based on groups and repositories, which facilitates the management of teams, permissions, and projects.

  • Groups: A group is like a main folder that can contain multiple projects (repositories) and even other subgroups. It serves to group everything that belongs to the same team, product, or company area.

    • Permissions can be assigned at the group level, and all projects inherit these permissions.
    • They help in organization by areas (e.g., frontend, backend, devsecops) and also in applying policies and configurations together.
    • This is one of the biggest advantages I see of GitLab against GitHub.
  • Subgroups: They are like subfolders within a group. In fact, they are other groups within groups.

    • They allow an even more granular organization (e.g., company/backend/services).
    • Useful for large companies or with many teams.
  • Repositories (Projects)

    • A repository, called a project in GitLab, is where the source code, commit history, pipelines, issues, and everything else related to that specific project actually lives.
    • Each project has its own Git version control.
    • Can contain CI/CD configured via .gitlab-ci.yml.
    • Can be public or private, and follow rules inherited from the group.
company/
β”œβ”€β”€ platform/ # group
β”‚ β”œβ”€β”€ api-auth # repo
β”‚ β”œβ”€β”€ dashboard # repo
β”‚ └── infra/ # subgroup
β”‚ β”œβ”€β”€ terraform-network # repo
β”‚ └── terraform-vm # repo
β”œβ”€β”€ frontend/ # group
β”‚ └── landing-page # repo
└── backend/ # group
β”œβ”€β”€ user-service # repo
└── payment-service # repo

The group hierarchy in GitLab is essential when thinking about scale and standardization within an organization. Groups allow centralizing configurations that will be automatically inherited by all child repositories. This includes everything from environment variables, templates, to access permissions and security configurations.

When we configure a pipeline in a project, we can define variables and rules directly in it. However, by using groups, we can apply global configurations that will be replicated to all projects in that group, avoiding repetition and facilitating management.

Every time a pipeline is executed, it uses a Runner, which is the computational force responsible for running the jobs. These runners can be shared (at the group level) or dedicated (at the project level), and it's possible to associate environment variables at the group level, which are automatically read by any runner that executes pipelines in child projects.

In other words, organization by groups is not just a visual or structural matter β€” it's fundamental for scaling, standardizing, and maintaining governance of pipelines and execution environments within GitLab.

Runners​

The GitLab Runner is a service responsible for executing the jobs defined within the pipelines (.gitlab-ci.yml). In other words: the runner is who does the hands-on work when a pipeline is triggered.

Types of Runners​

  • Shared Runners: Configured by the GitLab instance, available for multiple groups and projects.

  • Group Runners: Associated directly with a group. All repositories (projects) within that group can use it.

  • Project Runners: Exclusive to a specific project. Only that project can use it.

This division allows flexibility and control, especially when talking about different teams, distinct workloads, or environments with specific requirements.

The runner is the environment where the code will be executed during the pipeline. This environment can be generic or specific, depending on the project's needs.

For example, we can have a runner that starts with a basic image, like a "raw" Ubuntu, without any installed tools. During job execution, the pipeline itself is responsible for installing everything needed β€” such as dependencies, languages, and tools β€” before running the project.

On the other hand, we can use pre-configured images with everything the project needs. If we're going to run project A, we can use a container A with all the technologies already installed. If project B is a Node.js app, we can use an official node image that already comes with Node.js ready to use. This speeds up execution and avoids installation repetition.

In some cases, it may make sense to use only a base image and let each project configure what it needs via pipeline. This gives more flexibility, but can increase execution time and require more maintenance.

There's no right or wrong β€” what exists is architecture and governance. Each organization has its own security and compliance requirements, and this can directly impact how runners are configured and used.

For example, in some more restricted environments, it may not be allowed to run custom images for audit or traceability reasons. In these cases, customization needs to happen at runtime, within the job itself.

And what does this have to do with runner types?

  • A shared runner, used by multiple projects, should be as generic and neutral as possible, ensuring broad compatibility and security.
  • A group runner already allows a higher level of customization, as it's under the control of a specific team and can be adapted to the common technologies of that group.
  • A project runner can be highly specialized, custom-made for the needs of that specific project β€” including customized images and pre-installed tools.

During pipeline definition we choose which runners we'll use, but that's further ahead.

Where Do Runners Run?​

Runners are not necessarily executed in containers, but in practice, container use is very common because it facilitates isolation, portability, and automation.

A runner is nothing more than a service that communicates with GitLab, ready to clone a repository and execute pipeline code. If it's a service in an operating system, what do we normally have?

ExecutorRuns where?Uses container?
shellOn the host itself (like bash)❌ No
dockerInside Docker containersβœ… Yes
docker+machineIn Docker containers created on demandβœ… Yes
kubernetesIn pods within a clusterβœ… Yes (pods)
sshOn a remote server via SSH❌ No
customYou defineDepends

Why use containers then?

  • Isolation: No mess between builds.
  • Security: Each job in a controlled environment.
  • Portability: The same container runs anywhere.
  • Reproducibility: "It works on my machine" loses strength.

It's not mandatory for the runner to use containers, but most of the time, it's the safest, cleanest, and most scalable path. And the choice of executor has everything to do with your infrastructure strategy and the level of control you want to have.

Runner Environment Variables​

A runner loads environment variables defined in a project. When you define environment variables in a project in GitLab (defined per project or inherited from parent groups), these variables are associated with the runner during pipeline execution for that specific project. This means that when running the pipeline, the runner will have access to these variables and can use them during job execution.

You can define environment variables directly in the GitLab interface, within your project or groups. These variables can be used to store information such as API credentials, authentication keys, database configurations, among others.

These variables are available to any job within this project, as long as the runner is executing the pipeline.

When the project's pipeline is triggered, the runner that is configured for this project (it can be a specific runner for the project or a shared runner) loads the defined environment variables.

The runner passes these variables to the execution environment where the job is being executed, whether in a Docker container, on a server via SSH, or another type of executor.

Pipeline Triggers​

Above we mentioned "When the project's pipeline is triggered" and you might be wondering how this works. Who identifies the triggers is GitLab itself according to events that happen in a repository. When GitLab identifies an event it will look at the .gitlab-ci.yml file and see how the configuration is and whether it should or shouldn't trigger the pipeline based on the rules you defined.

Some possible triggers:

  1. Push to Repository: The most common way to trigger a pipeline is through a push to the repository. Whenever there's a new commit sent to the repository (a push), GitLab automatically triggers the pipeline to execute the jobs defined in the .gitlab-ci.yml file.

    • Example: Every time you push to a branch (for example, main or develop), the pipeline will be executed.
    • What happens: GitLab will fetch the .gitlab-ci.yml file and trigger the pipeline with the jobs defined in that file.
    • It's possible to execute when a push is made to any branch or some specific branch.
  2. Merge Requests (MRs): When you create or update a merge request in GitLab, a pipeline can be automatically triggered to validate changes before merging them into the destination branch. This helps ensure that the proposed code is working correctly before being integrated.

    • Example: Whenever a merge request is opened, the pipeline is automatically executed to test and verify the code of the branch being merged.
    • What happens: The pipeline is executed to validate the code that will be integrated and ensure it's in compliance with the quality and testing rules defined in .gitlab-ci.yml.
    • It's possible to execute when a merge is opened, but also being able to specify to which branch this merge is.
  3. Manual Triggers: You can configure manual triggers so that pipelines are triggered only when a user triggers manually. This is useful for actions that don't need to be executed with each commit, like deployments or specific tests that aren't part of the standard flow.

    • Example: A deploy job that's only triggered when someone authorized clicks the "Play" button in the GitLab interface.
    • What happens: The job is executed only when the user explicitly requests it, providing greater control over when actions are performed.
  4. GitLab CI/CD Pipeline Schedules (Scheduling): You can configure pipeline schedules, which allow a pipeline to be triggered at regular intervals or at specific times. This is useful for maintenance tasks, such as dependency updates or periodic builds.

    • Example: A pipeline can be configured to run every night at 3 AM to do cleanup or update dependencies.
    • What happens: The pipeline will be automatically triggered based on the defined schedule, without manual intervention.
  5. Triggers via API: GitLab allows triggering pipelines from outside the system, using triggers via API. With this, you can integrate GitLab with other systems and tools that need to trigger pipelines at specific moments or after certain events.

    • Example: A monitoring tool can trigger a pipeline when a new software version is detected or when a new commit is made.
    • What happens: You use a trigger token generated by GitLab to make a POST via API and trigger the pipeline externally.
  6. Push on Specific Branches: It's possible to configure pipelines to be triggered only when a push is made to a specific branch. This can be done by configuring .gitlab-ci.yml to listen to changes on specific branches.

    • Example: If you want the pipeline to be executed only when a commit is made to the main branch (or another specific branch), you can configure the pipeline to trigger only under those circumstances.

    • What happens: The pipeline will be executed only if the push occurs on the branches you configured in the .gitlab-ci.yml file.

  7. Pipeline Trigger via Webhooks: Another way to trigger a pipeline is using webhooks. A webhook allows you to send an HTTP request to a GitLab endpoint whenever an event occurs in another system. This is useful when you want to integrate GitLab with other external tools that can trigger a pipeline.

    • Example: If you're using a code monitoring tool, like Sentry, or an external CI tool, it can trigger a pipeline in GitLab when it detects a specific event.
    • What happens: The webhook sends a request to GitLab, which in turn triggers the pipeline based on the configured conditions.

There may be others, but these are the main and most used.

All these configurations should be in the .gitlab-ci.yml file at the repository (project) root.