Skip to content
appsgit

Deploy guide

How to self-host GitLab with Docker Compose

GitLab Docker Compose setup with the official CE image: memory sizing, SSH on a custom port, built-in Let's Encrypt HTTPS, root password, backups, upgrades.

  • Updated
  • Intermediate
  • About 30 minutes

You will need

  • 4 vCPU / 8 GB RAM recommended (4 GB + swap for a small team)
  • Docker + Docker Compose v2
  • 40 GB+ SSD storage
  • A domain name pointing at the server

What is GitLab?

GitLab is a complete DevOps platform: Git repositories, merge requests, issues, wikis, CI/CD pipelines, a container and package registry, and security scanning in one application. The self-managed Community Edition is open source under the MIT license and is what many companies run on their own servers instead of GitHub. It ships as the "omnibus" Docker image, which bundles GitLab, PostgreSQL, Redis, Nginx and Gitaly in one container.

Requirements

  • Memory is the big one. GitLab's requirements page sets the baseline at 8 vCPU and 16 GB of RAM, with 8 GB as the floor for constrained installs. A small private instance for a few users can run on 4 GB with swap and the tuning below; 8 GB is the comfortable minimum.
  • Docker Engine and Docker Compose v2.
  • At least 40 GB of SSD storage, plus room for repositories, CI artifacts and registry images.
  • A domain with an A record pointing at the server, so GitLab can get a Let's Encrypt certificate.

Step 1: Prepare the server

This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide. On a 4 GB server, add swap first:

sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Create the folder GitLab will use:

sudo mkdir -p /srv/gitlab && cd /srv/gitlab

Step 2: Create the Docker Compose file

This follows the official Docker Compose example, with the CE image, SSH moved to port 2424 and memory tuning from GitLab's constrained-environment docs. Save it as /srv/gitlab/docker-compose.yml:

services:
  gitlab:
    image: gitlab/gitlab-ce:19.4.1-ce.0
    container_name: gitlab
    restart: always
    hostname: gitlab.example.com
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab.example.com'
        letsencrypt['contact_emails'] = ['you@example.com']
        gitlab_rails['gitlab_shell_ssh_port'] = 2424
        puma['worker_processes'] = 0
        sidekiq['concurrency'] = 10
        prometheus_monitoring['enable'] = false
    ports:
      - "80:80"
      - "443:443"
      - "2424:22"
    volumes:
      - ./config:/etc/gitlab
      - ./logs:/var/log/gitlab
      - ./data:/var/opt/gitlab
    shm_size: "256m"

Replace both gitlab.example.com values and the email. The three tuning lines (single-process Puma, lower Sidekiq concurrency, built-in Prometheus off) save several hundred MB; remove them on a server with 8 GB or more. Pin a specific version tag, because GitLab upgrades must follow a defined path and latest can skip required stops.

Step 3: Start and open the app

docker compose up -d
docker compose logs -f gitlab

The first boot runs gitlab-ctl reconfigure and database migrations, which can take 5 to 10 minutes. When docker compose ps shows the container as healthy, get the root password:

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

Open https://gitlab.example.com, sign in as root, and change the password under your avatar, Edit profile, Password. Then go to Admin, Settings, General, Sign-up restrictions and turn off open sign-ups unless you want strangers creating accounts.

Step 4: Put it behind HTTPS

Because external_url starts with https://, the omnibus package requests and renews a Let's Encrypt certificate by itself, as long as ports 80 and 443 reach the container. Nothing else is required.

If another reverse proxy already owns ports 80 and 443, change the port mapping to "127.0.0.1:8929:80" and add to GITLAB_OMNIBUS_CONFIG:

nginx['listen_port'] = 80
nginx['listen_https'] = false
letsencrypt['enable'] = false

Then point your proxy at 127.0.0.1:8929. Docker-published ports bypass ufw, so binding to localhost is what actually keeps the plain HTTP port private.

Backups and upgrades

Create an application backup (repositories, database, uploads, CI artifacts):

docker exec -t gitlab gitlab-backup create

Archives land in ./data/backups. The backup deliberately excludes ./config/gitlab-secrets.json and ./config/gitlab.rb; copy those separately, because without the secrets file you cannot restore encrypted data such as CI variables and two-factor settings.

To upgrade, check GitLab's Upgrade Path tool for required stops between your version and the target, back up, change the image tag one stop at a time and run:

docker compose pull && docker compose up -d

Wait for background migrations to finish (Admin, Monitoring, Background migrations) before the next stop.

Troubleshooting

  • 502 "GitLab is taking too much time to respond": normal for several minutes after start. If it persists, the server is short on memory; check free -h and add RAM or swap.
  • initial_root_password file is missing: it was removed after 24 hours. Reset the password with docker exec -it gitlab gitlab-rake "gitlab:password:reset[root]".
  • SSH clone fails: use port 2424 in the URL or SSH config, and make sure the firewall or cloud security group allows it.
  • Let's Encrypt fails on first boot: DNS was not pointing at the server yet. Fix DNS, then run docker exec gitlab gitlab-ctl reconfigure.

Next steps

Register a GitLab Runner in a separate container to run CI/CD pipelines, set up outgoing email over SMTP, enable the container registry on its own subdomain, and schedule nightly gitlab-backup create runs with off-site copies.

Spotted something out of date? Tell us and we will update the guide.

FAQ

GitLab questions

Still curious? Email info@appsgit.com.

How much RAM does GitLab need?

GitLab's own baseline for a standard install is 8 vCPU and 16 GB of RAM, and it can run with 8 GB in a constrained setup. For a handful of users, 4 GB plus swap works if you apply the memory-saving settings in this guide. Below that, GitLab becomes slow or crashes.

What is the default GitLab root password?

There is no fixed default. GitLab generates a random password for the root user and stores it in /etc/gitlab/initial_root_password inside the container. The file is deleted on the first container restart after 24 hours, so sign in and change it straight away.

What ports does GitLab use in Docker?

GitLab serves HTTP on port 80, HTTPS on port 443 and Git over SSH on port 22. Because the host already uses 22 for its own SSH, this guide maps GitLab's SSH to port 2424 and tells GitLab to show that port in clone URLs.

Is GitLab free?

GitLab Community Edition (gitlab/gitlab-ce) is free and open source under the MIT license. GitLab Enterprise Edition runs as the Free tier without a license and unlocks Premium or Ultimate features when you buy a subscription.

Gitea vs GitLab: which should I self-host?

GitLab is a full DevOps platform with built-in CI/CD, container registry, issue boards and security scanning, but it needs gigabytes of RAM. Gitea is a lightweight Git host that runs in a few hundred MB and adds CI through Gitea Actions. For a personal server, Gitea is usually the better fit.

Can I use an external reverse proxy with GitLab?

Yes. Set external_url to your https:// address, then disable GitLab's own HTTPS with nginx['listen_https'] = false and nginx['listen_port'] = 80, and let your proxy forward to the container over HTTP.