Getting Started with GitLab CI/CD
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-dependenciesrun-unit-testsdeploy-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:20python:3.12golang:1.24maven: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:
- fetches the job definition
- prepares the environment
- checks out the repository code
- runs the script commands
- streams logs back to GitLab
- 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:
dockernodelinuxproduction
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
scriptsection - 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:
- Which command failed?
- Did the environment have the tools it needed?
- Did a file or dependency go missing?
- Was the YAML correct but the script logic wrong?
- 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
stagesdeclares the order: build, then test, then deploy.build-appcompiles the project.run-testsvalidates it.deploy-demostands 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.ymllike 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
Question 1: Pipeline File Location
Where should the main .gitlab-ci.yml file be placed for a standard GitLab pipeline?
Question 2: GitLab Runners
What is the role of a GitLab Runner?
Question 3: First Debugging Step
When a GitLab CI job fails, what is the best first place to investigate?