DEV Community

Cyrille Sepele
Cyrille Sepele

Posted on • Originally published at rocketrunner.io

GitLab CE Comes Without a Runner: Why Nothing Executes Your Pipelines

You installed GitLab Community Edition, pushed a .gitlab-ci.yml, and watched
the pipeline sit at pending until it went grey. No error, no failed job,
nothing in the logs worth reading.

Nothing is broken. Your instance has no runners, and it never had any.

Why a fresh instance has none

GitLab is two things that people assume are one thing. There is the application:
repositories, issues, merge requests, the CI/CD system that reads your
.gitlab-ci.yml and builds a pipeline out of it. And there is GitLab Runner: a
separate program, on a separate machine, that actually executes jobs.

The Omnibus package installs the first. It does not install the second, and it
does not come with any machines to run it on.

The confusion comes from GitLab.com, where shared runners are switched on by
default and most people's first experience of CI is that it simply works. That
shared fleet is hardware GitLab owns and operates as part of their hosted
service. It is not part of the software you downloaded, so it does not come
across when you run your own instance.

So on a self-managed install, GitLab will happily accept your pipeline
definition, parse it, create the jobs, and queue them. Then it waits for a
runner to ask for work. If no runner ever asks, the jobs wait indefinitely.

Confirming it in thirty seconds

Go to Admin Area → CI/CD → Runners on your instance. If the list is empty,
that is your answer.

For a single project, Settings → CI/CD → Runners shows the same thing scoped
narrower. A fresh instance shows nothing in either place.

From the command line on the GitLab server:

sudo gitlab-rails runner "puts Ci::Runner.count"
Enter fullscreen mode Exit fullscreen mode

If that prints 0, no runner has ever been registered against this instance.

One thing worth ruling out at the same time: a job can also sit pending when
runners do exist but none of them match the job's tags. If your runner list is
not empty, check whether your jobs specify tags: that no runner carries. That
is a different problem with a different fix, and it is the second most common
cause after this one.

Your three ways out

Install a runner yourself

The traditional answer. Take a machine, install the gitlab-runner package,
register it against your instance with a token from the runners page, and pick
an executor. The Docker executor is the usual choice because it gives each job a
clean container.

This is genuinely the cheapest option on day one, and if you have a spare server
and one project that never changes, it may stay that way. Budget an afternoon
for the first one, less if you have done it before.

The cost arrives later, and it arrives quietly. Runner versions drift out of
support. Disks fill with old build caches until a job fails for reasons that
look nothing like a disk problem. The machine accumulates undocumented local
changes, and eventually nobody is quite sure how to rebuild it. None of this is
hard, exactly. It is just permanent.

Build a runner fleet

At real scale you stop managing machines individually and start managing a
system: the Kubernetes executor, or Docker Machine autoscaling, defined in
Terraform, with runners created and destroyed on demand.

If you already run Kubernetes, this is probably the right answer and you should
skip the rest of this post. It is worth being clear-eyed that it is a platform
project rather than a task, with a platform project's ongoing maintenance. Teams
regularly underestimate this by a factor of several because the first working
version comes together quickly.

Rent a dedicated runner

Someone else provisions the machine, installs the runner, registers it against
your instance, and keeps it patched. You get a single-tenant VM that behaves
like any other runner on your runners page.

The trade you are making: you stop owning the machine's lifecycle, and you
accept that build compute runs outside your network. Which brings us to the part
that matters most.

The requirement nobody mentions upfront

If you rent runners, your GitLab instance has to be reachable from that runner
over the public internet, on https, with a valid certificate.

That is not a vendor preference, it is how the protocol works. The runner polls
your instance for jobs, and registration calls your instance's API. Both need a
public hostname that resolves to a public address.

That covers most self-managed instances at companies and agencies, which sit on
a real domain behind a real certificate. It does not cover an air-gapped
instance, a VPN-only instance, or one bound to a private network. If that is
you, the answer is a runner inside your perimeter, and no hosted service can
change that.

Better to know in paragraph twenty than after signing up.

Being straight about what a runner sees

A runner clones your source in order to build it. That is true of every runner
ever created, including one you install yourself on your own hardware. Anyone
implying otherwise is selling something.

So the useful question is not whether build compute touches your code. It is
what kind of machine touches it, and who else is on that machine.

A shared runner is multi-tenant. You did not choose the hardware, you cannot see
what ran on it before your job, and you cannot point at it in an audit. A
dedicated VM is yours alone, in a region you picked, destroyed when you delete
it. That is a posture you can describe to a client security review, which a
shared runner never was.

Which one is actually right for you

If you self-host GitLab because a policy requires source to stay inside your
network, and that policy extends to build compute, install your own runners.
That is the whole answer and the rest is noise.

If you self-host for the more common reasons, keeping control of your data,
avoiding SaaS lock-in, satisfying a client contract about where the repository
lives, then the repository decision and the build compute decision are separate,
and most teams only ever made the first one deliberately.

Agencies feel this hardest, because the problem repeats per client. Eight
clients on their own instances means eight runner setups, eight sets of
credentials, eight machines to patch, and an awkward question at the end of
every engagement about what is still sitting on that box.

The short version

Your pipelines are pending because a self-managed GitLab ships with no runners,
by design. Install one, build a fleet, or rent one, and pick based on how much
of your attention you want the choice to keep asking for after the first green
pipeline.

If renting sounds right, runners for self-hosted GitLab explains
how connecting your own instance works, including what stays on your side.

Top comments (0)