DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Cicd Tutorial

Introduction to CI/CD
Getting Started with GitLab CI/CD
Stages, Jobs, Artifacts & Cache
Variables, Secrets and Environments in GitLab CI
GitLab Runners
GitLab CI Pipeline Rules
GitLab CI Environments
GitLab CI Docker Workflows
GitLab CI Testing
GitLab CI Security Scanning
GitLab CI Deploy to AWS
GitLab CI Multi-Project Pipelines
GitLab CI Best Practices

Getting Started with GitLab CI/CD

PreviousPrev
Next

Create your first GitLab CI/CD pipeline from scratch, write a .gitlab-ci.yml file, understand GitLab Runners, trigger your first pipeline, and read the job output. Step-by-step tutorial for beginners.

If you want to learn GitLab CI/CD, the fastest path is to build a real pipeline yourself. The good news is that GitLab makes the beginner experience surprisingly approachable. You do not need a separate CI server, a complicated plugin system, or a deep background in release engineering. In most cases, you only need a GitLab repository and a single file named .gitlab-ci.yml.

This tutorial walks you through getting started with GitLab CI/CD from scratch. You will learn what prerequisites you need, where the configuration file lives, how a simple pipeline is structured, what a GitLab Runner does, how jobs are matched to runners, how pipelines get triggered, how to read logs, how to debug common first-time mistakes, and how to build up from an echo-based demo to a more realistic Node.js test pipeline.

Prerequisites

Before you create your first pipeline, make sure you have a few basics in place.

1. A GitLab account

You need access to GitLab, either on GitLab.com or a self-managed GitLab instance. If you are using GitLab.com, many starter projects can use shared runners immediately.

2. A repository

You need a project repository where the pipeline configuration file will live. This can be:

  • a brand-new test repository
  • an existing application repository
  • a simple demo repo with a README and one source file

3. Permission to run pipelines

In most personal or team projects, if you can push to the repository, you can trigger pipelines. In some organizations, runner access or protected branch rules may affect what can run.

4. Basic Git workflow knowledge

You should know how to:

  • create or edit files locally
  • commit changes
  • push to GitLab
  • open the project in the GitLab web UI

That is enough to begin.

What Is .gitlab-ci.yml?

The heart of GitLab CI/CD is a file named .gitlab-ci.yml.

This file is the pipeline definition. It tells GitLab:

  • what stages exist
  • what jobs should run
  • what commands each job executes
  • which Docker image to use
  • when jobs should run
  • what artifacts, rules, or variables apply

Where the file goes

.gitlab-ci.yml must be placed in the root of the repository.

That means its path should look like this:

my-project/
  .gitlab-ci.yml
  README.md
  src/
  package.json

If you accidentally place the file inside a subdirectory such as ci/.gitlab-ci.yml without using advanced include patterns, GitLab will not treat it as the main pipeline definition for the project.

Why the file matters

GitLab reads .gitlab-ci.yml whenever a pipeline event occurs. Because the file lives in version control, your pipeline is managed as code. That means you can review changes, compare versions, and evolve the delivery process alongside the application.

Your First .gitlab-ci.yml: A Minimal 3-Stage Pipeline

A great beginner exercise is a simple pipeline that does not depend on a real application yet. It just shows the structure of a GitLab pipeline.

Create .gitlab-ci.yml in the repository root with the following content:

stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  image: alpine:3.20
  script:
    - echo "Building the application"
    - echo "Pretend a build happens here"

test-job:
  stage: test
  image: alpine:3.20
  script:
    - echo "Running tests"
    - echo "Pretend automated tests run here"

deploy-job:
  stage: deploy
  image: alpine:3.20
  script:
    - echo "Deploying the application"
    - echo "Pretend a deployment happens here"

This is not a production pipeline, but it is a complete working example of a 3-stage GitLab pipeline.

What this first pipeline teaches you

It shows you the core building blocks:

  • a pipeline can have multiple stages
  • each stage can have one or more jobs
  • jobs execute shell commands from script
  • GitLab shows the result of each job in the UI
  • stages normally run in the listed order

Once this file is committed and pushed, GitLab should create a pipeline automatically.

Breaking Down Each Part of the YAML

YAML is indentation-sensitive, so learning the structure matters.

stages

The stages array defines the order of major pipeline phases.

stages:
  - build
  - test
  - deploy

In this example, GitLab will try to run all build-stage jobs first. If that stage succeeds, it will move to the test stage. If test succeeds, it will move to deploy.

Think of stages as signposts that organize the journey.

Job names

The names build-job, test-job, and deploy-job are job identifiers.

build-job:

A job name can be almost anything descriptive. Good names help you and your teammates quickly understand what each job does.

For example:

  • install-dependencies
  • run-unit-tests
  • deploy-staging

stage

The stage keyword assigns a job to one of the declared stages.

stage: test

If the stage name is misspelled or does not exist in the stages list, the pipeline will fail to validate or behave unexpectedly.

script

The script section is the actual list of commands that the runner executes.

script:
  - echo "Running tests"
  - echo "Pretend automated tests run here"

If any command in the script exits with a non-zero status, the job usually fails.

image

The image keyword tells GitLab which container image to use for the job runtime.

image: alpine:3.20

For a beginner demo, Alpine is a convenient lightweight Linux image. In real projects, you often choose images that already contain the tools you need, such as:

  • node:20
  • python:3.12
  • golang:1.24
  • maven:3.9-eclipse-temurin-21

Why GitLab Uses Runners

A pipeline definition lives in GitLab, but GitLab itself does not magically execute your shell commands. That is the job of a GitLab Runner.

A runner is an agent that picks up CI/CD jobs and runs them in an execution environment.

What a runner actually does

When a pipeline starts, GitLab looks for an available runner that is allowed to execute the job. The runner then:

  1. fetches the job definition
  2. prepares the environment
  3. checks out the repository code
  4. runs the script commands
  5. streams logs back to GitLab
  6. reports success or failure

Without a runner, the pipeline definition exists, but nothing can actually run.

Shared Runners vs Self-Hosted Runners

When learning GitLab Runner basics, you will encounter two common models.

Shared runners

Shared runners are provided for multiple projects, usually managed centrally by GitLab.com or by your organization. They are the easiest option for beginners because you often do not need to install anything yourself.

Benefits of shared runners:

  • fast setup
  • no infrastructure to manage
  • good for standard builds and tests
  • ideal for learning and small projects

Possible limits:

  • usage quotas on some plans
  • less control over the environment
  • not suitable for private internal network access

Self-hosted runners

Self-hosted runners are runners you install and manage yourself. They are useful when you need:

  • access to private infrastructure
  • custom tools or hardware
  • tighter performance control
  • specialized security or compliance boundaries

They require more maintenance, but they offer more flexibility.

How GitLab Matches a Job to a Runner

A beginner question often sounds like this: How does GitLab decide where a job runs?

GitLab uses runner availability, scope, and optionally tags.

Shared pool matching

If your project is allowed to use shared runners and a job has no special tag requirement, GitLab can send that job to the shared runner pool.

Tagged runner matching

Runners can be registered with tags such as:

  • docker
  • node
  • linux
  • production

A job can request matching tags:

test-node:
  stage: test
  image: node:20
  tags:
    - docker
    - node
  script:
    - npm ci
    - npm test

GitLab will look for a runner that has all required tags. If none are available, the job will stay pending.

Why tags matter

Tags help make sure the right job lands on the right infrastructure. For example, a deployment job that needs VPN access or cloud credentials should not run on an arbitrary general-purpose runner.

Triggering a Pipeline

There are several ways to start a pipeline in GitLab.

On push

The most common trigger is a Git push. If .gitlab-ci.yml exists and the branch is configured normally, a pipeline is created after the push.

On merge request

Pipelines can also run when a merge request is opened or updated. These are especially valuable because they show whether a proposed change passes validation before it is merged.

Manually

You can trigger a pipeline manually from the GitLab UI. This is useful for reruns, ad hoc checks, or deployment jobs that should only run on demand.

On schedule

GitLab can run pipelines on a schedule, such as every night or every Monday morning. Scheduled pipelines are useful for routine maintenance tasks, dependency scans, or regular integration checks.

Why multiple triggers are helpful

Different triggers support different workflows:

  • push pipelines give fast developer feedback
  • merge request pipelines protect code review quality
  • manual pipelines support controlled release actions
  • scheduled pipelines catch issues over time

Pushing Your First Pipeline

Once your .gitlab-ci.yml file exists, commit and push it.

A typical local flow looks like this:

git add .gitlab-ci.yml
git commit -m "Add first GitLab CI pipeline"
git push origin main

After the push, open the project in GitLab and navigate to the pipeline view.

Watching the Pipeline Run in the GitLab UI

GitLab provides a visual pipeline interface that is beginner-friendly once you know what to look for.

Pipeline list view

In the Build > Pipelines area, you can usually see:

  • pipeline ID
  • branch or commit reference
  • status icon
  • author
  • start time
  • duration

This is the top-level view of what happened.

Pipeline graph view

Click into a pipeline to see the stage-by-stage layout. This view helps you understand which jobs ran in which order and where a failure happened.

Job details and logs

Click a job name to open its live or completed log output. This is where you see the commands that ran and the exact output they produced.

Green, red, and other statuses

The status colors communicate quickly:

  • green usually means success
  • red means failure
  • yellow or orange often indicates running or warning-like states
  • gray may indicate skipped, pending, or manual states depending on the context

Reading these statuses becomes second nature once you use pipelines regularly.

Reading Job Logs

Job logs are one of the most important beginner skills in GitLab CI/CD.

What the output usually includes

A job log often shows:

  • runner preparation steps
  • repository checkout
  • commands from the script section
  • command output
  • any error messages
  • final job status

For example, if your script says:

script:
  - npm ci
  - npm test

then the job log will show the output from both commands.

How to think about a failing log

When a job fails, ask these questions:

  1. Which command failed?
  2. Did the environment have the tools it needed?
  3. Did a file or dependency go missing?
  4. Was the YAML correct but the script logic wrong?
  5. Did the job run on the expected branch or runner?

The first meaningful error message is often more useful than the final one. Beginners sometimes scroll straight to the bottom, but the root cause is often earlier in the output.

Common First-Time Mistakes

Almost everyone hits a few small issues while creating a first pipeline.

Wrong YAML indentation

YAML uses spaces to define structure. A misplaced indent can break the file.

For example, this is wrong:

build-job:
stage: build
  script:
    - echo "Oops"

The keys must be indented correctly under the job name.

Wrong stage name

If a job says stage: tests but the stages list only contains test, GitLab cannot place the job correctly.

Forgetting to push .gitlab-ci.yml

Creating the file locally is not enough. GitLab only sees it after you commit and push it.

Using the wrong image

If your job runs npm test but the image is alpine:3.20, npm may not be available unless you install it manually. Pick an image that already contains the tools you need.

Assuming every project has a runner

If no suitable runner is available, the job may remain pending forever. Check whether shared runners are enabled or whether your project requires a specific tagged runner.

Editing the file in the wrong location

Remember that the main pipeline file belongs in the root of the repository.

Building Up to a Real Pipeline: Add a Node.js Test Job

Once the echo-based demo makes sense, the next step is to run something real.

Imagine a Node.js project with a package.json and a test script. A basic test pipeline might look like this:

stages:
  - build
  - test
  - deploy

build-app:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build

test-app:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm test

deploy-preview:
  stage: deploy
  image: alpine:3.20
  script:
    - echo "Deploy step placeholder"

This example introduces a practical improvement: the build and test jobs now run in node:20, which already includes Node.js and npm.

Why npm ci is often preferred

In CI, npm ci is usually better than npm install because it uses the lock file predictably and is designed for automated environments.

Why the deploy step is still a placeholder

When you are learning, it is smart to separate pipeline structure from deployment complexity. First get the pipeline green. Then add real deployment logic later.

A Complete Working .gitlab-ci.yml Example with Annotations

Here is a beginner-friendly working example that combines the key ideas from this tutorial:

stages:
  - build
  - test
  - deploy

build-app:
  stage: build
  image: node:20
  script:
    - echo "Installing dependencies"
    - npm ci
    - echo "Building application"
    - npm run build

run-tests:
  stage: test
  image: node:20
  script:
    - echo "Installing dependencies for tests"
    - npm ci
    - echo "Running test suite"
    - npm test

deploy-demo:
  stage: deploy
  image: alpine:3.20
  script:
    - echo "This is where a real deployment command would run"

How to read this file

  • stages declares the order: build, then test, then deploy.
  • build-app compiles the project.
  • run-tests validates it.
  • deploy-demo stands in for a later real deployment.
  • Each job defines its own runtime image.
  • Every script section contains the shell commands to execute.

This is still small, but it looks and feels like a real pipeline rather than a toy example.

The Pipeline Status Badge in Your README

One nice beginner feature in GitLab is the pipeline status badge.

A badge is a small status image you can place in your README.md to show whether the default branch pipeline is passing.

Why it is useful:

  • teammates instantly see project health
  • reviewers can quickly check whether the main branch is green
  • open source visitors get immediate signal about repository maintenance

The exact badge URL comes from the GitLab UI, but conceptually it turns the pipeline result into a visible quality indicator right on the project homepage.

Debugging a Failing Job Step by Step

Sooner or later, your first “real” pipeline will fail. That is normal.

A calm debugging process helps a lot.

Step 1: Confirm the failing job

Look at the pipeline graph and identify which specific job failed.

Step 2: Open the job log

Read the log carefully from top to bottom. Search for the first genuine error.

Step 3: Check the runtime image

Does the image contain the tool you are calling? For example, npm test needs Node and npm.

Step 4: Compare the command locally

If the same command fails locally, the issue may be in the code or scripts rather than GitLab.

Step 5: Fix one thing at a time

Do not change ten things at once. Small, deliberate edits are easier to reason about.

Step 6: Push again and observe

Each new push creates another feedback cycle. That loop is the learning engine of CI/CD.

Best Beginner Mindset for GitLab CI/CD

When you are just starting, remember these principles:

  • start small
  • get a green pipeline first
  • prefer clear job names
  • use the right runtime image
  • read logs carefully
  • treat .gitlab-ci.yml like application code
  • evolve the pipeline step by step

The goal is not to build the perfect enterprise pipeline on day one. The goal is to understand the mechanics and create a reliable foundation.

Final Takeaway

To create your first GitLab CI/CD pipeline, you place a .gitlab-ci.yml file in the repository root, define stages and jobs, choose appropriate images, commit the file, push it, and watch GitLab trigger the pipeline. A runner executes the work, the UI shows the status, and job logs tell you exactly what happened.

Once you are comfortable with this flow, you are ready to learn more advanced topics such as artifacts, caching, variables, rules, environments, and deployment strategies. But the core idea stays simple:

push code, let the pipeline run, read the feedback, improve the system.

Knowledge Check

Exercise

Question 1: Pipeline File Location

Where should the main .gitlab-ci.yml file be placed for a standard GitLab pipeline?

Exercise

Question 2: GitLab Runners

What is the role of a GitLab Runner?

Exercise

Question 3: First Debugging Step

When a GitLab CI job fails, what is the best first place to investigate?

PreviousPrev
Next

Continue Learning

Introduction to CI/CD

Learn what CI/CD means, why every software team needs it, how Continuous Integration, Continuous Delivery, and Continuous Deployment differ, and what a modern pipeline looks like from commit to production.

12 min read·Easy

Stages, Jobs, Artifacts & Cache

Understand how GitLab pipelines are organized with stages and jobs, how artifacts move files between jobs, and how cache speeds up repeated work.

6 min read·Easy

GitLab CI Stages and Jobs

Master GitLab CI/CD stages and jobs, learn how to define pipeline stages, write job scripts, use rules and only/except conditions, control job ordering, and run parallel jobs.

12 min read·Easy

Explore Related Topics

Do

Docker Tutorials

Build and push Docker images in your pipelines

Gi

Git Tutorials

Trigger CI/CD pipelines from Git events

Try the Tool

YAML Validator

Validate GitHub Actions and CI/CD pipeline YAML files.

Cron Parser

Build and understand scheduled workflow cron expressions.

On This Page

Prerequisites1. A GitLab account2. A repository3. Permission to run pipelines4. Basic Git workflow knowledgeWhat Is `.gitlab-ci.yml`?Where the file goesWhy the file mattersYour First `.gitlab-ci.yml`: A Minimal 3-Stage PipelineWhat this first pipeline teaches youBreaking Down Each Part of the YAML`stages`Job names`stage``script``image`Why GitLab Uses RunnersWhat a runner actually doesShared Runners vs Self-Hosted RunnersShared runnersSelf-hosted runnersHow GitLab Matches a Job to a RunnerShared pool matchingTagged runner matchingWhy tags matterTriggering a PipelineOn pushOn merge requestManuallyOn scheduleWhy multiple triggers are helpfulPushing Your First PipelineWatching the Pipeline Run in the GitLab UIPipeline list viewPipeline graph viewJob details and logsGreen, red, and other statusesReading Job LogsWhat the output usually includesHow to think about a failing logCommon First-Time MistakesWrong YAML indentationWrong stage nameForgetting to push `.gitlab-ci.yml`Using the wrong imageAssuming every project has a runnerEditing the file in the wrong locationBuilding Up to a Real Pipeline: Add a Node.js Test JobWhy `npm ci` is often preferredWhy the deploy step is still a placeholderA Complete Working `.gitlab-ci.yml` Example with AnnotationsHow to read this fileThe Pipeline Status Badge in Your READMEDebugging a Failing Job Step by StepStep 1: Confirm the failing jobStep 2: Open the job logStep 3: Check the runtime imageStep 4: Compare the command locallyStep 5: Fix one thing at a timeStep 6: Push again and observeBest Beginner Mindset for GitLab CI/CDFinal TakeawayKnowledge Check