How to Self-Host GitLab CE with Docker Compose

GitLab CE is the heaviest self-hosted Git platform you can run — and the most complete. If you need CI/CD, a container registry, issue boards, and wikis in a single deployment, nothing else comes close. But it demands 4 GB of RAM minimum and takes several minutes to start. Know what you’re getting into before deploying.

What Is GitLab CE?

GitLab Community Edition is an open-source DevOps platform that provides Git repository hosting, continuous integration/delivery (CI/CD), issue tracking, a container registry, wikis, and package management. It’s the self-hosted version of gitlab.com, minus the premium features (which require GitLab EE). GitLab CE replaces GitHub, Bitbucket, and separate CI/CD tools like Jenkins or GitHub Actions with a single application.

Updated March 2026: Verified with latest Docker images and configurations.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 4 GB of RAM minimum (8 GB recommended — GitLab is resource-intensive)
  • 2+ CPU cores
  • 30 GB of free disk space (excluding repository data)
  • A domain name (strongly recommended)

Docker Compose Configuration

Create a docker-compose.yml file:

services:
  gitlab:
    image: gitlab/gitlab-ce:18.9.2-ce.0
    container_name: gitlab
    restart: unless-stopped
    hostname: gitlab.yourdomain.com
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab.yourdomain.com'
        gitlab_rails['gitlab_shell_ssh_port'] = 2424
        gitlab_rails['time_zone'] = 'UTC'
        gitlab_rails['lfs_enabled'] = true
        gitlab_rails['backup_keep_time'] = 604800

        # Disable monitoring to save ~300 MB RAM
        prometheus_monitoring['enable'] = false
        alertmanager['enable'] = false
        gitlab_exporter['enable'] = false
        node_exporter['enable'] = false
        postgres_exporter['enable'] = false
        prometheus['enable'] = false
        puma['exporter_enabled'] = false
        redis_exporter['enable'] = false
        sidekiq['metrics_enabled'] = false

        # Reduce memory usage for small teams
        puma['worker_processes'] = 0
        sidekiq['concurrency'] = 10
        gitlab_kas['enable'] = false

        # Aggressive memory reclamation
        gitlab_rails['env'] = {
          'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000'
        }
    ports:
      - "80:80"
      - "443:443"
      - "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:

The GITLAB_OMNIBUS_CONFIG block accepts any setting from GitLab’s gitlab.rb configuration file. Settings passed here are evaluated at container startup and override the persistent configuration.

Start the stack:

docker compose up -d

GitLab takes 3-5 minutes to initialize on first startup. Monitor progress with:

docker logs -f gitlab

Initial Setup

  1. Retrieve the auto-generated root password:
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

This file is deleted automatically after 24 hours — copy the password now.

  1. Open https://gitlab.yourdomain.com in your browser
  2. Log in with username root and the password from step 1
  3. Change the root password immediately: User Settings → Password
  4. Disable open sign-up: Admin → Settings → General → Sign-up restrictions → uncheck “Sign-up enabled”

Configuration

Volume Mounts

VolumeContainer PathContents
gitlab_config/etc/gitlabConfiguration files (gitlab.rb, secrets)
gitlab_logs/var/log/gitlabApplication and service logs
gitlab_data/var/opt/gitlabRepositories, uploads, database, CI artifacts

Key GITLAB_OMNIBUS_CONFIG Settings

SettingPurpose
external_urlYour GitLab URL (determines HTTP vs HTTPS)
gitlab_rails['gitlab_shell_ssh_port']SSH port (set to match your host mapping)
gitlab_rails['lfs_enabled']Enable Git Large File Storage
gitlab_rails['backup_keep_time']How long to keep backups (seconds)
puma['worker_processes']Web server workers (0 = single-process mode)
sidekiq['concurrency']Background job concurrency (default: 20)
prometheus_monitoring['enable']Toggle built-in monitoring stack
gitlab_kas['enable']Kubernetes Agent Server (disable if unused)

SMTP Configuration

GitLab does not include an SMTP server. Add email configuration to GITLAB_OMNIBUS_CONFIG:

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.yourdomain.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "[email protected]"
gitlab_rails['smtp_password'] = "your-smtp-password"
gitlab_rails['smtp_domain'] = "yourdomain.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['gitlab_email_from'] = '[email protected]'

Container Registry

GitLab includes a Docker container registry. Enable it in GITLAB_OMNIBUS_CONFIG:

registry_external_url 'https://registry.yourdomain.com'

This requires a separate domain or subdomain with its own SSL certificate.

Reverse Proxy

If running GitLab behind a reverse proxy instead of using its built-in nginx, add to GITLAB_OMNIBUS_CONFIG:

nginx['listen_port'] = 80
nginx['listen_https'] = false
nginx['proxy_set_headers'] = {
  "X-Forwarded-Proto" => "https",
  "X-Forwarded-Ssl" => "on"
}

Map port 80:80 only and let your reverse proxy handle SSL.

See Reverse Proxy Setup for full configuration.

Backup

GitLab has a built-in backup command that creates a tar archive of all data:

docker exec -t gitlab gitlab-backup create

Backups are stored in /var/opt/gitlab/backups/ (inside the gitlab_data volume). Also back up the config volume separately — it contains secrets needed for restore:

docker exec -t gitlab tar cf - /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab.rb > gitlab_config_backup.tar

See Backup Strategy for automated backup workflows.

Troubleshooting

GitLab takes forever to start

Symptom: Container stays unhealthy for 5+ minutes, web UI inaccessible. Fix: GitLab legitimately takes 3-5 minutes to start. If it exceeds 10 minutes, check RAM — GitLab needs at least 4 GB. Monitor with:

docker exec -it gitlab gitlab-ctl status

If services keep restarting, add swap space or increase RAM.

502 Bad Gateway after startup

Symptom: Web UI shows 502 errors intermittently after container starts. Fix: Puma (the web server) may still be initializing. Wait 2-3 more minutes. If persistent, check if shm_size is set to at least 256m in your Compose file — Prometheus metrics require this shared memory allocation.

SSH connections refused on port 22

Symptom: git clone via SSH fails with “connection refused.” Fix: Port 22 on the host is likely used by the host’s own SSH daemon. Map GitLab’s SSH to a different host port (e.g., 2424:22) and set gitlab_rails['gitlab_shell_ssh_port'] = 2424 in GITLAB_OMNIBUS_CONFIG. Users clone with git clone ssh://[email protected]:2424/user/repo.git.

Out of memory (OOM) kills

Symptom: Container crashes or services restart randomly. dmesg shows OOM killer messages. Fix: Apply the memory optimizations in the Docker Compose example above: disable Prometheus monitoring, set puma['worker_processes'] = 0, reduce sidekiq['concurrency'], and disable gitlab_kas. These save 400-700 MB of RAM. If still insufficient, add 2 GB of swap.

Resource Requirements

  • RAM: 2 GB absolute minimum (with all optimizations), 4 GB recommended for small teams, 8+ GB for 50+ users
  • CPU: 2 cores minimum, 4+ recommended
  • Disk: 10 GB for the application, 20+ GB for PostgreSQL, plus repository storage

Verdict

GitLab CE is the most feature-complete self-hosted DevOps platform — nothing else gives you Git hosting, CI/CD, container registry, issue tracking, wikis, and package management in a single container. The trade-off is resource consumption: 4 GB of RAM for a small team is steep compared to Gitea at 200 MB or Forgejo at similar levels. For teams that need integrated CI/CD and are willing to pay the resource cost, GitLab CE is the right choice. For teams that just need Git hosting with pull requests, Gitea or Forgejo are 20x lighter and perfectly capable.

Frequently Asked Questions

How much RAM does GitLab CE actually need?

With the memory optimizations in the Docker Compose above (disabled Prometheus, single Puma process, reduced Sidekiq concurrency), GitLab runs in 2-3 GB for a small team (1-5 users). Without optimizations, expect 4-6 GB. For 50+ users, plan for 8+ GB. GitLab is the heaviest self-hosted Git server by a wide margin.

Can I use GitLab CI/CD without GitLab Runners?

No. GitLab CI/CD requires at least one GitLab Runner to execute pipeline jobs. The runner is a separate process — install it on the same machine or a different one. The simplest option is the Docker executor, which runs each job in an isolated container.

What’s the difference between GitLab CE and GitLab EE?

GitLab EE (Enterprise Edition) includes premium features like advanced security scanning (SAST/DAST), approval rules, burndown charts, and portfolio management. GitLab CE covers repositories, CI/CD, issue tracking, container registry, and wikis. For most small to medium teams, CE has everything you need.

Can I migrate from GitHub to GitLab CE?

Yes. GitLab has a built-in GitHub import tool under New Project → Import project → GitHub. It migrates repositories, issues, pull requests, and wikis. For large organizations, use the GitLab API for bulk migration.

Is GitLab CE suitable for a single developer?

It works but is overkill. GitLab CE uses 2-3 GB of RAM even with optimizations — that’s a lot for one person’s Git hosting. Gitea or Forgejo provide Git hosting, issues, and pull requests at ~100 MB RAM. Use GitLab only if you specifically need its CI/CD or container registry.

Can I run GitLab CE on a Raspberry Pi?

Not practically. GitLab needs a minimum of 2 GB RAM (with aggressive optimizations) and performs poorly on ARM processors. Even a Pi 5 with 8 GB RAM would struggle. Use Gitea or Forgejo instead — they run well on a Raspberry Pi.

Comments