GitLab CE self-hosted installation on Ubuntu VPS

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:

System Requirements

Before installing GitLab CE, verify that your VPS meets the minimum specifications:

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:

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:

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:

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

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:

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:

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:

  1. Add their SSH public key in User Settings → SSH Keys
  2. 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:

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

View all affordable VPS Thailand plans →