Set up a complete self-hosted GitLab CE instance with SSL encryption, Docker-based CI/CD runners, automated backup system, and security hardening for production workloads.
Prerequisites
- Minimum 4GB RAM (8GB recommended)
- 2 CPU cores
- 50GB available disk space
- Domain name with DNS pointing to server
- Root or sudo access
What this solves
GitLab Community Edition provides a complete DevOps platform for source code management, CI/CD pipelines, and project collaboration. This tutorial shows you how to install GitLab CE with SSL certificates, configure GitLab Runner with Docker executor for automated testing and deployment, and implement automated backup procedures with monitoring.
Prerequisites
Step-by-step installation
Update system packages and install dependencies
Start by updating your system and installing required packages for GitLab CE installation.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl openssh-server ca-certificates tzdata perl postfix
Add GitLab CE repository
Download and add the official GitLab repository to your system package manager.
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
Install GitLab CE
Install GitLab Community Edition with your domain name configured for SSL setup.
sudo EXTERNAL_URL="https://gitlab.example.com" apt install -y gitlab-ce
Configure SSL with Let's Encrypt
Enable automatic SSL certificate management using Let's Encrypt for secure HTTPS access.
external_url 'https://gitlab.example.com'
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['admin@example.com']
letsencrypt['auto_renew'] = true
letsencrypt['auto_renew_hour'] = 2
letsencrypt['auto_renew_minute'] = 30
letsencrypt['auto_renew_day_of_month'] = "*/7"
Configure GitLab settings
Set up basic GitLab configuration including email notifications, backup retention, and performance settings.
gitlab_rails['gitlab_email_enabled'] = true
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_email_display_name'] = 'GitLab'
gitlab_rails['backup_keep_time'] = 604800
gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
gitlab_rails['backup_upload_connection'] = {
'provider' => 'Local',
'local_root' => '/opt/backups'
}
puma['worker_processes'] = 2
puma['min_threads'] = 4
puma['max_threads'] = 4
postgresql['shared_buffers'] = "512MB"
postgresql['max_connections'] = 200
Apply configuration and start GitLab
Reconfigure GitLab to apply all settings and start all services.
sudo gitlab-ctl reconfigure
sudo gitlab-ctl status
Configure firewall rules
Open necessary ports for GitLab web interface, SSH access, and container registry.
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
Install Docker for GitLab Runner
Install Docker to provide container execution environment for CI/CD pipelines.
sudo apt install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker gitlab-runner
Install GitLab Runner
Install GitLab Runner to execute CI/CD jobs defined in your repositories.
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install -y gitlab-runner
Register GitLab Runner
Register the runner with your GitLab instance using a registration token from GitLab admin area.
sudo gitlab-runner register \
--non-interactive \
--url "https://gitlab.example.com" \
--registration-token "your-registration-token" \
--executor "docker" \
--docker-image alpine:latest \
--description "docker-runner" \
--tag-list "docker,linux" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"
Configure GitLab Runner for Docker
Optimize the runner configuration for Docker executor with caching and security settings.
concurrent = 2
check_interval = 3
[[runners]]
name = "docker-runner"
url = "https://gitlab.example.com"
token = "runner-token-here"
executor = "docker"
[runners.docker]
tls_verify = false
image = "alpine:latest"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
extra_hosts = ["host.docker.internal:host-gateway"]
shm_size = 0
[runners.cache]
Type = "local"
Path = "/tmp/gitlab-runner-cache"
Shared = true
Set up automated backups
Create a comprehensive backup script that includes GitLab data, configuration files, and SSL certificates.
#!/bin/bash
set -e
BACKUP_DIR="/opt/backups"
DATE=$(date +"%Y%m%d_%H%M%S")
RETENTION_DAYS=7
Create backup directory
mkdir -p $BACKUP_DIR
Create GitLab backup
echo "Creating GitLab backup..."
gitlab-backup create BACKUP_DIR=$BACKUP_DIR
Backup configuration files
echo "Backing up configuration files..."
tar -czf $BACKUP_DIR/gitlab-config-$DATE.tar.gz \
/etc/gitlab/gitlab.rb \
/etc/gitlab/gitlab-secrets.json \
/etc/ssh/ \
/etc/letsencrypt/ 2>/dev/null || true
Backup GitLab Runner configuration
cp /etc/gitlab-runner/config.toml $BACKUP_DIR/gitlab-runner-config-$DATE.toml
Clean old backups
find $BACKUP_DIR -name "*.tar" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.toml" -mtime +$RETENTION_DAYS -delete
echo "Backup completed successfully"
echo "Backup location: $BACKUP_DIR"
ls -lah $BACKUP_DIR/ | tail -5
Make backup script executable and schedule
Set proper permissions and create a cron job for automated daily backups.
sudo chmod 755 /opt/gitlab-backup.sh
sudo chown root:root /opt/gitlab-backup.sh
sudo mkdir -p /opt/backups
sudo chown git:git /opt/backups
sudo chmod 755 /opt/backups
Configure automated backup scheduling
Set up daily backups at 2 AM with email notifications on failure.
sudo crontab -e
0 2 * /opt/gitlab-backup.sh >> /var/log/gitlab-backup.log 2>&1 || echo "GitLab backup failed on $(hostname)" | mail -s "Backup Failure" admin@example.com
Create backup restore script
Prepare a restore script for disaster recovery scenarios.
#!/bin/bash
set -e
if [ "$#" -ne 1 ]; then
echo "Usage: $0 "
echo "Example: $0 20240115_020001"
exit 1
fi
BACKUP_TIMESTAMP=$1
BACKUP_DIR="/opt/backups"
echo "Stopping GitLab services..."
gitlab-ctl stop puma
gitlab-ctl stop sidekiq
echo "Restoring GitLab data..."
gitlab-backup restore BACKUP=$BACKUP_TIMESTAMP BACKUP_DIR=$BACKUP_DIR
echo "Restoring configuration files..."
if [ -f "$BACKUP_DIR/gitlab-config-$BACKUP_TIMESTAMP.tar.gz" ]; then
tar -xzf "$BACKUP_DIR/gitlab-config-$BACKUP_TIMESTAMP.tar.gz" -C /
fi
echo "Reconfiguring GitLab..."
gitlab-ctl reconfigure
gitlab-ctl restart
echo "Restore completed successfully"
sudo chmod 755 /opt/gitlab-restore.sh
sudo chown root:root /opt/gitlab-restore.sh
Configure monitoring and health checks
Set up basic monitoring to track GitLab performance and runner status.
#!/bin/bash
set -e
HEALTH_URL="https://gitlab.example.com/-/health"
RUNNER_STATUS=$(gitlab-runner status 2>&1 || echo "runner not running")
GITLAB_STATUS=$(gitlab-ctl status | grep -c "run:" || echo "0")
echo "=== GitLab Health Check $(date) ==="
echo "GitLab Services Running: $GITLAB_STATUS"
echo "Runner Status: $RUNNER_STATUS"
Check HTTP health endpoint
if curl -sf "$HEALTH_URL" > /dev/null; then
echo "HTTP Health Check: OK"
else
echo "HTTP Health Check: FAILED"
echo "GitLab may be experiencing issues" | mail -s "GitLab Health Alert" admin@example.com
fi
Check disk space
DISK_USAGE=$(df /var/opt/gitlab | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 85 ]; then
echo "WARNING: Disk usage is $DISK_USAGE%"
echo "GitLab disk usage is $DISK_USAGE% on $(hostname)" | mail -s "GitLab Disk Alert" admin@example.com
else
echo "Disk Usage: $DISK_USAGE%"
fi
sudo chmod 755 /opt/gitlab-health-check.sh
sudo chown root:root /opt/gitlab-health-check.sh
Schedule health monitoring
Run health checks every 15 minutes to detect issues early.
/15 * /opt/gitlab-health-check.sh >> /var/log/gitlab-health.log 2>&1
Apply security hardening
Configure security settings including rate limiting, session timeouts, and access controls.
# Security settings
gitlab_rails['gitlab_shell_ssh_port'] = 22
gitlab_rails['rate_limit_requests_per_period'] = 10
gitlab_rails['rate_limit_period'] = 60
gitlab_rails['session_expire_delay'] = 10080
gitlab_rails['password_authentication_enabled_for_web'] = true
gitlab_rails['password_authentication_enabled_for_git'] = true
Disable unused features
gitlab_rails['gravatar_enabled'] = false
gitlab_rails['usage_ping_enabled'] = false
prometheus_monitoring['enable'] = false
alertmanager['enable'] = false
Configure fail2ban integration
nginx['real_ip_trusted_addresses'] = ['127.0.0.1/32']
nginx['real_ip_header'] = 'X-Real-IP'
nginx['real_ip_recursive'] = 'on'
Apply final configuration
Reconfigure GitLab to apply all security and monitoring settings.
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart
sudo systemctl restart gitlab-runner
Verify your setup
Confirm that GitLab CE, runners, and backup systems are working correctly.
# Check GitLab services status
sudo gitlab-ctl status
Verify GitLab Runner registration
sudo gitlab-runner list
Test SSL certificate
curl -I https://gitlab.example.com
Check backup directory
ls -la /opt/backups/
Test backup script
sudo /opt/gitlab-backup.sh
View GitLab logs
sudo gitlab-ctl tail
Check Docker functionality
sudo docker run --rm hello-world
Access your GitLab instance at https://gitlab.example.com and log in with the root account. The initial password is stored in:
sudo cat /etc/gitlab/initial_root_password
Performance optimization
| Setting | 4GB RAM | 8GB RAM | 16GB+ RAM |
|---|---|---|---|
| puma worker_processes | 2 | 4 | 8 |
| postgresql shared_buffers | 256MB | 512MB | 1GB |
| gitlab_rails backup_keep_time | 259200 (3 days) | 604800 (7 days) | 1209600 (14 days) |
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway error | GitLab services not started | sudo gitlab-ctl restart |
| Runner jobs fail immediately | Docker permission issues | sudo usermod -aG docker gitlab-runner && sudo systemctl restart gitlab-runner |
| SSL certificate issues | Let's Encrypt rate limits | Check /var/log/gitlab/nginx/error.log and wait before retry |
| Backup fails with permission denied | Incorrect backup directory ownership | sudo chown git:git /opt/backups && sudo chmod 755 /opt/backups |
| High memory usage | Default settings too high | Reduce puma workers and PostgreSQL buffers in gitlab.rb |
| Repository clone fails | SSH key or firewall issues | Check SSH port 22 is open and keys are properly configured |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
GITLAB_DOMAIN=""
ADMIN_EMAIL=""
RUNNER_TOKEN=""
# Usage function
usage() {
echo "Usage: $0 -d <domain> -e <admin_email> [-t <runner_token>]"
echo " -d: GitLab domain (e.g., gitlab.example.com)"
echo " -e: Administrator email"
echo " -t: GitLab Runner registration token (optional, can be added later)"
exit 1
}
# Parse arguments
while getopts "d:e:t:h" opt; do
case $opt in
d) GITLAB_DOMAIN="$OPTARG" ;;
e) ADMIN_EMAIL="$OPTARG" ;;
t) RUNNER_TOKEN="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required arguments
if [[ -z "$GITLAB_DOMAIN" || -z "$ADMIN_EMAIL" ]]; then
usage
fi
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Check logs above.${NC}"
}
trap cleanup ERR
# Check prerequisites
echo -e "${YELLOW}[0/10] Checking prerequisites...${NC}"
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Auto-detect distribution
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}/etc/os-release not found. Cannot detect distribution.${NC}"
exit 1
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewalld"
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_CMD="firewalld"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
# Update system packages
echo -e "${YELLOW}[1/10] Updating system packages...${NC}"
$PKG_UPDATE
# Install dependencies
echo -e "${YELLOW}[2/10] Installing dependencies...${NC}"
$PKG_INSTALL curl openssh-server ca-certificates tzdata perl
# Install and configure Postfix non-interactively
if [[ "$PKG_MGR" == "apt" ]]; then
debconf-set-selections <<< "postfix postfix/mailname string $GITLAB_DOMAIN"
debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'"
$PKG_INSTALL postfix
else
$PKG_INSTALL postfix
systemctl enable postfix
systemctl start postfix
fi
# Add GitLab repository
echo -e "${YELLOW}[3/10] Adding GitLab CE repository...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash
else
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | bash
fi
# Install GitLab CE
echo -e "${YELLOW}[4/10] Installing GitLab CE...${NC}"
EXTERNAL_URL="https://$GITLAB_DOMAIN" $PKG_INSTALL gitlab-ce
# Configure GitLab
echo -e "${YELLOW}[5/10] Configuring GitLab...${NC}"
cat >> /etc/gitlab/gitlab.rb << EOF
external_url 'https://$GITLAB_DOMAIN'
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['$ADMIN_EMAIL']
letsencrypt['auto_renew'] = true
letsencrypt['auto_renew_hour'] = 2
letsencrypt['auto_renew_minute'] = 30
letsencrypt['auto_renew_day_of_month'] = "*/7"
gitlab_rails['gitlab_email_enabled'] = true
gitlab_rails['gitlab_email_from'] = 'gitlab@$(echo $GITLAB_DOMAIN | cut -d. -f2-)'
gitlab_rails['gitlab_email_display_name'] = 'GitLab'
gitlab_rails['backup_keep_time'] = 604800
gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
puma['worker_processes'] = 2
puma['min_threads'] = 4
puma['max_threads'] = 4
postgresql['shared_buffers'] = "512MB"
postgresql['max_connections'] = 200
EOF
# Create backup directory
mkdir -p /opt/backups
chown git:git /opt/backups
chmod 755 /opt/backups
# Reconfigure GitLab
echo -e "${YELLOW}[6/10] Applying GitLab configuration...${NC}"
gitlab-ctl reconfigure
# Configure firewall
echo -e "${YELLOW}[7/10] Configuring firewall...${NC}"
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
else
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Install Docker
echo -e "${YELLOW}[8/10] Installing Docker...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL docker.io
else
$PKG_INSTALL docker
fi
systemctl enable docker
systemctl start docker
# Install GitLab Runner
echo -e "${YELLOW}[9/10] Installing GitLab Runner...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | bash
$PKG_INSTALL gitlab-runner
else
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | bash
$PKG_INSTALL gitlab-runner
fi
# Add gitlab-runner to docker group
usermod -aG docker gitlab-runner
systemctl enable gitlab-runner
systemctl start gitlab-runner
# Setup automated backups
echo -e "${YELLOW}[10/10] Setting up automated backups...${NC}"
cat > /etc/cron.d/gitlab-backup << 'EOF'
# GitLab backup cron job - runs daily at 2 AM
0 2 * * * root /opt/gitlab/bin/gitlab-backup create CRON=1 && find /var/opt/gitlab/backups -name "*.tar" -mtime +7 -delete
EOF
chmod 644 /etc/cron.d/gitlab-backup
# Verification
echo -e "${YELLOW}Verifying installation...${NC}"
sleep 10
if ! gitlab-ctl status | grep -q "run:"; then
echo -e "${RED}GitLab services are not running properly${NC}"
exit 1
fi
if ! systemctl is-active --quiet docker; then
echo -e "${RED}Docker is not running${NC}"
exit 1
fi
if ! systemctl is-active --quiet gitlab-runner; then
echo -e "${RED}GitLab Runner is not running${NC}"
exit 1
fi
# Get initial root password
INITIAL_PASSWORD=$(cat /etc/gitlab/initial_root_password 2>/dev/null | grep Password: | cut -d' ' -f2 || echo "Not found")
echo -e "${GREEN}GitLab CE installation completed successfully!${NC}"
echo ""
echo -e "${GREEN}Access your GitLab instance at: https://$GITLAB_DOMAIN${NC}"
echo -e "${GREEN}Initial root password: $INITIAL_PASSWORD${NC}"
echo -e "${YELLOW}Please change the root password after first login${NC}"
echo ""
if [[ -n "$RUNNER_TOKEN" ]]; then
echo -e "${YELLOW}To register the GitLab Runner, run:${NC}"
echo "gitlab-runner register --url https://$GITLAB_DOMAIN --registration-token $RUNNER_TOKEN --executor docker --docker-image alpine:latest"
else
echo -e "${YELLOW}To register GitLab Runner later:${NC}"
echo "1. Go to Admin Area > Runners in GitLab UI"
echo "2. Copy the registration token"
echo "3. Run: gitlab-runner register --url https://$GITLAB_DOMAIN --registration-token TOKEN --executor docker --docker-image alpine:latest"
fi
Review the script before running. Execute with: bash install.sh