GitLab CI/CD: Architecting, Deploying, and Optimizing Pipelines

Continuous Integration with GitLab

What are Services

The image is a blue gradient background with the text "GitLab CI/CD – Image and Service" and a copyright notice for KodeKloud.

GitLab CI/CD pipelines run jobs on self-managed or GitLab-hosted runners. Each job executes in an environment defined by tags, Docker images, and optional service containers. This guide covers:

  • Choosing runners with tags
  • Default containers for SAST
  • Installing custom runtimes
  • Using dedicated Docker images
  • Spinning up service containers

1. Choosing a Runner with Tags

Tags assign jobs to specific runners. For example, ubuntu-latest provides a full Ubuntu environment with pre-installed tools:

# .gitlab-ci.yml
unit-testing:
  tags:
    - ubuntu-latest
  script:
    - echo "Running unit tests on Ubuntu"

2. Default Containers for SAST Runners

GitLab’s SAST runners come pre-configured with a Ruby 3.1 image. This is ideal for Ruby projects but can lead to errors if used for other languages:

# .gitlab-ci.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

sast-unit-test:
  tags:
    - sast-linux-small-amd64
  script:
    - ruby --version
    - bundle install
    - bundle exec rspec

3. Installing Custom Runtimes

When you need a language runtime not in the default image—such as Node.js 20—you can install it during before_script. This adds pipeline time and may increase CI minutes usage.

Note

Installing additional packages in before_script can slow down your pipelines. Consider using a custom image (see next section) for faster, repeatable builds.

# .gitlab-ci.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

node-test:
  tags:
    - sast-linux-small-amd64
  before_script:
    - apt-get update && apt-get install -y curl gnupg
    - curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
    - apt-get install -y nodejs
  script:
    - npm install
    - npm test

4. Using Dedicated Docker Images

Specifying an image lets GitLab pull a pre-built container with your desired tools. This ensures consistency and speeds up jobs by eliminating runtime installation steps.

# .gitlab-ci.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

node-test:
  tags:
    - linux
  image: node:20-alpine3.17
  script:
    - npm install
    - npm test

Docker Container - node:20-alpine3.17

  1. Checkout Code – 2s
  2. Install Dependencies – 3m
  3. Run Tests – 5m

Comparison: Before vs. After Using an Image

ConfigurationInstallation StepsPipeline DurationMaintenance Overhead
Custom Runtime in JobUpdate OS, add repos, install NodeHighHigh
Docker ImagePull pre-built containerLowLow

5. Avoid Hitting Production Dependencies

Warning

Running tests against your production database can degrade performance and expose sensitive data. Always use a separate test database or service container for CI tasks.

6. Using Service Containers

Service containers run alongside your job’s main container in the same network. Use them for databases, caches, or any supporting service:

# .gitlab-ci.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

integration-test:
  tags:
    - linux
  image: node:20-alpine3.17
  services:
    - name: siddarth67/mongo-db:non-prod
      alias: mongo
  variables:
    MONGO_URI: "mongodb://mongo:27017/db"
  script:
    - npm install
    - npm test

When this job runs, GitLab launches two containers on a shared bridge network:

  1. mongo (alias): MongoDB service with test data
  2. Job container: Checks out code, installs dependencies, and runs tests against mongodb://mongo:27017/db

Summary

By combining runner tags, Docker images, and service containers, you can build CI/CD pipelines that are:

  • Isolated and reproducible
  • Fast and cost-effective
  • Configurable per-project requirements

Watch Video

Watch video content

Previous
Project Status Meeting 2