GitLab CI/CD: Architecting, Deploying, and Optimizing Pipelines
Continuous Integration with GitLab
What are Services
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
- Checkout Code – 2s
- Install Dependencies – 3m
- Run Tests – 5m
Comparison: Before vs. After Using an Image
Configuration | Installation Steps | Pipeline Duration | Maintenance Overhead |
---|---|---|---|
Custom Runtime in Job | Update OS, add repos, install Node | High | High |
Docker Image | Pull pre-built container | Low | Low |
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:
- mongo (alias): MongoDB service with test data
- 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
Links and References
Watch Video
Watch video content