Self-Hosting GitLab - A Complete Guide
Step-by-step guide to deploying and managing your own GitLab instance for complete control over your development workflow.
Why Self-Host GitLab?
After working with various Git hosting platforms, I decided to self-host GitLab to gain complete control over my development infrastructure. Here's why it made sense for my projects:
- Complete Control: Own your data and infrastructure
- Unlimited Private Repositories: No storage or user limits
- Custom CI/CD Runners: Deploy on your own hardware
- Enhanced Privacy: Keep sensitive code on your own servers
- Cost-Effective: One-time setup vs. recurring subscription fees
- Learning Experience: Deep dive into DevOps and infrastructure
Prerequisites
Before starting, ensure you have:
- A server with at least 4GB RAM (8GB recommended)
- 20GB+ free disk space
- Ubuntu 20.04 or 22.04 LTS
- A domain name (optional but recommended)
- SSH access to your server
Installation Process
System Preparation
First, update your system and install dependencies:
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install required dependencies
sudo apt install -y curl openssh-server ca-certificates tzdata perl
# Install Postfix for email notifications
sudo apt install -y postfix
# Select "Internet Site" during installationAdd GitLab Repository
# Add GitLab package repository
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
# Note: Using gitlab-ee (Enterprise Edition) - free features are availableInstall GitLab
# Set your domain name (or use IP address)
sudo EXTERNAL_URL="https://gitlab.example.com" apt install gitlab-ee
# This will take several minutes to completeInitial Configuration
After installation, GitLab automatically configures itself. Access the web interface:
# Get the initial root password
sudo cat /etc/gitlab/initial_root_password
# Login at: https://gitlab.example.com
# Username: root
# Password: (from the file above)Important: Change the root password immediately after first login!
SSL/TLS Configuration
Using Let's Encrypt (Recommended)
Edit GitLab configuration:
sudo nano /etc/gitlab/gitlab.rbAdd these lines:
# Enable Let's Encrypt
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['admin@example.com']
# External URL with HTTPS
external_url 'https://gitlab.example.com'Reconfigure GitLab:
sudo gitlab-ctl reconfigureManual SSL Certificate
If you have your own SSL certificate:
# In /etc/gitlab/gitlab.rb
external_url 'https://gitlab.example.com'
nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.example.com.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.example.com.key"Essential Configuration
Email Settings
Configure SMTP for notifications:
# In /etc/gitlab/gitlab.rb
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "your-email@gmail.com"
gitlab_rails['smtp_password'] = "your-app-password"
gitlab_rails['smtp_domain'] = "gmail.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = false
gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
# From address
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com'Apply changes:
sudo gitlab-ctl reconfigureBackup Configuration
Set up automated backups:
# In /etc/gitlab/gitlab.rb
gitlab_rails['backup_keep_time'] = 604800 # 7 days
gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"Create a backup cron job:
# Edit crontab
sudo crontab -e
# Add daily backup at 2 AM
0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1Manual backup:
sudo gitlab-backup createResource Optimization
For smaller servers, optimize memory usage:
# In /etc/gitlab/gitlab.rb
# Reduce Puma worker processes
puma['worker_processes'] = 2
# Reduce Sidekiq concurrency
sidekiq['max_concurrency'] = 10
# Reduce PostgreSQL connections
postgresql['shared_buffers'] = "256MB"
postgresql['max_worker_processes'] = 4Setting Up CI/CD Runners
Install GitLab Runner
# Download and install
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install gitlab-runner
# Register runner
sudo gitlab-runner register
# Follow prompts:
# - GitLab instance URL: https://gitlab.example.com
# - Registration token: (from GitLab Admin → Runners)
# - Description: my-runner
# - Tags: docker, linux
# - Executor: docker
# - Default Docker image: alpine:latestConfigure Docker Executor
# Edit runner config
sudo nano /etc/gitlab-runner/config.tomlExample configuration:
[[runners]]
name = "my-docker-runner"
url = "https://gitlab.example.com"
token = "your-token"
executor = "docker"
[runners.docker]
image = "alpine:latest"
privileged = false
volumes = ["/cache"]Creating Your First Pipeline
Example .gitlab-ci.yml for a Node.js project:
stages:
- build
- test
- deploy
build:
stage: build
image: node:18
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:
stage: test
image: node:18
script:
- npm install
- npm test
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache rsync openssh
- rsync -avz dist/ user@server:/var/www/app/
only:
- mainMonitoring and Maintenance
Check GitLab Status
# Overall status
sudo gitlab-ctl status
# Detailed service status
sudo gitlab-rake gitlab:check
# Check configuration
sudo gitlab-rake gitlab:env:infoView Logs
# All logs
sudo gitlab-ctl tail
# Specific service logs
sudo gitlab-ctl tail nginx
sudo gitlab-ctl tail postgresql
sudo gitlab-ctl tail sidekiqUpdate GitLab
# Backup before updating
sudo gitlab-backup create
# Update
sudo apt update
sudo apt install gitlab-ee
# Reconfigure
sudo gitlab-ctl reconfigureSecurity Hardening
Firewall Configuration
# Allow SSH, HTTP, and HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableRate Limiting
# In /etc/gitlab/gitlab.rb
gitlab_rails['rack_attack_git_basic_auth'] = {
'enabled' => true,
'ip_whitelist' => ["127.0.0.1"],
'maxretry' => 10,
'findtime' => 60,
'bantime' => 3600
}Two-Factor Authentication
Enable 2FA for all users:
- Go to Admin Area → Settings → General
- Expand "Sign-in restrictions"
- Enable "Require all users to set up Two-factor authentication"
Performance Tuning
PostgreSQL Optimization
# In /etc/gitlab/gitlab.rb
postgresql['shared_buffers'] = "256MB"
postgresql['work_mem'] = "16MB"
postgresql['maintenance_work_mem'] = "64MB"
postgresql['effective_cache_size'] = "1GB"
postgresql['checkpoint_completion_target'] = 0.9Redis Optimization
# In /etc/gitlab/gitlab.rb
redis['maxmemory'] = "256mb"
redis['maxmemory_policy'] = "allkeys-lru"Troubleshooting Common Issues
502 Bad Gateway
# Check if all services are running
sudo gitlab-ctl status
# Restart GitLab
sudo gitlab-ctl restart
# Check Puma/Unicorn logs
sudo gitlab-ctl tail pumaOut of Memory
# Check memory usage
free -h
# Reduce worker processes (see Resource Optimization)
# Consider upgrading server RAMDatabase Issues
# Check PostgreSQL
sudo gitlab-rake gitlab:db:configure
# Run database migrations
sudo gitlab-rake db:migrateBackup and Recovery
Restore from Backup
# Stop processes that connect to database
sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq
# Restore backup
sudo gitlab-backup restore BACKUP=1234567890_2024_03_15
# Restart GitLab
sudo gitlab-ctl restart
sudo gitlab-rake gitlab:check SANITIZE=trueAdvanced Features
Container Registry
Enable Docker container registry:
# In /etc/gitlab/gitlab.rb
registry_external_url 'https://registry.gitlab.example.com'
gitlab_rails['registry_enabled'] = trueGitLab Pages
Enable static site hosting:
# In /etc/gitlab/gitlab.rb
pages_external_url "https://pages.example.com"
gitlab_pages['enable'] = trueCost Analysis
My Setup:
- VPS: $20/month (4GB RAM, 80GB SSD)
- Domain: $12/year
- Total: ~$252/year
Compared to GitLab SaaS:
- Premium: $29/user/month = $348/year
- Savings: ~$100/year (plus unlimited users/repos)
Lessons Learned
- Start with more RAM: 8GB is comfortable, 4GB is minimum
- Regular backups are essential: Automated daily backups saved me
- Monitor disk space: GitLab artifacts can grow quickly
- Update regularly: Security patches are critical
- Document everything: Keep notes on customizations
Useful Commands Reference
# Reconfigure GitLab
sudo gitlab-ctl reconfigure
# Restart all services
sudo gitlab-ctl restart
# Status check
sudo gitlab-ctl status
# Create backup
sudo gitlab-backup create
# Check configuration
sudo gitlab-rake gitlab:check
# Rails console (for advanced debugging)
sudo gitlab-rails consoleConclusion
Self-hosting GitLab has been incredibly rewarding. I now have complete control over my development infrastructure, unlimited private repositories, and a powerful CI/CD platform running on my own hardware.
The initial setup takes time, but the long-term benefits in cost savings, privacy, and learning make it worthwhile. Start small, monitor closely, and scale as needed.
Have questions about self-hosting GitLab? Connect with me on GitHub or LinkedIn!