GitLab Self-Hosted in 2026: 6-Month Production Review for 5 Users
Honest review of GitLab CE in production: setup, resources, Discord OAuth, Container Registry, and CI runners. Discover practical pitfalls and successful choices for small teams.
TL;DR: Self-hosting GitLab CE 18.x in 2026 remains an excellent choice for 5β50 users if you accept ~4β8 GB RAM at runtime and a 30-minute initial setup. The major upgrades vs. 2024: clean generic OAuth, stable Container Registry, and the official MCP server (135 tools). Concrete pitfalls below.
The Context
I have been running GitLab CE 18.x self-hosted for 6 months on an Ubuntu 24.04 VM (192.168.10.93, 4 vCPU, 12 GB RAM, 200 GB SSD). 5 active users, ~40 projects, ~30 GB of cumulative repos, with CI runners separated on another VM. This article summarizes what works, what broke, and what I would do differently.
Why Self-Hosted (vs. gitlab.com)
My concrete reasons in 2026:
- No CI minute quota: On free gitlab.com, itβs 400 min/month. My Astro + Docker pipelines take ~10 min each Γ 10/day = saturated quickly. Self-hosted = unlimited.
- Privacy: Proprietary code that doesnβt reside anywhere with a third party.
- Included Container Registry: Since GitLab 17.x, self-hosted registry auth is stable. No more need for Harbor alongside.
- Official MCP: Since late 2025, GitLab provides an official MCP server exposing 135 tools to LLMs (create issue, MR, read file, etc.). Huge productivity boost if you code with Claude/GPT/Cursor.
Initial Setup β 30 Real Minutes
Hardware Prerequisites
- Real 2026 Minimum: 4 vCPU / 8 GB RAM (4 GB starts swapping as soon as a GC runs)
- Recommended: 4 vCPU / 12 GB RAM
- Disk: SSD is mandatory. HDD = 5Γ slower pipelines.
I tested on Hostinger VPS Cloud (KVM 4 vCPU / 8 GB / 200 GB NVMe, ~β¬7/month β see VPS offers [affiliate link]). It works. But the best option remains a homelab if you can afford it.
Omnibus Install
# Ubuntu 24.04
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo EXTERNAL_URL="https://git.exemple.com" apt install gitlab-ce
30 seconds for package installation, 5 minutes for auto-reconfigure on the first gitlab-ctl reconfigure. Thatβs it.
Pitfall #1: If you set EXTERNAL_URL=http://... and want to switch to HTTPS later using an external reverse proxy (Caddy / Traefik), you must explicitly disable the embedded Letβs Encrypt in /etc/gitlab/gitlab.rb:
letsencrypt['enable'] = false
nginx['listen_https'] = false
nginx['listen_port'] = 80
external_url 'https://git.exemple.com' # still HTTPS in the URL
Otherwise, the bundled nginx tries to issue a cert on its own β conflict with your external Caddy.
Discord OAuth β Finally Functional in 2026
Since GitLab 17.5, OAuth via oauth2_generic works for Discord without workarounds. In /etc/gitlab/gitlab.rb:
gitlab_rails['omniauth_providers'] = [
{
name: 'oauth2_generic',
label: 'Discord',
app_id: 'DISCORD_APP_ID',
app_secret: 'DISCORD_APP_SECRET',
args: {
client_options: {
site: 'https://discord.com',
authorize_url: '/api/oauth2/authorize',
token_url: '/api/oauth2/token',
user_info_url: '/api/users/@me'
},
user_response_structure: {
root_path: [],
id_path: ['id'],
attributes: {
nickname: 'username',
first_name: 'global_name',
email: 'email',
image: 'avatar'
}
},
authorize_params: {
scope: 'identify email'
},
strategy_class: "OmniAuth::Strategies::OAuth2Generic"
}
}
]
Pitfall #2: Discord does not return a directly usable avatar URL, just a hash. GitLab displays a default Gravatar identicon. Solution: a daily cron job that syncs Discord avatars to GitLab via their admin API. A ~50-line Python script.
Container Registry β The 2026 Improvement
Clean self-hosted registry auth setup finally available since GitLab 17.x:
# /etc/gitlab/gitlab.rb
registry_external_url 'https://registry.git.exemple.com'
registry_nginx['listen_https'] = false
registry_nginx['listen_port'] = 5050
- External Caddy/Traefik vhost that forwards 5050 β 5050 with TLS termination.
Once in place, direct push from CI pipeline:
# .gitlab-ci.yml
docker:build:
stage: build
image: docker:24-cli
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Automatic auth via CI variables. No more need for Harbor / DockerHub.
CI Runners β Separation of Concerns
Pitfall #3: Do not put runners on the same host as the GitLab server. This guarantees GitLab will swap during heavy pipelines.
I placed 2 GitLab Runners (concurrent=3 each) on a separate host (Intel N100 mini-PC with 8 GB RAM, ~β¬150). Auto-registered via token, Docker executor with a custom Node 20 + pre-installed Astro image to save cache time.
Measurement: Astro + Docker build pipeline = 2β4 min on separate runners vs. 8β12 min if runners are on the GitLab host (the server itself competing for resources).
Backups β The Forgotten Part
# Daily Cron
sudo gitlab-backup create STRATEGY=copy
# β /var/opt/gitlab/backups/TIMESTAMP_NN.X_gitlab_backup.tar
Pitfall #4: The backup does NOT contain /etc/gitlab/gitlab.rb nor /etc/gitlab/gitlab-secrets.json. If you restore without these, your instance cannot decrypt its own secrets. Back up these 2 files separately (encrypted).
I send everything to a self-hosted MinIO on another VM via rclone, 30-day rotation. Additional cost: β¬0 (homelab).
The Verdict After 6 Months
What Works Great
- Stability: 99.9% uptime over 6 months (1 incident: OOM kill when I pushed a 4 GB repo without Git LFS)
- Performance: 2β4 min pipelines on Node/Docker stack
- Discord OAuth: Zero friction for adding friends / collaborators
- Container Registry: 30 images in production, no issues
What Broke
- Upgrade 17 β 18: 25 min downtime (vs. estimated 5 min) due to a DB schema migration. Read the release notes BEFORE planning the upgrade.
- GitLab Pages: Requires wildcard DNS + wildcard cert. Not easy to set up with external Caddy. I worked around it by using external Cloudflare Pages for static content.
- Memory: GitLab βbreathesβ between 4 and 8 GB depending on the active Sidekiq worker. Without 12 GB of headroom, it ends up in an OOM kill.
Alternative to Consider
If you are 1β3 people and do little CI: Gitea + Drone or Forgejo use ~200 MB RAM at runtime (vs. 4β8 GB for GitLab). For code-only without heavy pipelines, itβs much lighter.
But as soon as you want Container Registry + OAuth + mature CI + full UI + MCP server: GitLab CE remains superior.
Complementary Tools I Use
- Bitdefender GravityZone on the GitLab host: A serious EDR is non-negotiable when you expose a Git service to the public (GravityZone offers β affiliate link)
- Hostinger VPS Cloud: If you donβt have a homelab, this is the best price/performance ratio in 2026 (see VPS β affiliate link)
- NordVPN: For SSH admin from outside without exposing port 22 (NordVPN β affiliate link)
FAQ
How much does self-hosting really cost? If you already have a homelab: β¬0 / month (electricity ~β¬5/month on a mini-PC). On a VPS: β¬7β15 / month for 4 vCPU / 12 GB.
GitLab EE vs. CE in 2026? CE covers 95% of use cases for <50 users. EE becomes relevant above that, for portfolio management features (crossed epics, value stream analytics, etc.).
Is free GitLab.com enough to start? Yes, until you hit the 400 CI min/month limit. Migrating away from gitlab.com is then trivial (full project export via API).
Is GitHub better? GitHub Enterprise self-hosted = significantly more expensive (~$21/user/month) for a comparable feature set. GitLab CE = $0.
Affiliation Disclosure
This article contains affiliate links. See full disclosure. These links do not alter the content or recommendations β I have been using GitLab CE self-hosted for 6 months and this is my raw feedback.
Article written on May 28, 2026.