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 -hand 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.