Sign in
← CI/CD: hands-on
10 min · easy · Lesson 2 of 10

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:

  1. it sees the new commit and the .gitlab-ci.yml file;
  2. it queues the pipeline;
  3. a free runner picks up the job and spins up a clean Linux container;
  4. it clones your repository into that container;
  5. it runs the commands from script in order;
  6. 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

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.

Done reading?

Mark the lesson as read to move your track progress forward.

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson cicd-practice-02-first-pipeline