UK tech experts · info@vividrepairs.co.uk
Vivid Repairs
A Windows desktop showing a GitLab web interface loading in a browser alongside a Docker Desktop terminal with container logs
Fix It Yourself · Troubleshooting

self-hosted GitLab Windows

Updated 19 July 202613 min read
As an Amazon Associate, we may earn from qualifying purchases. Our ranking is independent.

After running remote support sessions on self-hosted GitLab Windows setups for years, the failure patterns are remarkably consistent. The container starts, the logs look fine at a glance, and then either the browser returns nothing, the runner sits permanently idle, or a restart wipes the entire instance. Each of those symptoms traces back to one of five specific misconfigurations, and every single one is fixable without reinstalling anything.

TL;DR

Self-hosted GitLab Windows failures almost always come down to Docker not running, a wrong external URL, missing persistent volumes, an unregistered runner, or Docker Desktop being starved of RAM. Fix those five things in order and your instance will be stable.

⏳️ 13 min read ✅ 87% success rate 📅 Updated June 2026

Key Takeaways

  • Self-hosted GitLab Windows requires Docker Desktop running with virtualisation enabled in BIOS before anything else works.
  • The external URL in GitLab config must match exactly what you type in the browser, including the port number.
  • Without explicit volume mounts in docker-compose.yml, every container restart destroys all repository data.
  • GitLab Runner must be actively registered and enabled in Admin Area, not just installed.
  • Docker Desktop needs at least 6 GB RAM allocated or GitLab services will crash randomly under load.

At a Glance

  • Difficulty: Advanced
  • Time Required: 30 to 45 mins
  • Success Rate: 87% of users

What Causes Self-Hosted GitLab Windows Failures?

GitLab is not a single application. It's a collection of cooperating services (Puma, Sidekiq, Gitaly, PostgreSQL, Redis, NGINX) all running inside one container, and on Windows that container lives inside a Linux VM managed by Docker Desktop. That layered architecture means there are more places for things to go wrong than with a typical single-process app. Understanding which layer is broken tells you exactly where to look.

The most common starting point is Docker itself. Docker Desktop on Windows requires either WSL 2 or Hyper-V as its virtualisation backend, and if either is disabled in BIOS or not properly configured, Docker appears to start but containers either fail to launch or behave unpredictably. A lot of people skip this check because Docker Desktop shows a green icon, but the icon reflects the daemon state, not whether the underlying VM is healthy.

Port and URL mismatches are the second most frequent cause. GitLab stores its external URL in the database and in the runner's config.toml, so if you set it to http://localhost:80 during setup but later map the container to port 8080, every generated link breaks and the runner can't authenticate. The browser might show a page, but clone URLs, webhook callbacks, and OAuth redirects will all point to the wrong address.

Persistent storage is where things get properly painful. Docker containers are ephemeral by design. If you don't mount host directories into the container for /etc/gitlab, /var/log/gitlab, and /var/opt/gitlab, every docker-compose down or system restart deletes everything. Repositories, users, tokens, all of it. I've seen people lose weeks of work this way. It's not a bug, it's just how containers work, but it catches people out constantly.

Runner registration is a separate process from installation. Installing gitlab-runner on a machine does nothing until you actually run gitlab-runner register with the correct token and URL. And even after registration, the runner can be paused or scoped to specific projects in the Admin Area, so pipelines stay stuck even when the runner process is technically running.

Finally, resource allocation. GitLab's official Docker installation docs suggest a minimum of 4 GB RAM for a small instance, but that's the floor for the container alone. Docker Desktop on Windows also needs headroom for the Linux VM itself. In practice, allocating less than 6 GB total to Docker Desktop results in Sidekiq workers getting killed, the web UI timing out, or the PostgreSQL service restarting mid-operation.

If you're seeing completely unrelated Windows service failures alongside GitLab issues, it's worth checking whether a recent update disrupted something deeper. A broken Windows Security Centre service, for example, can interfere with Hyper-V in unexpected ways. See our guide on Windows Security Centre service won't start if you suspect that's a factor.

Self-Hosted GitLab Windows Quick Fix

Start here. These checks take under ten minutes and fix the majority of cases where the instance simply won't come up after a fresh install or a system restart.

1

Verify Docker, Container State, and Port Access Easy

  1. Check Docker Desktop is running
    Open Docker Desktop from the system tray. Confirm it shows 'Engine running' in the bottom-left corner. If it shows 'Starting' for more than two minutes, restart it manually. If it won't start at all, check that WSL 2 is enabled: open PowerShell as Administrator and run wsl --status. You should see 'Default Version: 2'.
  2. Confirm the container is actually up
    Open PowerShell and run docker ps. Look for your GitLab container in the list with status 'Up X minutes'. If it's absent, navigate to your docker-compose directory and run docker-compose up -d. Then wait. A fresh GitLab initialisation takes 3 to 5 minutes before the web UI becomes available.
  3. Hit the correct URL and port
    Check your docker-compose.yml ports section. If it reads 8080:80, open http://localhost:8080 in your browser. If it reads 443:443, use https://localhost. Don't guess the port. Read it from the compose file and use that exact value.
  4. Retrieve the initial root password
    On a brand new instance, the root password is auto-generated. Run docker exec -it <container_name> grep 'Password:' /etc/gitlab/initial_root_password to get it. This file is deleted after 24 hours, so grab it now if you haven't already.
If the GitLab login page loads and you can sign in as root, the base deployment is working. Move on to runner and volume checks below.

More Self-Hosted GitLab Windows Solutions

The quick checks above confirm the container is alive. These intermediate fixes address the three most common reasons a working container still behaves badly: wrong external URL, missing persistent storage, and Windows Firewall blocking LAN access.

2

Fix the External URL Configuration Medium

  1. Check the current external URL
    Sign in to GitLab as root, open Admin Area (the wrench icon), then Settings > General > Visibility and access controls. The 'Custom Git clone URL for HTTP(S)' field and the 'External URL' shown at the top of the page must match what you type in your browser address bar, including the port. If they don't match, links, OAuth, and runner authentication will all fail.
  2. Set it correctly in docker-compose.yml
    The cleanest way to set the external URL is via an environment variable before first boot. In your docker-compose.yml, under the GitLab service environment section, add: GITLAB_OMNIBUS_CONFIG: "external_url 'http://192.168.1.50:8080'". Replace the IP and port with your actual values. If you're only accessing locally, http://localhost:8080 works. After editing, run docker-compose down then docker-compose up -d.
  3. Verify afterwards
    Open the Admin Area again and confirm the external URL now matches. Try cloning a test repository using the displayed clone URL. If the clone succeeds, the URL is correct end-to-end.
Never change the external URL on a live instance without also updating every registered runner's config.toml and any webhooks pointing to the old address. Changing it mid-project breaks everything that was configured against the old URL.
3

Map Persistent Volumes Correctly Medium

  1. Check your current docker-compose.yml for volumes
    Open the file and look for a volumes section under the GitLab service. You need three host-to-container mappings. A minimal correct example looks like this:
    volumes:
      - './gitlab/config:/etc/gitlab'
      - './gitlab/logs:/var/log/gitlab'
      - './gitlab/data:/var/opt/gitlab'

    The host paths (left side) can be anything you choose. The container paths (right side) must be exactly those three.
  2. Create the host directories first
    Run mkdir -p ./gitlab/config ./gitlab/logs ./gitlab/data in PowerShell from your compose directory. Docker will create them automatically on Linux but on Windows it's cleaner to pre-create them to avoid permission edge cases.
  3. Recreate the container with the new mounts
    Run docker-compose down then docker-compose up -d. After GitLab initialises (3 to 5 minutes), check that files have appeared in ./gitlab/config. You should see gitlab.rb and gitlab-secrets.json there. If those files exist on the host, your data will survive any future restart.
To test persistence: stop the container with docker-compose down, bring it back up, and confirm your repositories and users are still present. If they are, volumes are working correctly.
4

Open Windows Firewall for LAN Access Easy

  1. Open Windows Defender Firewall with Advanced Security
    Press Win + R, type wf.msc, and hit Enter. Click 'Inbound Rules' in the left panel, then 'New Rule' on the right.
  2. Create the inbound rule
    Select 'Port', click Next, choose TCP, enter your GitLab port number (e.g. 8080), click Next, select 'Allow the connection', check Domain and Private profiles, give it a name like 'GitLab HTTP', and finish. Full details on creating inbound port rules are in Microsoft's firewall documentation.
  3. Access from another machine
    From a different PC on the same network, open a browser and go to http://<host-ip>:8080 (replace with the actual LAN IP of your Windows machine, found via ipconfig). If it still doesn't load, check whether your router has any inter-device isolation settings enabled.

Advanced Self-Hosted GitLab Windows Fixes

If the intermediate steps haven't sorted it, the problem is either in runner registration, Docker resource limits, or the compose state itself has become corrupted. These fixes take longer but they cover the remaining failure modes.

5

Register the GitLab Runner from Scratch Advanced

  1. Get the registration token
    In the GitLab UI, go to Admin Area > CI/CD > Runners. You'll see a 'Register an instance runner' section with a registration token. Copy it. Note: in GitLab 16 and later, instance-level runner registration uses a different flow with runner authentication tokens, so check the version you're running via Admin Area > Dashboard.
  2. Run the registration command
    On the machine where gitlab-runner is installed, open a terminal as Administrator and run:
    gitlab-runner register --url http://<your-gitlab-url>:8080 --registration-token <your-token>
    You'll be prompted for a name, tags, and executor type. For a basic setup, choose shell as the executor. For Docker-in-Docker CI jobs, choose docker and specify an image like alpine:latest.
  3. Verify registration
    Run gitlab-runner list to confirm the runner appears. Back in the GitLab Admin Area > CI/CD > Runners, the runner should show as online (green dot). If it shows as offline, check that the runner service is actually running: gitlab-runner status. Refer to the official GitLab Runner registration docs for version-specific token changes.
  4. Enable the runner for your project
    If the runner is instance-level, it should be available to all projects by default. If it's project-specific, go to the project's Settings > CI/CD > Runners and enable it there. Check that any required tags in your .gitlab-ci.yml match the tags you assigned during registration.
A runner that's registered but has no matching tags for a job will silently skip it. Always check that the tags in your .gitlab-ci.yml jobs section match exactly what the runner was registered with, including capitalisation.
6

Increase Docker Desktop Resources and Inspect Logs Medium

  1. Increase CPU and RAM in Docker Desktop
    Open Docker Desktop, go to Settings > Resources. Set CPU to at least 4 cores and Memory to at least 6 GB (8 GB if you're running CI jobs on the same machine). Click 'Apply and Restart'. This single change fixes a surprising number of 'GitLab is slow' and 'services keep restarting' complaints.
  2. Read the container logs
    Run docker logs <container_name> --tail 100 in PowerShell. Look for lines containing ERROR, FATAL, or 'Address already in use'. Port binding errors mean something else on the Windows host is using the same port. Service startup failures usually point to a storage permission issue or insufficient memory. The logs are specific enough to tell you exactly which internal service is failing.
  3. Check disk space
    GitLab repositories and CI artefacts grow fast. Run docker system df to see how much disk Docker is using. If the data volume is on a drive that's nearly full, GitLab's PostgreSQL will refuse to start. Free up space or move the volume to a larger drive.
7

Recreate from a Clean docker-compose.yml Advanced

  1. Back up your data volume first
    Before touching anything, copy the contents of your ./gitlab/data directory to a safe location. This is your repository data. Don't skip this step.
  2. Write a clean compose file
    Start with the minimal working template from GitLab's documentation. A correct base looks like this (condensed):
    version: '3.6'
    services:
      gitlab:
        image: 'gitlab/gitlab-ee:latest'
        restart: always
        hostname: 'gitlab.example.com'
        environment:
          GITLAB_OMNIBUS_CONFIG: |
            external_url 'http://localhost:8080'
        ports:
          - '8080:80'
          - '443:443'
          - '22:22'
        volumes:
          - './gitlab/config:/etc/gitlab'
          - './gitlab/logs:/var/log/gitlab'
          - './gitlab/data:/var/opt/gitlab'
  3. Bring it up and verify
    Run docker-compose up -d and wait 5 minutes. Check docker ps to confirm the container is healthy. Open the browser to your configured URL. If it loads, your deployment is now on a clean, known-good base.
If you're dealing with file permission errors on Windows when GitLab tries to write to the mounted volumes, this is a known Docker Desktop for Windows quirk. The workaround is to use named Docker volumes instead of bind mounts, or to run Docker Desktop in WSL 2 mode with the volumes stored inside the WSL 2 filesystem rather than on the Windows NTFS drive.

Completely unrelated to GitLab itself, but worth knowing: if your Windows host starts behaving oddly after all this (slow file access, Explorer hanging), it might be a separate Windows issue rather than Docker. We've seen cases where File Explorer not responding on Windows 11 was caused by the same overloaded disk that was also starving Docker Desktop of I/O bandwidth.

Preventing Self-Hosted GitLab Windows Problems

Most of the failures above are one-time mistakes that become permanent headaches because nobody documents what was set up. Here's what actually keeps a self-hosted GitLab Windows instance stable long-term, in order of importance.

1. Lock down the external URL on day one. Set it, test it, and treat it as immutable. Changing it later means updating every runner config, every webhook, and every OAuth application. It's not worth it. If you're not sure whether you'll access it by hostname or IP, use the IP for now and document it.

2. Persistent volumes are non-negotiable. Every docker-compose.yml for a self-hosted GitLab Windows instance must have all three volume mounts before you push a single commit to it. No exceptions. Treat a GitLab container without persistent volumes the same way you'd treat an unsaved document.

3. Allocate resources generously upfront. Bumping Docker Desktop from 4 GB to 8 GB RAM after the instance is already struggling is less effective than starting with 8 GB. GitLab's memory footprint grows as you add projects and CI jobs, so headroom matters.

4. Use HTTPS for anything beyond localhost. A self-hosted GitLab Windows instance exposed on a LAN or the internet over plain HTTP is transmitting credentials in clear text. Generate a self-signed cert or use Let's Encrypt, mount it at /etc/gitlab/ssl/, and set the external URL to https://. Browsers will warn about self-signed certs, but that's better than no encryption at all. For context on why certificate trust matters in Windows environments, the Windows Defender vs Windows Security breakdown covers how Windows handles certificate validation at the OS level.

5. Separate CI runners from the GitLab server. Running CI jobs on the same machine as the GitLab instance means every pipeline competes with the web UI for CPU and RAM. Even a cheap second machine or a cloud VM registered as a dedicated runner makes a noticeable difference to UI responsiveness during builds.

6. Document everything. Write down the port number, the external URL, the volume paths, and the firewall rule names. Windows updates occasionally reset firewall rules or change network adapter names. Without documentation, you're debugging from scratch every time.

Self-Hosted GitLab Windows Summary

Getting a self-hosted GitLab Windows deployment working reliably comes down to five things done correctly: Docker Desktop running with enough resources, the right external URL set before anything else is configured, persistent volumes mapped in docker-compose.yml, the runner actively registered and enabled, and Windows Firewall open on the right port. Miss any one of those and you get a symptom that looks mysterious but isn't. The architecture is well-documented, the official GitLab Docker setup guide covers the compose file structure in detail, and the logs from docker logs <container> are specific enough to tell you exactly what's wrong when something breaks. Self-hosted GitLab Windows is genuinely worth the setup effort for open source projects that want full control over their infrastructure, no SaaS limitations, and no vendor AI features they didn't ask for. It just needs to be set up properly the first time.

Frequently Asked Questions

Check that Docker Desktop is actually running, then run 'docker ps' to confirm the container is up. Verify you're hitting the correct URL and port (e.g. http://localhost:8080 if you mapped 8080:80). The first-time initialisation takes 2 to 3 minutes on a fresh deploy, so give it time before assuming something is broken.

Run 'gitlab-runner list' on the runner host to confirm registration. Open Admin Area > CI/CD > Runners in the GitLab UI and check the runner is enabled and not paused. Make sure the runner's gitlabUrl in config.toml matches your external URL exactly, including the port. If in doubt, re-register with 'gitlab-runner register'.

Your docker-compose.yml is missing persistent volume mounts. You must map host directories to /etc/gitlab, /var/log/gitlab, and /var/opt/gitlab inside the container. Without these, Docker recreates a fresh container filesystem on every restart and all config and repository data is gone.

Open Windows Defender Firewall with Advanced Security and create an inbound rule allowing TCP traffic on your GitLab port. Use the host machine's LAN IP address in the browser (e.g. http://192.168.1.50:8080), not localhost. If your router is involved, forward the same port to the host's LAN IP.

GitLab's own documentation recommends a minimum of 4 GB RAM and 2 CPU cores for a small instance, but in practice Docker Desktop on Windows needs at least 6 to 8 GB allocated to stay stable under any CI load. Open Docker Desktop > Settings > Resources and increase both sliders. Underprovisioning is the single most common cause of slow UI and random service restarts.