GitLab.com is free for small teams, but self-hosting GitLab Community Edition on your own VPS offers complete control over sensitive repositories, the ability to work offline, eliminate per-seat costs, and customize your Git workflow without vendor restrictions. GitLab CE includes all the core features your development team needs: Git hosting, CI/CD pipelines, issue tracking, merge requests, wikis, container registry, and even basic monitoring — all open source and completely free to operate.
Why Self-Host GitLab CE?
GitLab.com works well for public projects and small teams with basic needs, but self-hosting is ideal when:
- Security & Privacy: Sensitive source code stays on your infrastructure, not on shared cloud platforms
- Compliance: Meet data residency and regulatory requirements (GDPR, HIPAA, CCPA) by keeping code local
- Offline Workflows: Work without internet dependency for critical development tasks
- Cost Efficiency: No per-user monthly fees — unlimited users on your VPS
- Customization: Full control over plugins, authentication, CI/CD runners, and infrastructure
- Performance: No bandwidth bottlenecks from cloud quotas; faster local deployments and backups
System Requirements
Before installing GitLab CE, verify that your VPS meets the minimum specifications:
- RAM: 8 GB recommended (4 GB absolute minimum; expect sluggish performance and OOM kills below 4 GB)
- CPU: 2 cores minimum; 4+ cores preferred for heavy CI/CD workloads
- Disk: 50 GB SSD minimum; allocate more based on repository size (repositories grow over time)
- Operating System: Ubuntu 22.04 LTS (other distributions may require different package manager commands)
- Domain: Valid domain name with DNS A record pointing to your VPS (required for Let's Encrypt SSL)
- Internet: Stable connection; inbound ports 22, 80, 443 must be open
Memory Optimization Tip: On a 4 GB VPS, immediately disable Elasticsearch, Grafana, and Prometheus after installation to free up memory. Default GitLab services are bloated for small instances.
Step 1 — Update System and Install Dependencies
Begin by updating the package manager and installing required libraries. These packages are essential for GitLab's configuration tools and SSL support:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl openssh-server ca-certificates tzdata perl git
The ca-certificates package is critical for Let's Encrypt SSL verification. tzdata ensures correct timestamps in logs and backups.
Step 2 — Add GitLab Repository
GitLab publishes official APT packages through a dedicated repository. Download and run their installation script to configure your package manager:
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
This script adds GitLab's GPG key and APT sources to your system, ensuring you receive updates and security patches automatically.
Step 3 — Install GitLab CE with HTTPS URL
Install the GitLab package and configure it with your domain name in one step. Setting an HTTPS URL triggers automatic Let's Encrypt certificate provisioning:
# Replace gitlab.example.com with your actual domain
sudo EXTERNAL_URL="https://gitlab.example.com" apt-get install gitlab-ce
Installation typically takes 10–15 minutes. During this time, GitLab will:
- Initialize PostgreSQL database
- Configure Nginx reverse proxy
- Request and install a free Let's Encrypt SSL certificate (requires DNS to be already pointing to your VPS)
- Start all GitLab services (Puma, Sidekiq, PostgreSQL, Redis, Nginx)
Step 4 — Retrieve and Change the Initial Root Password
GitLab generates a temporary root password during installation. This password is valid for 24 hours only:
sudo cat /etc/gitlab/initial_root_password
# Output example: Password: randomGeneratedPassword123
Log in to `https://gitlab.example.com` using username root and this password. Change it immediately — go to User Settings → Password and set a strong, unique password. After 24 hours, the temporary password file is automatically deleted by GitLab.
Step 5 — Configure GitLab (SMTP, Signup, Backup)
Edit the main GitLab configuration file to set up email notifications, disable public signups, and configure backup retention:
sudo nano /etc/gitlab/gitlab.rb
Key Configuration Settings
Update these sections in gitlab.rb:
1. External URL (already set, but verify)
external_url 'https://gitlab.example.com'
2. SMTP (Email Notifications)
Configure GitLab to send invitation emails, password reset links, and merge request notifications:
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-specific-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['gitlab_email_from'] = '[email protected]'
gitlab_rails['gitlab_email_display_name'] = 'GitLab'
gitlab_rails['gitlab_email_reply_to'] = '[email protected]'
Note for Gmail: Use an App-Specific Password, not your account password. Enable 2-Factor Authentication, then generate an app password at myaccount.google.com/apppasswords.
3. Disable Public Signups (Security)
By default, GitLab allows anyone to sign up. Disable this if you want a private instance:
gitlab_rails['gitlab_signup_enabled'] = false
4. Backup Retention
Configure how long GitLab keeps local backups before deleting them:
# Keep backups for 7 days (604800 seconds)
gitlab_rails['backup_keep_time'] = 604800
# Backup compress concurrency (adjust based on CPU cores)
gitlab_rails['backup_compress_concurrency'] = 4
5. Apply Configuration
After editing, reconfigure GitLab to apply all changes:
sudo gitlab-ctl reconfigure
This command validates configuration, restarts services, and may take 5 minutes. Watch for errors in the output.
Step 6 — Install and Register GitLab Runner
GitLab Runner is a separate service that executes CI/CD pipeline jobs. You can run it on the same VPS or a dedicated machine.
Install GitLab Runner
# Add GitLab Runner repository
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
# Install the runner package
sudo apt-get install gitlab-runner -y
Register the Runner with GitLab
Each runner must register itself with your GitLab instance. Get the registration token from:
GitLab UI: Admin Area (gear icon) → CI/CD → Runners → "Register an instance runner" button
Then register the runner:
sudo gitlab-runner register \
--url https://gitlab.example.com/ \
--token YOUR_REGISTRATION_TOKEN \
--executor docker \
--docker-image alpine:latest \
--docker-pull-policy=always \
--description "docker-runner-001" \
--docker-privileged \
--paused=false
Alternative: Shell Executor (simpler, less isolated):
sudo gitlab-runner register \
--url https://gitlab.example.com/ \
--token YOUR_TOKEN \
--executor shell \
--description "shell-runner-001"
Executor Comparison:
- Docker Executor: Each job runs in a fresh container → clean environment, no cross-build pollution, but slower startup (need to pull image)
- Shell Executor: Jobs run directly on host → faster, but artifacts accumulate and may interfere with subsequent builds
Understanding .gitlab-ci.yml
GitLab CI/CD is defined in a .gitlab-ci.yml file in your repository root. This file describes your pipeline: what stages to run, what commands to execute, and what artifacts to produce.
Simple Node.js Example
stages:
- build
- test
- deploy
variables:
NODE_ENV: production
build:
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
- node_modules/
expire_in: 1 week
cache:
paths:
- node_modules/
test:
stage: test
script:
- npm run lint
- npm test
dependencies:
- build
coverage: '/^TOTAL.*?(\d+%)$/'
deploy:
stage: deploy
script:
- rsync -avz --delete dist/ user@server:/var/www/myapp/
environment:
name: production
url: https://myapp.example.com
only:
- main
dependencies:
- build
Key Concepts:
- Stages: Each stage runs sequentially; jobs within a stage run in parallel
- Artifacts: Files produced by one stage, consumed by the next (e.g., build outputs used for deployment)
- Cache: Persists across stages (e.g., `node_modules` to avoid reinstalling)
- Only/Except: Control which branches/tags trigger this job
- Environment: Track deployments and link to the live application URL
Backup and Disaster Recovery
Backups are critical. A single backup command captures your entire GitLab data, but you must also back up the secrets file separately.
Create a Manual Backup
sudo gitlab-backup create
Backup file location: /var/opt/gitlab/backups/ (by date: 1623456789_2021_06_10_14.0.0_gitlab_backup.tar)
Schedule Automated Backups via Cron
Create a cron job to back up every day at 2 AM:
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1") | crontab -
Back Up Secrets (Critical!)
The database backup alone is not decryptable without the secrets file:
sudo tar -czf ~/gitlab-secrets-backup.tar.gz /etc/gitlab/gitlab-secrets.json
# Or copy to external storage
sudo cp /etc/gitlab/gitlab-secrets.json /mnt/backup/gitlab-secrets.json
Backup Strategy Checklist
- ✓ Back up
/var/opt/gitlab/backups/— the main archive - ✓ Back up
/etc/gitlab/gitlab-secrets.json— encryption keys (separately!) - ✓ Back up
/etc/gitlab/gitlab.rb— configuration settings - ✓ Sync backups to external NAS, cloud storage, or second VPS (3-2-1 rule: 3 copies, 2 media types, 1 offsite)
- ✓ Test restoration on a non-production instance at least quarterly
Restore from Backup
If disaster strikes, restore GitLab to a previous state:
# List available backups
sudo ls -la /var/opt/gitlab/backups/
# Stop Sidekiq (background jobs)
sudo gitlab-ctl stop sidekiq
sudo gitlab-ctl stop puma
# Restore from specific backup
sudo gitlab-backup restore BACKUP=1623456789_2021_06_10_14.0.0
# Start services again
sudo gitlab-ctl restart
Maintenance Commands and System Monitoring
Keep GitLab healthy by monitoring status, logs, and resource usage:
# Check status of all services
sudo gitlab-ctl status
# Restart all services
sudo gitlab-ctl restart
# Tail logs from all services (last 50 lines, follow mode)
sudo gitlab-ctl tail
# Tail specific service (e.g., nginx, postgresql, sidekiq)
sudo gitlab-ctl tail nginx
sudo gitlab-ctl tail postgresql
sudo gitlab-ctl tail sidekiq
# View GitLab version
sudo gitlab-rails -v
# Run database migrations (if manual upgrade needed)
sudo gitlab-rake db:migrate
# Check database integrity
sudo gitlab-rake gitlab:db:validate_config
# View memory usage by process
ps aux | grep -E 'puma|sidekiq|postgres' | grep -v grep | awk '{print $6, $11}'
RAM Optimization for Small VPS (4–6 GB)
If you're on a tight budget, disable unnecessary services to reduce memory footprint. Edit /etc/gitlab/gitlab.rb:
# Disable monitoring services
prometheus_monitoring['enable'] = false
grafana['enable'] = false
alertmanager['enable'] = false
node_exporter['enable'] = false
# Reduce worker concurrency
puma['worker_processes'] = 2
puma['max_threads'] = 4
sidekiq['concurrency'] = 5
# Disable mattermost chat integration (uses ~200 MB RAM)
mattermost['enable'] = false
# Reduce database connection pooling
postgresql['shared_buffers'] = '256MB'
# Reconfigure to apply changes
sudo gitlab-ctl reconfigure && sudo gitlab-ctl restart
After Optimization: Monitor memory with free -h and top. Even after disabling services, GitLab on 4 GB RAM will be slow; upgrade to 8 GB if possible.
SSL Certificate Renewal and HTTPS
GitLab uses Let's Encrypt for free SSL certificates. Renewal happens automatically, but monitor it:
# Manual renewal (runs automatically daily anyway)
sudo gitlab-ctl renew-le-certs
# Check certificate expiry
sudo gitlab-ctl show-secrets | grep letsencrypt
# View renewal logs
sudo gitlab-ctl tail letsencrypt
If https://gitlab.example.com shows certificate warnings:
- Verify DNS A record points to your VPS
- Check inbound port 80 is open (needed for Let's Encrypt validation)
- Run
sudo gitlab-ctl reconfigureto re-request a certificate
GitLab Container Registry
Self-hosted GitLab includes a private Docker registry. Use it to store container images without external dependencies:
# Login to registry
docker login registry.gitlab.example.com
# Build and tag image
docker build -t registry.gitlab.example.com/mygroup/myproject:1.0 .
# Push to registry
docker push registry.gitlab.example.com/mygroup/myproject:1.0
# Use in CI pipeline
stages:
- build
- deploy
build-image:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Frequently Asked Questions
How much disk space does GitLab need?
Start with 50 GB, but requirements depend on:
- Number of repositories and commit history
- Repository clone frequency (stored as objects)
- Container image registry storage
- Backup retention (set via
backup_keep_time)
Monitor disk usage with df -h and configure alerts when usage exceeds 80%.
Can I migrate from GitLab.com to self-hosted?
Yes, but with limitations. You can export project data (issues, merge requests, wiki) from GitLab.com and import into self-hosted. However, CI/CD pipelines and runner history do not transfer; you must reconfigure.
How do I enable SSH key-based git cloning?
SSH is enabled by default. Users must:
- Add their SSH public key in User Settings → SSH Keys
- Clone projects:
git clone [email protected]:mygroup/myproject.git
Ensure port 22 is open and not blocked by firewall rules.
What if GitLab is slow or unresponsive?
Troubleshooting steps:
- Check memory usage:
free -h— if nearly full, restart or disable services - Check disk I/O:
iostat -x 1 5— high I/O means disk bottleneck - Check PostgreSQL:
sudo gitlab-ctl tail postgresql— look for slow queries - Increase Puma workers if CPU is underutilized:
puma['worker_processes'] = 4
How much RAM does GitLab CE require?
GitLab CE requires at least 4 GB RAM, but 8 GB or more is strongly recommended for production use. For CI/CD pipelines running frequently, consider installing a separate GitLab Runner on a different VPS to avoid resource contention. If RAM is below 4 GB, the Linux OOM Killer may terminate GitLab processes.
What is the difference between Shell and Docker executors for GitLab Runner?
Shell executor runs jobs directly on the host system — simple but less isolated. Docker executor runs each job in a separate container — more isolated, repeatable, and prevents build artifacts from accumulating on the host. Docker is recommended for CI/CD because it ensures clean, consistent environments for every build.
Need a VPS for Self-Hosted GitLab?
AsiaGB offers SSD-powered VPS with 8 GB+ RAM and 99% uptime SLA — perfect for running GitLab CE. Full root access, no resource limits on users. Starting from 500 THB/month.
View VPS Plans