First job: three lines and a green checkmark
We write a minimal .gitlab-ci.yml, push it, and watch GitLab spin up a container and run the command on its own.
Write a minimal .gitlab-ci.yml, push it, and watch GitLab spin up a container on its own and
run a command. You need the repository on gitlab.com from the previous lesson.
The file that runs everything
CI in GitLab is turned on by one file in the root of the repository: .gitlab-ci.yml. No buttons,
no server setup. Drop the file in, push it, and GitLab picks it up on its own.
Create the file right in the project folder:
hello:
script:
- echo "Hello, CI!"
Three lines, and that's already a working pipeline. hello is the job's name, pick any name you
like. A job (a unit of work in the pipeline) consists of a name and a list of commands. script
lists the commands the runner will execute.
Push it, and here's what happens next
git add .gitlab-ci.yml
git commit -m "add first pipeline"
git push
From here, GitLab does the rest:
- it sees the new commit and the
.gitlab-ci.ymlfile; - it queues the pipeline;
- a free runner picks up the job and spins up a clean Linux container;
- it clones your repository into that container;
- it runs the commands from
scriptin order; - it shuts down the container and reports the status.
A runner is a machine that executes jobs. On gitlab.com there are shared runners (instance
runners), so you don't need to set up your own. If a job doesn't specify an image, gitlab.com's
hosted Linux runners default to ruby:3.1. We'll cover choosing your own image later.
The runner gets the code from the commit, not from your folder. A file you created but didn't commit and push doesn't exist as far as GitLab is concerned.
Watch the result
In the project, open Build > Pipelines. There's a row with your commit and its status.
The first few seconds it's Pending (waiting for a free runner), then Running, then Passed.
A simple job this size usually finishes in tens of seconds, of which the echo command itself
takes milliseconds. The rest of the time goes into spinning up the container and cloning the
repository.
Click the job to see the full log: how the runner picked the image, cloned the code, ran
echo "Hello, CI!", and finished with Job succeeded.
Green and red
Every command in script returns an exit code: zero means success, any other number means
failure. As soon as one command returns non-zero, the job fails, and the remaining commands don't
run. This is the usual shell logic with set -e.
Try it yourself: add a second command that's guaranteed to fail.
hello:
script:
- echo "Hello, CI!"
- cat no-such-file.txt
The pipeline turns red, the log shows No such file or directory and
ERROR: Job failed: exit code 1. Remove the extra line, push again, and the status goes back to
green.
A red pipeline means CI is doing its job. That's the whole point: failing early, before broken code reaches production.
A red log is easier to read from the bottom. The runner prints every command with a $ sign
before running it. The last such line before ERROR: Job failed is the command that failed.
If the pipeline doesn't show up
The file must be named exactly .gitlab-ci.yml, with a leading dot, and sit in the root of the
repository. GitLab won't pick up gitlab-ci.yml or .gitlab-ci.yaml by default. You can set a
different name or path in Settings > CI/CD > General pipelines, but don't do that
without a reason.
A long Pending usually means there are no free runners, or you've run out of free minutes:
Settings > CI/CD > Runners shows what's available to the project. And if the account
hasn't passed identity verification, the job doesn't hang, it fails with the message
Identity verification is required in order to run CI jobs.
YAML is sensitive to indentation. You can check your config before pushing in Build > Pipeline editor: it validates the syntax against the GitLab CI schema and highlights errors.
Common mistakes
- The file was created but not committed or pushed. No pipeline will appear, because the runner only sees what's in the commit on gitlab.com.
- A typo in the file name (
gitlab-ci.yml,.gitlab-ci.yaml) or the file isn't in the root. scriptlines without a leading-, or with indentation that doesn't match the neighboring lines. The Pipeline editor will flag this before you push.- Expecting the remaining commands to run after a failed one. They won't: the job stops at the first error.
What to remember
.gitlab-ci.yml in the root of the repo is the only thing you need to run CI.
A job is a name plus a list of commands in script. Every job runs in a fresh container, which
dies right after the job finishes.
Status comes from exit codes: the first command with a nonzero code stops the job and turns the pipeline red.
Next, we'll split the work into stages and order them.
What you'll need for the tasks
The tasks are done locally, without gitlab.com. The lab environment creates a git repository
project/, and the setup/run-job.sh script repeats the runner's steps: it takes the latest
commit, puts it in a clean alpine:3.20.3 container, and runs script in order. This is a
simplified simulation: a real pipeline only runs after git push.
- create
.gitlab-ci.ymlwith a job and ascriptlist; git -C project status,git -C project add,git -C project commit;bash setup/run-job.sh <job_name>and reading the log: lines starting with$ …,Job succeeded,ERROR: Job failed: exit code N.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson cicd-practice-02-first-pipeline
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress