Cloud Native 18 min read

Setting Up a Local CI/CD Stack with Harbor, GitLab, and Jenkins Using Docker Compose

This guide walks through configuring a local Docker/Compose environment that provides accessible Harbor, GitLab (with a shared Runner), and Jenkins, demonstrates a push/pull test on Harbor, runs a minimal GitLab CI pipeline, and validates a simple Jenkins pipeline, while covering prerequisites, host configuration, installation steps, verification, troubleshooting, and cleanup.

Xike
Xike
Xike
Setting Up a Local CI/CD Stack with Harbor, GitLab, and Jenkins Using Docker Compose

Goal

Set up a local Docker/Compose environment that provides reachable Harbor, GitLab (with Runner), and Jenkins; perform a push/pull test on Harbor, run a minimal .gitlab-ci.yml pipeline, and execute a minimal Jenkins pipeline.

Prerequisites

Docker Engine ≥ 20.10.10

Docker Compose V1 (≥ 1.18.0) or V2 plugin

Memory ≥ 8 GB (GitLab Omnibus is memory‑heavy)

Disk ≥ 30 GB free for Harbor packages, images, and data

CPU 4 cores recommended

Linux host is preferred for production; macOS Docker Desktop works for learning only.

Host and insecure‑registry configuration

Add the following entries to /etc/hosts:

127.0.0.1 harbor.local gitlab.local jenkins.local

Configure Docker daemon to trust the Harbor registry by adding harbor.local:18060 to /etc/docker/daemon.json:

{
  "insecure-registries": ["harbor.local:18060"]
}

Restart Docker after editing the file.

Harbor installation

Run the one‑click installer (offline mode is recommended):

chmod +x infra/harbor/install.sh
./infra/harbor/install.sh

The installer uses infra/harbor/harbor.yml.overlay to set:

Hostname: harbor.local HTTP port: 18060 (HTTPS is commented out for the learning environment)

Admin user: admin / Harbor12345 (written only on first start)

Data directory: ~/cicd-data/harbor Installation files live in ~/cicd-data/harbor-installer/harbor. Start, stop, or restart with:

cd ~/cicd-data/harbor-installer/harbor
docker compose start   # or stop

Verify the setup with the smoke script:

chmod +x infra/smoke/harbor-push-pull.sh
./infra/smoke/harbor-push-pull.sh

The script logs in, tags, pushes, pulls, and runs the image hello‑harbor:demo. After a successful run the image appears in the Harbor UI under the library project.

GitLab and Runner

Start GitLab with its compose file:

chmod +x infra/scripts/start-gitlab-jenkins.sh
./infra/scripts/start-gitlab-jenkins.sh

GitLab will be reachable at http://gitlab.local:18061 (HTTP) and SSH on port 2224. The initial root password is stored in /etc/gitlab/initial_root_password for 24 hours; retrieve it with:

docker exec -it cicd-gitlab grep 'Password:' /etc/gitlab/initial_root_password
# or
./infra/scripts/show-credentials.sh

To reduce memory consumption, disable Prometheus and lower Puma/Sidekiq workers in the compose file.

Register a shared Runner:

Log in to GitLab as root, go to Admin Area → CI/CD → Runners → New instance runner .

Check Run untagged jobs (convenient for the learning series).

Copy the generated Runner authentication token (e.g., glrt-…).

Export the token and run the registration script:

export GITLAB_RUNNER_TOKEN='glrt-xxxxxxxx'
chmod +x infra/runner/register.sh
./infra/runner/register.sh

The script configures the Runner to reach the host via http://host.docker.internal:18061 (Docker Desktop) or adds extra_hosts: ["host.docker.internal:host-gateway"] for Linux Docker Engine ≥ 20.10.

Validate the Runner:

docker exec cicd-gitlab-runner gitlab-runner verify
docker exec cicd-gitlab-runner gitlab-runner list

Run a minimal pipeline. Create an empty project (e.g., cicd-smoke) and add the following .gitlab-ci.yml at the repository root:

hello:
  stage: smoke
  image: docker.m.daocloud.io/library/alpine:3.20
  script:
    - echo "gitlab-ci-ok"

Push the file; the pipeline should turn green and the job log must contain gitlab-ci-ok.

Jenkins

Start Jenkins with its compose file:

source infra/prereq/env.sh
cp infra/jenkins/.env.example infra/jenkins/.env
cd infra/jenkins && docker compose up -d

Open http://jenkins.local:18062. Retrieve the initial admin password:

docker exec cicd-jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Complete the setup wizard by installing the suggested plugins, creating an admin user, and setting the instance URL to http://jenkins.local:18062/.

Create a new Pipeline job named hello-smoke and paste the following Jenkinsfile (stored at infra/smoke/jenkins/hello.Jenkinsfile):

pipeline {
  agent any
  stages {
    stage('Smoke') {
      steps {
        echo 'jenkins-ok'
        sh 'uname -a || true'
      }
    }
  }
}

Build the job; the console output should show jenkins-ok. This article does not cover Docker‑in‑Jenkins or image pushes; it only demonstrates that an independent CI server can run a basic pipeline.

Connectivity checklist

Host → Harbor: push/pull script succeeds.

GitLab → Runner → Job: minimal .gitlab-ci.yml pipeline green.

Host → Jenkins: minimal pipeline green.

Runner → Harbor: configuration ready for image pushes in the next article.

Troubleshooting

Docker pull timeout – verify image URLs in env.sh or set DOCKER_HUB_PROXY.

Harbor HTTPS errors – ensure harbor.local:18060 is listed in insecure-registries and Docker has been restarted.

Port conflicts (18060/18061/18062) – use lsof -i :18060 to find the occupying process, then adjust the ports in .env and update hosts and external_url accordingly.

GitLab not reachable – check logs with docker logs cicd-gitlab, ensure sufficient memory, and wait for the reconfigure step to finish.

Job stays pending – verify the Runner is registered and online; run gitlab-runner verify and make sure “Run untagged jobs” is enabled.

Job cannot pull image – confirm the image name matches a locally pulled image or pull it manually first.

Runner cannot reach GitLab – set GITLAB_URL_FOR_RUNNER to the host’s LAN IP or add extra_hosts = ["host.docker.internal:host-gateway"] for Linux.

Disk growth – run docker system df to clean up unused layers; consider Harbor GC after becoming familiar.

Cleanup and persistence

All data lives under ~/cicd-data: ~/cicd-data/harbor – Harbor images and metadata ~/cicd-data/harbor-logs – Harbor logs ~/cicd-data/harbor-installer – installer files and compose ~/cicd-data/gitlab – GitLab config, logs, data ~/cicd-data/runner – Runner config ( config.toml) ~/cicd-data/jenkins – Jenkins home directory

Running docker compose down stops the containers but does not delete the bind‑mount directories; remove them manually with rm -rf ~/cicd-data/… for a full reset.

Final acceptance checklist

Hosts file and Docker insecure-registries are configured. http://harbor.local:18060 logs in and the smoke script pushes/pulls successfully. http://gitlab.local:18061 logs in; Runner verification passes.

Minimal .gitlab-ci.yml pipeline succeeds. http://jenkins.local:18062 is unlocked and the minimal pipeline succeeds.

After all items are checked, the environment setup is complete and you can proceed to the next article, which builds a full end‑to‑end pipeline for Node, Python, and Spring Boot projects.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CI/CDDevOpsGitLabJenkinsDocker ComposeHarborLocal DevelopmentRunner
Xike
Written by

Xike

Stupid is as stupid does.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.