gitlab/gitlab-ce) with three volume mounts for config, logs, and data, plus optional Runner and SMTP containers for CI/CD and email.docker compose up -d with a docker-compose.yml that maps ports 80/443/22 and mounts $GITLAB_HOME/{config,logs,data}.docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password.| Service | Image | Ports | Volumes | Key Env |
|---|---|---|---|---|
| GitLab CE | gitlab/gitlab-ce:18.7.1-ce.0 | 80:80, 443:443, 22:22 | config:/etc/gitlab, logs:/var/log/gitlab, data:/var/opt/gitlab | GITLAB_OMNIBUS_CONFIG, external_url |
| GitLab Runner | gitlab/gitlab-runner:latest | None | runner-config:/etc/gitlab-runner, /var/run/docker.sock | CI_SERVER_URL |
| PostgreSQL (bundled) | Included in omnibus | 5432 (internal) | data:/var/opt/gitlab/postgresql | postgresql['shared_buffers'] |
| Redis (bundled) | Included in omnibus | 6379 (internal) | data:/var/opt/gitlab/redis | redis['maxmemory'] |
| Nginx (bundled) | Included in omnibus | 80, 443 (via GitLab) | config:/etc/gitlab/ssl | nginx['ssl_certificate'] |
| Puma (app server) | Included in omnibus | 8080 (internal) | -- | puma['worker_processes'] (default: 2) |
| Sidekiq (background) | Included in omnibus | -- | -- | sidekiq['max_concurrency'] (default: 20) |
| Prometheus (monitoring) | Included in omnibus | 9090 (internal) | data:/var/opt/gitlab/prometheus | prometheus['enable'] |
| Container Registry | Included in omnibus | 5050 (configurable) | data:/var/opt/gitlab/gitlab-rails/shared/registry | registry_external_url |
| Gitaly (Git RPC) | Included in omnibus | 8075 (internal) | data:/var/opt/gitlab/git-data | gitaly['configuration'] |
| Host Path | Container Path | Purpose |
|---|---|---|
$GITLAB_HOME/config | /etc/gitlab | gitlab.rb, gitlab-secrets.json, SSL certs |
$GITLAB_HOME/logs | /var/log/gitlab | Omnibus component logs |
$GITLAB_HOME/data | /var/opt/gitlab | Repositories, uploads, PostgreSQL, Redis data |
| Component | Default Memory | Tunable Setting |
|---|---|---|
| Puma (per worker) | 1.2 GB limit | puma['per_worker_max_memory_mb'] |
| Sidekiq | ~200 MB initial | sidekiq['max_concurrency'] |
| Prometheus + exporters | ~200 MB | prometheus['enable'] = false to disable |
| PostgreSQL | 256 MB shared_buffers | postgresql['shared_buffers'] = '512MB' |
| Total minimum | ~4 GB | -- |
START: What kind of GitLab Docker deployment?
├── Local development / testing?
│ ├── YES → Use HTTP-only compose (Step 1) with minimal resources
│ └── NO ↓
├── Production with own domain?
│ ├── YES → Do you handle SSL at a reverse proxy?
│ │ ├── YES → Set external_url to https:// but disable built-in Nginx SSL
│ │ └── NO → Use Let's Encrypt auto-SSL (Step 3, Variant A)
│ └── NO ↓
├── Need CI/CD pipelines?
│ ├── YES → Add GitLab Runner service to compose (Step 4)
│ └── NO ↓
├── Need email notifications?
│ ├── YES → Configure SMTP in GITLAB_OMNIBUS_CONFIG (Step 5)
│ └── NO ↓
└── DEFAULT → Basic compose with HTTP, add features as needed
Create the GitLab home directory and set the environment variable. [src1]
# Set GITLAB_HOME (add to ~/.bashrc for persistence)
export GITLAB_HOME=/srv/gitlab
sudo mkdir -p $GITLAB_HOME/{config,logs,data}
Verify: ls -la $GITLAB_HOME → should show config/, logs/, data/ directories.
Production-ready base configuration for GitLab CE. [src1]
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
container_name: gitlab
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.example.com'
gitlab_rails['time_zone'] = 'UTC'
ports:
- '80:80'
- '443:443'
- '22:22'
volumes:
- '${GITLAB_HOME}/config:/etc/gitlab'
- '${GITLAB_HOME}/logs:/var/log/gitlab'
- '${GITLAB_HOME}/data:/var/opt/gitlab'
shm_size: '256m'
Verify: docker compose config → should show resolved configuration without errors.
Choose one approach depending on your infrastructure. [src2]
# Variant A: Let's Encrypt (automatic)
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['[email protected]']
letsencrypt['auto_renew'] = true
# Variant B: Behind a reverse proxy
nginx['listen_port'] = 80
nginx['listen_https'] = false
nginx['proxy_set_headers'] = {
"X-Forwarded-Proto" => "https",
"X-Forwarded-Ssl" => "on"
}
# Variant C: Own certificates
nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.key"
Verify: curl -I https://gitlab.example.com → should return HTTP/2 200 with valid TLS.
Add the Runner service and register it using authentication tokens (GitLab 16.0+). [src4] [src7]
# Register runner (after creating in UI: Admin > CI/CD > Runners)
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url "https://gitlab.example.com" \
--token "glrt-YOUR_TOKEN_HERE" \
--executor "docker" \
--docker-image "alpine:latest" \
--description "docker-runner"
Verify: docker exec -it gitlab-runner gitlab-runner list → should show the registered runner.
GitLab Docker image does not include an SMTP server. [src5]
# Gmail SMTP configuration (add to GITLAB_OMNIBUS_CONFIG)
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "[email protected]"
gitlab_rails['smtp_password'] = "your-app-password"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
Verify: Run Notify.test_email('[email protected]', 'Test', 'Works!').deliver_now in gitlab-rails console.
Start GitLab and get the auto-generated root password. [src1]
docker compose up -d
# Wait 3-5 minutes for initialization
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Verify: Open https://gitlab.example.com and log in with username root and the retrieved password.
Configure cron for regular backups. Back up secrets separately. [src3]
# Create backup (data only -- secrets NOT included)
docker exec -t gitlab gitlab-backup create
# Back up secrets (CRITICAL)
cp $GITLAB_HOME/config/gitlab-secrets.json \
$GITLAB_HOME/config/gitlab-secrets.json.bak
# Crontab: daily at 2 AM
0 2 * * * docker exec -t gitlab gitlab-backup create CRON=1
Verify: ls -la $GITLAB_HOME/data/backups/ → should show *_gitlab_backup.tar files.
# docker-compose.yml -- GitLab CE Production Setup
# Usage: GITLAB_HOME=/srv/gitlab docker compose up -d
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
container_name: gitlab
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.example.com'
gitlab_rails['time_zone'] = 'UTC'
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['[email protected]']
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
puma['worker_processes'] = 2
sidekiq['max_concurrency'] = 10
postgresql['shared_buffers'] = '512MB'
ports:
- '80:80'
- '443:443'
- '22:22'
volumes:
- '${GITLAB_HOME}/config:/etc/gitlab'
- '${GITLAB_HOME}/logs:/var/log/gitlab'
- '${GITLAB_HOME}/data:/var/opt/gitlab'
shm_size: '256m'
gitlab-runner:
image: gitlab/gitlab-runner:latest
container_name: gitlab-runner
restart: always
depends_on:
- gitlab
volumes:
- gitlab-runner-config:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
volumes:
gitlab-runner-config:
# Minimal GitLab for dev/small teams (4 GB RAM)
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
container_name: gitlab
restart: always
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://gitlab.local'
puma['worker_processes'] = 0
puma['min_threads'] = 1
puma['max_threads'] = 2
sidekiq['max_concurrency'] = 5
prometheus['enable'] = false
grafana['enable'] = false
registry['enable'] = false
postgresql['shared_buffers'] = '128MB'
ports:
- '80:80'
- '22:22'
volumes:
- '${GITLAB_HOME}/config:/etc/gitlab'
- '${GITLAB_HOME}/logs:/var/log/gitlab'
- '${GITLAB_HOME}/data:/var/opt/gitlab'
shm_size: '256m'
#!/bin/bash
# restore-gitlab.sh -- Restore GitLab from backup
BACKUP_TIMESTAMP="$1"
docker exec -it gitlab gitlab-ctl stop puma
docker exec -it gitlab gitlab-ctl stop sidekiq
docker exec -it gitlab gitlab-backup restore BACKUP="${BACKUP_TIMESTAMP}"
docker cp gitlab-secrets.json.bak gitlab:/etc/gitlab/gitlab-secrets.json
docker exec -it gitlab gitlab-ctl reconfigure
docker exec -it gitlab gitlab-ctl restart
docker exec -it gitlab gitlab-rake gitlab:check SANITIZE=true
# BAD -- latest tag can pull breaking changes on restart
services:
gitlab:
image: gitlab/gitlab-ce:latest
# GOOD -- pinned version ensures reproducible deployments
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
# BAD -- data is lost when container is removed
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
# No volumes defined
# GOOD -- persistent data survives container recreation
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
volumes:
- '${GITLAB_HOME}/config:/etc/gitlab'
- '${GITLAB_HOME}/logs:/var/log/gitlab'
- '${GITLAB_HOME}/data:/var/opt/gitlab'
# BAD -- registration tokens deprecated in GitLab 16.0
docker exec -it gitlab-runner gitlab-runner register \
--registration-token "DEPRECATED_TOKEN"
# GOOD -- authentication tokens from GitLab 16.0+
docker exec -it gitlab-runner gitlab-runner register \
--token "glrt-YOUR_AUTH_TOKEN" \
--url "https://gitlab.example.com" \
--executor "docker" \
--docker-image "alpine:latest"
# BAD -- Prometheus may crash with OOM errors
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
# No shm_size specified
# GOOD -- 256m prevents Prometheus OOM errors
services:
gitlab:
image: gitlab/gitlab-ce:18.7.1-ce.0
shm_size: '256m'
# BAD -- secrets file NOT included in gitlab-backup
docker exec -t gitlab gitlab-backup create
# GOOD -- back up both data and secrets
docker exec -t gitlab gitlab-backup create
cp $GITLAB_HOME/config/gitlab-secrets.json \
/backup/gitlab-secrets.json.$(date +%F)
'2224:22' and set gitlab_rails['gitlab_shell_ssh_port'] = 2224. [src1]docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password. [src1]shm_size: '256m', ensure 4 GB+ RAM, disable Prometheus in constrained environments. [src6]', ", #, \ in SMTP passwords. [src5]external_url matches actual access URL including protocol. [src2]extra_hosts in compose or ensure same Docker network. [src4]:Z to volume mounts. [src1]# Check container status and health
docker compose ps
docker inspect gitlab --format='{{.State.Health.Status}}'
# View GitLab logs
docker compose logs -f gitlab
# Check all omnibus component status
docker exec -it gitlab gitlab-ctl status
# Run environment check
docker exec -it gitlab gitlab-rake gitlab:check SANITIZE=true
# Check GitLab version
docker exec -it gitlab gitlab-rake gitlab:env:info
# Test SMTP email
docker exec -it gitlab gitlab-rails console -e production
# Check runner connectivity
docker exec -it gitlab-runner gitlab-runner verify
# Check disk usage
du -sh $GITLAB_HOME/{config,logs,data}
# List backups
ls -la $GITLAB_HOME/data/backups/
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| 18.x | Current | New UI, Duo Analytics | Direct upgrade from 17.x |
| 17.x | Maintenance | Requires PostgreSQL 14+ | Upgrade PostgreSQL before upgrading from 16.x |
| 16.x | EOL | Runner registration tokens deprecated | Switch to runner authentication tokens |
| 15.x | EOL | Praefect/Gitaly changes | Must stop at 15.11.x before 16.x |
| 14.x | EOL | Background migrations required | Must complete all migrations before upgrading |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Small to mid-size team (< 500 users) self-hosting | Kubernetes infrastructure available | GitLab Helm Chart or GitLab Operator |
| Single-server deployment | High availability required | GitLab HA reference architecture |
| Quick local development/testing | Enterprise features needed | GitLab EE Docker image |
| Air-gapped / on-premise requirement | SaaS is acceptable | gitlab.com (hosted) |
| Full DevOps platform (SCM + CI/CD + Registry) | Only need lightweight Git hosting | Gitea or Gogs Docker |
docker compose pull && docker compose up -d. Always back up before upgrading.docker-compose) is deprecated. Use V2 (docker compose without hyphen).