GitLab Self-Hosting Made Easy: GitLab + Runner Setup with Docker

install-gitlab-with-docker

Running your own GitLab instance locally is one of the best ways to learn and experiment with CI/CD, version control, and DevOps workflows without relying on external services. Whether you’re a beginner exploring GitLab for the first time or a professional testing pipelines locally, a self-hosted GitLab setup gives you complete control over your environment.

In this article, we’ll set up a self-hosted GitLab instance and a GitLab Runner on your local machine using Docker Compose. Using containers makes the setup simple, portable.

By the end of this guide, you’ll have a fully functional local GitLab environment capable of running CI/CD pipelines.

Before we begin, this guide assumes you have a basic understanding of Git and containers.

Prerequisites

Before starting, ensure the following tools are installed on your local machine:

  • Docker
  • Docker Compose

Setting Up GitLab Community Edition with Docker Compose

Now that the prerequisites are out of the way, let’s move on to the actual setup.

For this guide, I’m using GitLab Community Edition (CE), which is open-source and perfectly suited for self-hosting on a local machine.

Choosing the GitLab Version

GitLab provides official Docker images through Docker Hub. From the available tags, I selected version 18.5.2-ce.0 . You can browse the available images here (Dockerhub)

Project Directory Structure

To keep things clean and organized, I created a dedicated project folder named gitlab. Inside it, I separated gitlab-ce and gitlab-runner into their own directories, below is my folder structure:

gitlab/
├── gitlab-ce/
│   └── docker-compose.yml
└── gitlab-runner/
    └── docker-compose.yml
folder-structure

Docker Compose Configuration for GitLab CE

Create a shared Docker network, so that gitlab and gitlab runner remain in the same network. You only need to do this once.

docker network create gitlab-net

Inside the gitlab-ce directory, I created a basic docker-compose.yml file based on the official GitLab Docker documentation. Below is the compose file I used:

services:
  gitlab:
    image: gitlab/gitlab-ce:18.5.2-ce.0
    container_name: gitlab
    networks:
      - gitlab-net
    restart: always
    hostname: 'localhost'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://localhost:8929'
        gitlab_rails['gitlab_shell_ssh_port'] = 2424
        nginx['listen_port'] = 8929
        nginx['listen_https'] = false
    ports:
      - '8929:8929'
      - '2424:22'
    volumes:
      - gitlab_config:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    shm_size: '256m'

volumes:
  gitlab_config:
  gitlab_logs:
  gitlab_data:

networks:
  gitlab-net:
    external: true  

Understanding the GitLab Docker Compose Configuration

GitLab Omnibus Configuration

GitLab’s Omnibus image allows configuration using Ruby-style settings via the GITLAB_OMNIBUS_CONFIG environment variable. You can read more about it in the official documentation

  • external_url ‘http://localhost:8929
    Sets the URL GitLab uses to generate links in the UI, emails, and repository clone URLs.
  • gitlab_shell_ssh_port = 2424
    Tells GitLab that SSH access will happen on port 2424 instead of the default 22. This will modify the port in the gitlab. ssh clone url
  • nginx[‘listen_port’] = 8929
    Configures GitLab’s internal NGINX server to listen on port 8929.
  • nginx[‘listen_https’] = false
    Disables HTTPS for now, keeping the setup simple for local development.
    (HTTPS can be added later using a reverse proxy like NGINX or Traefik.)
Port Mappings
  • 8929:8929
    Exposes GitLab’s web interface on host systems port 8929
  • 2424:22
    Maps container SSH port 22 to host port 2424, matching the SSH port configured earlier.

This setup allows:

  • Web UI → http://localhost:8929
  • Git over SSH → ssh -p 2424
Persistent Volumes

These volumes ensure data persistence:

  • config → GitLab configuration files
  • logs → Application and system logs
  • data → Repositories, CI artifacts, database files, and uploads

Even if the container is removed or recreated, your GitLab data remains intact. These folder will be created automatically in the gitlab-ce folder when you start the container initially.

Starting GitLab

From inside the gitlab-ce directory, bring up GitLab using:

docker compose up -d

The first startup may take a few minutes since GitLab needs to initialize services and perform internal setup.

Once it’s ready, you can access GitLab at:

http://localhost:8929
gitlab-home-page

the default username is root and the default password can be obtained by running the following command:

docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

After login you’ll be presented with the homepage

self-hosted-gitlab-homepage

At this point, your self-hosted GitLab instance is up and running. In the next step, we’ll set up GitLab Runner and connect it to this GitLab instance to enable CI/CD pipelines.

Setting Up a GitLab runner

Docker Compose Configuration for GitLab Runner

create the docker-compose.yaml file in the gitlab-runner folder like we discussed above.

services:
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    container_name: gitlab-runner
    networks:
      - gitlab-net
    restart: always
    privileged: true  # Needed if you want to use Docker executor
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # Allow Docker builds
      - gitlab_runner_config:/etc/gitlab-runner
    environment:
      - TZ=Asia/Kolkata

volumes:
  gitlab_runner_config:

networks:
  gitlab-net:
    external: true  

Starting GitLab-Runner

From inside the gitlab-runner directory, bring up GitLab-Runner using:

docker compose up -d

Registering the runner with Gitlab

Our Gitlab-Runner is now up and the next step is to connect the runner with our Gitlab instance, for that follow the below steps.

1. Click on the admin icon on the home page

admin-area-on-gitlab

2. Under CI/CD select the option runners.

cicd-settings-gitlab

3. Select the option, create instance runner

create-instance-runner-button

4.Give it a tag and check the run untagged jobs checkbox and click create runner

create runner

5. Copy the runner authentication token ( Keep it handy, you need it later)

copy authentication token

6. Now we’ll configure the runner, for that go to the terminal and run the following command to exec into the container

docker exec -it gitlab-runner gitlab-runner register

7. It will prompt you to enter the gitlab instance url, in this case the runner and gitlab is on the same docker network so the url will be http://container-name:port, in this case it is

http://gitlab:8929

8. In the next step enter the registration token you copied in step 5.

9. Give a name for your runner

10. Enter an executor shell, in this case it is: docker

11. Enter the default docker image: docker:stable

If you go back to the gitlab runner page you can see that the runner is now online

Gitlab runner online

Conclusion

Self-hosting your own GitLab instance and GitLab Runner using Docker gives you complete control over your CI/CD workflows, source code, and infrastructure. With Docker and Docker Compose, the setup becomes reproducible, portable, and easy to manage—even on a local machine. This approach is ideal for learning, experimenting, and building a solid foundation in GitLab CI/CD without relying on third-party services.

Whether you’re a DevOps beginner sharpening your skills or a team looking for a lightweight self-hosted CI/CD solution, this setup is a great starting point. Keep experimenting, optimize your pipelines, and gradually evolve your GitLab deployment toward a more production-ready architecture.