Self-Hosting GitLab: Complete CI/CD Setup Guide
Deploy your own GitLab instance with complete CI/CD pipelines. Replace GitHub Actions and CircleCI with unlimited self-hosted runners for free.
Self-Hosting GitLab: Complete CI/CD Setup Guide
GitHub costs $21/user/month for Teams plan. CircleCI charges $30/month for 2,500 credits (≈ 25 builds). Your 10-person engineering team spends $4,560/year on code hosting and CI/CD.
GitLab Community Edition provides unlimited repos, unlimited CI/CD minutes, and unlimited users—for the cost of a $40/month VPS.
This guide deploys a production-ready GitLab instance with integrated CI/CD in under 2 hours.
What You Get with Self-Hosted GitLab
Git repository management:
- Unlimited private repos
- Code review (merge requests)
- Protected branches
- Git LFS support
Built-in CI/CD:
- Unlimited pipeline minutes
- Docker support
- Kubernetes integration
- Auto DevOps
Project management:
- Issue tracking
- Milestones and boards
- Wiki and documentation
- Time tracking
Security features:
- Container scanning
- Dependency scanning
- SAST (Static Application Security Testing)
- License compliance
The only features locked behind GitLab EE (paid) are advanced security dashboards and compliance reports. Core functionality is 100% free.
Prerequisites and Planning
Infrastructure needs:
- VPS with 4GB RAM minimum (8GB recommended)
- 20GB storage minimum (grows with repo size)
- Ubuntu 22.04 LTS
- Domain name (e.g., git.yourcompany.com)
Cost breakdown:
- Hetzner CPX31 (4 vCPU, 8GB RAM): €11.50/month ($12.50)
- S3-compatible backup (100GB): $5/month
- Total: $17.50/month
Savings vs GitHub + CircleCI:
- GitHub Teams (10 users): $210/month
- CircleCI: $30/month
- Total SaaS: $240/month
- Self-hosted: $17.50/month
- Savings: $222.50/month ($2,670/year)
Step 1: Deploy GitLab (30 minutes)
Install GitLab using official packages:
# SSH into your server
ssh root@your-server-ip
# Install dependencies
apt-get update
apt-get install -y curl openssh-server ca-certificates tzdata perl
# Add GitLab package repository
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash
# Install GitLab (this takes 5-10 minutes)
EXTERNAL_URL="https://git.yourcompany.com" apt-get install gitlab-ce
What this does:
- Installs GitLab, PostgreSQL, Redis, NGINX
- Configures SSL automatically (Let's Encrypt)
- Sets up systemd services
- Creates initial configuration
Access GitLab:
- Visit https://git.yourcompany.com
- Root password stored in:
/etc/gitlab/initial_root_password - Login as
rootwith that password - Change password immediately
[AFFILIATE_CALLOUT_HERE]
Manually configuring GitLab's database pooling, Redis caching, and SSL certificates requires deep DevOps knowledge. Managed GitLab instances provide production-grade deployments with monitoring, backups, and automatic updates already configured.
Step 2: Configure GitLab (20 minutes)
Edit configuration:
nano /etc/gitlab/gitlab.rb
Essential settings:
# External URL (already set during install)
external_url 'https://git.yourcompany.com'
# Email configuration (using SMTP)
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "gitlab@yourcompany.com"
gitlab_rails['smtp_password'] = "your-app-password"
gitlab_rails['smtp_domain'] = "smtp.gmail.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['gitlab_email_from'] = 'gitlab@yourcompany.com'
# Increase worker processes (for 8GB RAM server)
puma['worker_processes'] = 4
# Configure backups
gitlab_rails['backup_keep_time'] = 604800 # 7 days
gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
Apply changes:
gitlab-ctl reconfigure
Step 3: Set Up GitLab Runner for CI/CD (30 minutes)
GitLab Runner executes your CI/CD jobs. Install on the same server or separate machines.
Install GitLab Runner:
# Add GitLab Runner repository
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | bash
# Install
apt-get install gitlab-runner
Register runner with GitLab:
gitlab-runner register
Configuration prompts:
GitLab instance URL: https://git.yourcompany.com
Registration token: [Get from GitLab Admin → Runners]
Description: docker-runner-01
Tags: docker,production
Executor: docker
Default Docker image: alpine:latest
Configure Docker executor:
nano /etc/gitlab-runner/config.toml
[[runners]]
name = "docker-runner-01"
url = "https://git.yourcompany.com"
executor = "docker"
[runners.docker]
privileged = true
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
Start runner:
gitlab-runner start
Step 4: Create Your First CI/CD Pipeline (20 minutes)
Create .gitlab-ci.yml in your repo:
stages:
- build
- test
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
test:
stage: test
image: node:18
script:
- npm install
- npm test
artifacts:
reports:
junit: test-results.xml
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh
- ssh user@production-server "docker pull $DOCKER_IMAGE && docker restart app"
only:
- main
when: manual
What this pipeline does:
- Build stage: Creates Docker image
- Test stage: Runs unit tests
- Deploy stage: Deploys to production (manual trigger)
Push to GitLab:
git add .gitlab-ci.yml
git commit -m "Add CI/CD pipeline"
git push origin main
Monitor pipeline:
Visit https://git.yourcompany.com/your-project/-/pipelines
Step 5: Configure Automated Backups (15 minutes)
Create backup script:
nano /usr/local/bin/gitlab-backup.sh
#!/bin/bash
# GitLab backup script
# Create backup
gitlab-backup create SKIP=registry
# Upload to S3-compatible storage
aws s3 cp /var/opt/gitlab/backups/$(ls -t /var/opt/gitlab/backups/ | head -1) \
s3://your-backup-bucket/gitlab-backups/ \
--endpoint-url=https://s3.wasabisys.com
# Clean old local backups (keep last 3)
cd /var/opt/gitlab/backups && ls -t | tail -n +4 | xargs rm -f
Make executable:
chmod +x /usr/local/bin/gitlab-backup.sh
Schedule daily backups:
crontab -e
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/gitlab-backup.sh
Step 6: Security Hardening (15 minutes)
Enable 2FA for admin:
- User Settings → Account → Two-Factor Authentication
- Scan QR code with authenticator app
- Require 2FA for all users: Admin → Settings → Sign-in restrictions
Configure SSH key authentication:
# Disable password authentication
nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
systemctl restart sshd
Set up firewall:
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP (redirects to HTTPS)
ufw allow 443/tcp # HTTPS
ufw enable
Enable security features:
- Admin → Settings → CI/CD → Container Registry (enable)
- Admin → Settings → Security → Dependency Scanning (enable)
- Admin → Settings → Security → SAST (enable)
Advanced CI/CD Patterns
Multi-Environment Deployment
deploy_staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.yourapp.com
only:
- develop
deploy_production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
url: https://yourapp.com
only:
- main
when: manual
Parallel Testing
test:parallel:
stage: test
parallel: 5
script:
- npm run test -- --partition=$CI_NODE_INDEX/$CI_NODE_TOTAL
Docker Build Caching
build:
image: docker:latest
services:
- docker:dind
variables:
DOCKER_BUILDKIT: 1
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Monitoring and Maintenance
Check GitLab health:
gitlab-rake gitlab:check
Monitor resource usage:
gitlab-ctl status
top -u git
View logs:
gitlab-ctl tail
Update GitLab:
apt-get update
apt-get install gitlab-ce
Restore from backup:
gitlab-backup restore BACKUP=1611682249_2021_01_26_13.8.4
Migration from GitHub/GitLab.com
Export GitHub repos:
# Use gh CLI
gh repo list --limit 100 | awk '{print $1}' | xargs -I {} gh repo clone {}
Import to GitLab:
- New Project → Import → GitHub
- Authenticate with GitHub
- Select repos to import
- GitLab imports code, issues, PRs
Migrate CI/CD: Convert GitHub Actions to GitLab CI:
# GitHub Actions
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm test
# GitLab CI equivalent
test:
image: node:18
script:
- npm install
- npm test
The Exit-Saas Perspective
GitHub and CircleCI aren't just expensive—they're black boxes. You can't audit their security, customize their infrastructure, or guarantee uptime.
Self-hosted GitLab gives you complete control over:
- Where code is stored
- Who has access
- Pipeline execution environment
- Data retention policies
Companies saving $2,500-10,000 annually by switching to self-hosted GitLab aren't sacrificing features—they're gaining sovereignty.
Browse our tools directory for CI/CD alternatives, container registries, and deployment automation tools.
The best repository for your code is the one you control.
Ready to Switch?
Deploy Your Open-Source Stack on DigitalOcean in 1-click
Get $200 in Free Credits
New users receive $200 credit valid for 60 days
Trusted by 600,000+ developers worldwide. Cancel anytime.