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

## Key facts

| Fact | Value |
|---|---|
| App | GitLab (https://appsgit.com/apps/gitlab) |
| Difficulty | intermediate |
| Time | about 30 minutes |
| Requirements | 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 |
| Last updated | 2026-10-06 |

## 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](https://docs.docker.com/engine/install/ubuntu/). On a 4 GB server, add swap first:

```bash
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:

```bash
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`:

```yaml
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

```bash
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:

```bash
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`:

```ruby
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):

```bash
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:

```bash
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.

## FAQ

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

Prefer not to do it yourself? [appsgit installation help](https://appsgit.com/services/install) installs it on your server for a fixed quote.

---

Canonical page: https://appsgit.com/guides/gitlab
Source: appsgit (https://appsgit.com), the app store for github. Data from the GitHub API, refreshed nightly.
Machine access: JSON API https://appsgit.com/api/v1/apps (OpenAPI: https://appsgit.com/openapi.json), MCP server https://mcp.appsgit.com/mcp, full index https://appsgit.com/llms-full.txt.
