DEV Community

Cover image for How to Set Up a GitLab CI/CD Pipeline
Emmanuel Mumba
Emmanuel Mumba

Posted on • Originally published at deepdocs.dev

How to Set Up a GitLab CI/CD Pipeline

Once upon a time, deploying apps used to be slow, manual and repetitive process. However, modern software is built and shipped rapidly through automation. This is called CI/CD (Continuous Integration / Continuous Deployment).

Previously, I covered about how to set-up a CI/CD Pipeline on Github. But if you’re using GitLab, you have a powerful CI/CD system built right into your repository. In-fact, one of the distinguishing features for GitLab is its native support for CI/CD workflows.

In this guide, I’ll walk you through setting up your very first GitLab CI/CD pipeline. We’ll break down what it actually is, why you need it, and then create a working example with a simple Node/React-style app. Along the way, I’ll share some practical developer insights that I’ve learned the hard way.

What is GitLab CI/CD (and Why Should You Care)?

At its core, GitLab CI/CD is GitLab’s built-in automation system. You define instructions in a special YAML file (.gitlab-ci.yml), and GitLab takes care of running them every time you push code.

Think of it like giving GitLab a recipe:

  • Step 1 → Build the app
  • Step 2 → Run the tests
  • Step 3 → Deploy somewhere

Instead of doing these things manually, GitLab CI/CD does them automatically, on every commit, merge request, or release tag.

Why is this useful?

  • You catch bugs early with automated tests.
  • You ensure every commit is deployable.
  • You remove the “it works on my machine” problem.
  • You free yourself from repetitive manual tasks.

If you’re building anything beyond a hobby project, CI/CD is the secret sauce to moving fast without breaking things.

Get Started with GitLab CI/CD

CI/CD is a continuous method of software development, where you continuously build, test, deploy, and monitor iterative code changes. This iterative process helps reduce the chance that you develop new code based on buggy or failed previous versions. GitLab CI/CD can catch bugs early in the development cycle and help ensure that the code deployed to production complies with your established standards.

This process is part of a larger workflow:

GitLab DevSecOps lifecycle with stages for Plan, Create, Verify, Secure, Release, and Monitor.

Step 1: Create a .gitlab-ci.yml file

To use GitLab CI/CD, you start with a .gitlab-ci.yml file at the root of your project. This file specifies the stages, jobs, and scripts to be executed during your CI/CD pipeline. In this file, you define variables, dependencies between jobs, and specify when and how each job should be executed.

Step 2: Find or create runners

Runners are the agents that run your jobs. These can run on physical machines or virtual instances. In your .gitlab-ci.yml file, you can specify a container image. The runner loads the image, clones your project, and runs the job either locally or in the container.

  • On GitLab.com, runners on Linux, Windows, and macOS are already available.
  • On self-managed GitLab, you’ll need to register your own runner.

Step 3: Define your pipelines

A pipeline is what you’re defining in .gitlab-ci.yml. It’s made up of jobs and stages:

  • Stages define the order of execution (build, test, deploy).
  • Jobs specify the tasks in each stage (compile, test, etc.).

Pipelines can be triggered by commits, merges, or schedules.

Step 4: Use CI/CD variables

GitLab CI/CD variables are key-value pairs used to store configuration settings or sensitive info (like API keys). Variables can be defined in the file, in project settings, or generated dynamically.

Types:

  • Custom variables → user-defined.
  • Predefined variables → set automatically by GitLab.

Variables can be protected or masked for security.

Step 5: Use CI/CD components

A CI/CD component is a reusable pipeline configuration unit. They help reduce duplication, improve maintainability, and promote consistency across projects. GitLab even provides templates for common tasks.

Create and Run Your First GitLab CI/CD Pipeline

Prerequisites

Before starting, make sure you have:

  • A project in GitLab. (If you don’t, create a free public project at gitlab.com)
  • Maintainer or Owner role for that project.

Steps

1. Ensure you have runners available

  • To check: go to Settings > CI/CD > Runners. At least one runner should show as active.
  • If none are available, install GitLab Runner locally and register it with your project.

2. Create a .gitlab-ci.yml file

At the root of your repository, add a .gitlab-ci.yml file to define your pipeline.

For example:

stages:          # Pipeline stages
  - build
  - test
  - deploy

build-job:       # Job 1: Build
  stage: build
  image: node:18
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/

test-job:        # Job 2: Test
  stage: test
  image: node:18
  script:
    - npm install
    - npm test

lint-job:        # Job 3: Lint (runs in parallel with test-job)
  stage: test
  image: node:18
  script:
    - npm run lint

deploy-job:      # Job 4: Deploy
  stage: deploy
  image: node:18
  script:
    - echo "Deploying to production server..."
  only:
    - main

Enter fullscreen mode Exit fullscreen mode

This defines four jobs across three stages: build → test → deploy.

  • build-job installs dependencies and builds your app.
  • test-job runs your tests.
  • lint-job checks your code quality (runs alongside tests).
  • deploy-job deploys to production, but only when changes are pushed to the main branch.

3. Commit and run

Commit the .gitlab-ci.yml file to your repository. GitLab automatically starts the pipeline.

4. View your pipeline and jobs

  • Go to Build > Pipelines to see the pipeline progress.

  • Click on a pipeline ID to view a graph of jobs and dependencies.

  • Select a job name (e.g., deploy-prod) to inspect logs and job details.

Done!  - you’ve just created and run your first GitLab CI/CD pipeline! From here, you can expand the .gitlab-ci.yml with more advanced jobs, variables, and integrations.

The Building Blocks of GitLab CI/CD

Before jumping into the setup, let’s clarify the three key concepts you’ll see in every .gitlab-ci.yml file:

1. Pipelines

A pipeline is the full CI/CD workflow. It’s triggered by a commit or merge, and it runs through a series of stages/jobs.

2. Stages

A stage is a phase in your pipeline (build, test, deploy). Stages run sequentially: the test stage won’t run until build has succeeded.

3. Jobs

A job is a single task inside a stage. It’s just a set of shell commands. For example, a test job might run npm test.

So the hierarchy looks like this:

Setting Up Your First GitLab CI/CD Pipeline

We’ll build a simple pipeline for a Node/React app. Assume your project has this structure:

Step 1: Create a .gitlab-ci.yml File

At the root of your repo, create a file named .gitlab-ci.yml. This file tells GitLab what to do when you push code.

Step 2: Define Your Stages

Step 3: Add Your Jobs

Step 4: Push and Watch the Magic

Head to Build → Pipelines in GitLab to see your pipeline running.

Developer Insights: Common Mistakes and Pro Tips

Even experienced developers slip up when setting up GitLab CI/CD. Here are a few common mistakes I’ve seen (and made myself), plus how to avoid them:

1. Forgetting to cache dependencies

If every job re-installs dependencies from scratch, pipelines get painfully slow. For example, in a Node.js project, npm install can take minutes each time.

Fix: Use GitLab’s cache keyword to speed things up:

cache:
  paths:
    - node_modules/

Enter fullscreen mode Exit fullscreen mode

2. Not pinning Docker images

Using image: node:latest seems convenient, but it can break your build if a new Node release introduces changes.

Fix: Pin to a specific version like node:18 to ensure stability.

3. Misusing stages and jobs

Beginners sometimes put all commands in a single job. That works… until you need parallelization or conditional deployments.

Fix: Break pipelines into clear stages (build, test, deploy) with multiple jobs. This makes debugging and scaling way easier.

4. Ignoring YAML anchors

If you repeat the same image, cache, or before_script blocks in multiple jobs, your .gitlab-ci.yml gets messy.

Fix: Use YAML anchors to DRY up your file:

.default-job: &default-job
  image: node:18
  cache:
    paths:
      - node_modules/

build-job:
  <<: *default-job
  script:
    - npm run build

Enter fullscreen mode Exit fullscreen mode

5. Not setting branch rules

Without only or except, you might accidentally deploy from feature branches.

Fix: Protect your deploy job:

only:
  - main

Enter fullscreen mode Exit fullscreen mode

That way, the section not only lists mistakes but also explains why they hurt and how to fix them with code snippets.

Pro Tips (Quick Wins)

  • Cache smartly → Save time by caching node_modules/ or vendor/.
  • Pin versions → Use node:18 instead of node:latest for stability.
  • Use YAML anchors → Keep your .gitlab-ci.yml clean and DRY.
  • Protect deploys → Run deployment jobs only on main (or release branches).
  • Keep pipelines fast → Fail fast with lint/tests before hitting deploy.

A Real-World Example: Node/React App

Let’s make this practical. Imagine you’ve got a simple React frontend with a Node/Express backend in the same repo. You want your pipeline to:

  1. Install dependencies
  2. Run tests
  3. Build the React app
  4. Deploy the backend + frontend

Here’s how your .gitlab-ci.yml might look:

stages:
  - install
  - test
  - build
  - deploy

default:
  image: node:18

cache:
  paths:
    - node_modules/

install:
  stage: install
  script:
    - npm install
  artifacts:
    paths:
      - node_modules/

test:
  stage: test
  script:
    - npm test

build_frontend:
  stage: build
  script:
    - cd frontend
    - npm install
    - npm run build
  artifacts:
    paths:
      - frontend/build

build_backend:
  stage: build
  script:
    - cd backend
    - npm install
    - npm run build
  artifacts:
    paths:
      - backend/dist

deploy:
  stage: deploy
  script:
    - echo "Deploying app..."
    - rsync -avz frontend/build/ user@server:/var/www/frontend
    - rsync -avz backend/dist/ user@server:/var/www/backend
  only:
    - main

Enter fullscreen mode Exit fullscreen mode

What’s Happening Here

  • Stages: Install → Test → Build → Deploy (a standard flow for Node + React apps).
  • Cache: Keeps node_modules/ between jobs so installs are faster.
  • Artifacts: React build output (frontend/build) and backend dist files (backend/dist) are passed to the deploy job.
  • Deploy: Uses rsync as a simple way to push files to a server — in real life, you’d use SSH, Docker, or Kubernetes.
  • only: main: Ensures deployment only runs from your main branch, avoiding those “oops, I deployed a feature branch” moments.

Wrapping Up

GitLab CI/CD may seem intimidating at first, but once you understand pipelines, stages, and jobs, it clicks. With just a .gitlab-ci.yml file, you can:

  • Build your app automatically.
  • Run tests on every commit.
  • Deploy safely and consistently.

The real power comes when you start refining your pipelines: caching, artifacts, YAML anchors, and custom runners.

If you’re working with Node or React, the examples above should give you a solid starting point. From here, you can expand into more advanced flows (linting, end-to-end tests, staging/production deployments, Docker builds, etc.).

One More Thing

While CI/CD pipelines are perfect for automating your builds and tests, our team at DeepDocs is working hard to bring CI/CD automation to keep your documentation updated.

You see, updating docs is still a manual process, and it is often forgotten as it is seen as a boring or low value task. DeepDocs takes care of this keeping your docs automatically updated with your commits, and with no manual effort. it completely free to try on GitHub.

Top comments (4)

Collapse
 
priyacodes profile image
Priya Mehra

Really clear breakdown, thanks for the simple YAML example! Helped me finally get my first pipeline running.

Collapse
 
therealmrmumba profile image
Emmanuel Mumba

Glad it was helpful

Collapse
 
williamsj04 profile image
Jessica Williams

This guide was incredibly helpful as I'm transitioning into DevOps roles. The step-by-step walkthrough of setting up a GitLab CI/CD pipeline using .gitlab-ci.yml was clear and practical. I especially appreciated the insights on configuring runners and automating builds, tests, and deployments. These are essential skills for modern software development, and this post has given me the confidence to implement CI/CD in my own projects. Thank you for sharing such a comprehensive and accessible resource!

Collapse
 
therealmrmumba profile image
Emmanuel Mumba

I'm glad you found it helpful. All the best in your new adventure