Installing GitLab on Ubuntu 20.04 — A Pragmatic Approach
Self-hosting GitLab gives you complete control and compliance for source code, pipelines, and credentials. For teams building regulated products or operating under strict data residency requirements, cloud-based SaaS can introduce unnecessary risk and complexity. A single-node GitLab deployment on Ubuntu 20.04 solves this without new infrastructure patterns—just a minimal server, a bit of configuration discipline, and attention to ongoing maintenance.
Baseline Requirements
- Ubuntu 20.04 LTS (tested with 5.4.0-162-generic kernel)
- 2 vCPUs minimum; 4 vCPUs and 8GB RAM recommended for concurrent CI tasks
- Non-root user with sudo
- Public DNS A record (e.g.,
gitlab.acme.internal
), critical for SSL but possible to skip for dry-run/testing via IP - Firewall open to TCP/22, 80, 443
If swap is disabled and RAM is tight, some CI jobs (notably parallel RSpec tests) may stall or fail outright. Noted.
Update System and Required Packages
SSH into the target. Any errors about "Failed to fetch" are almost always due to stale package signatures or clock skew. Fix before proceeding.
ssh engineer@<server-ip>
sudo apt update && sudo apt -y upgrade
sudo apt install -y curl openssh-server ca-certificates tzdata perl
sudo DEBIAN_FRONTEND=noninteractive apt install -y postfix
# Note: postfix config — Internet Site, system mail name = FQDN or leave as is if unsure
Anecdotally, some VPS providers inject unusual /etc/hosts
lines. Double-check that hostname -f
returns a usable FQDN.
Register GitLab Repository
The official package script adds the repository and imports the necessary signing keys.
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
Quick check: /etc/apt/sources.list.d/gitlab_gitlab-ce.list
should now exist.
GitLab Installation and Bootstrap
Replace <fqdn>
accordingly. For a quick validation, EXTERNAL_URL can be the IP, but HTTPS certificates always require a real DNS name.
sudo EXTERNAL_URL="http://gitlab.acme.internal" apt-get install -y gitlab-ce
Timeouts or missing dependencies? Look at /var/log/gitlab/*/*.log
—most initial setup hangs trace back to RAM exhaustion or missing locale settings. If the install fails with "locale: Cannot set LC_ALL", ensure locale data is present:
sudo apt-get install language-pack-en
After install:
sudo gitlab-ctl reconfigure
- Initializes internal services: PostgreSQL 12+, Redis 5.x, Gitaly
- Binds NGINX to EXTERNAL_URL
Web Interface and Initial Root Login
Navigate to http://gitlab.acme.internal
. First page prompts for a root password. Set and record this securely.
Default login:
Username: root
Password: (your new password)
Logging in via HTTP will throw browser security warnings. Accept only for internal/test deployments—production systems must enforce HTTPS.
Enabling HTTPS via Let’s Encrypt
- Stop here if you're offline or just prototyping—SSL config will fail without a valid DNS, open firewall, and correct time (NTP sync is essential).
- Edit
/etc/gitlab/gitlab.rb
:
external_url "https://gitlab.acme.internal"
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['it@acme.internal']
nginx['redirect_http_to_https'] = true
- Apply config:
sudo gitlab-ctl reconfigure
Gotcha: Let’s Encrypt rate limiting is aggressive—frequent config changes can quickly exhaust limits. Check /var/log/gitlab/nginx/error.log
for SSL errors.
- Open TCP/443 in your firewall, or HTTPS will silently fail.
Security Hardening (Selective, Not Exhaustive)
- Enforce
AllowUsers
in/etc/ssh/sshd_config
and put SSH keys in place; password login off. - Enable 2FA in GitLab admin settings (
/admin/application_settings
). - Backups (
/var/opt/gitlab/backups
):
Generate on demand:
For scheduled jobs, usesudo gitlab-backup create STRATEGY=copy SKIP=registry
crontab -e
with explicit environment variables (e.g.,PATH
, to avoid failed jobs). - Security patches:
Watch for upstream breakages. Readsudo apt update && sudo apt -y install gitlab-ce sudo gitlab-ctl reconfigure
/opt/gitlab/version-manifest.txt
if you need to audit shipped component versions.
SMTP Email Integration — Reliable Alerts
For outgoing mail, postfix defaults to a local relay, which is rarely sufficient. Integrating with SendGrid (or alternative provider) gives more reliable delivery:
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = 'smtp.sendgrid.net'
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = 'apikey'
gitlab_rails['smtp_password'] = '<sendgrid-api-key>'
gitlab_rails['smtp_domain'] = 'acme.internal'
gitlab_rails['smtp_authentication'] = 'login'
gitlab_rails['smtp_enable_starttls_auto'] = true
Reconfigure and send a test message from the admin panel.
Note: Some SMTP providers will greylist initial attempts or throw TLS protocol errors if your system clock is skewed.
Non-Obvious Tips & Troubleshooting
Symptom | Diagnosis & Fix |
---|---|
502 Bad Gateway on first login | NGINX started before unicorn; run gitlab-ctl restart |
High load under CI | Increase puma['worker_processes'] in gitlab.rb ; add swap if RAM-constrained |
Backups fill disk | Clean up /var/opt/gitlab/backups/ or adjust gitlab_rails['backup_keep_time'] |
- For airgapped or compliance-bound networks, use the GitLab Offline install docs.
- Some default runners (e.g. shell) can expose secrets in logs if misconfigured—review runner registration tokens and avoid wildcards in production setups.
Summary Table: Critical Steps
Step | Command/Summary |
---|---|
Update system | sudo apt update && sudo apt upgrade -y |
Install dependencies | sudo apt install -y curl openssh-server ca-certificates tzdata perl postfix |
Add GitLab repo | curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash |
Install GitLab | sudo EXTERNAL_URL="http://<fqdn>" apt-get install -y gitlab-ce |
Reconfigure | sudo gitlab-ctl reconfigure |
Enable HTTPS | Edit gitlab.rb ; reconfigure again |
Harden security/admin tasks | SSH config, 2FA, audit logs, regular upgrades |
Final Thoughts
This installation delivers a functional, secure starter GitLab environment, suitable for managed internal teams. Capacity planning becomes the key constraint as project size and CI loads grow—scale with caution. Alternative deployment models (containerized, HA, etc.) may be preferable for edge cases but introduce their own failure modes.
Side note: For disaster recovery, do a full dry-run restore from backup before trusting any setup in production.
Author: [Your Name]
Date: 2024-06-13