How to Self-Host GitLab
GitLab Community Edition (CE) is the open source version of GitLab, released under the MIT License. It includes the core features that most development teams need: Git repository hosting, merge requests with code review, CI/CD pipelines, a built-in container registry, package registry, issue boards, and wiki. The paid Enterprise Edition adds advanced features like SAML SSO, advanced SAST scanning, and compliance dashboards, but the Community Edition is complete enough for most self-hosted deployments.
Step 1: Plan Server Requirements and Choose an Installation Method
GitLab is a large application that bundles PostgreSQL, Redis, Puma (the application server), Sidekiq (background job processor), Gitaly (Git storage), and several other components. The minimum hardware for a small team (up to 20 users) is 4 CPU cores, 8 GB of RAM, and 50 GB of storage. For teams of 100 users, plan for 8 cores, 16 GB of RAM, and 200 GB of storage. The storage estimate depends heavily on repository sizes and CI/CD artifact retention.
GitLab offers three installation methods. The Linux package (Omnibus) is the recommended approach for most deployments, bundling all components into a single installation managed through a central configuration file at /etc/gitlab/gitlab.rb. Docker installation runs GitLab as a container, which simplifies updates but adds complexity around persistent storage. The Kubernetes Helm chart deploys GitLab as a distributed set of pods, suitable for large-scale installations that need horizontal scaling.
For most teams, the Linux package on Ubuntu LTS or Debian is the most straightforward and best-documented path. The examples in this guide follow that approach.
Step 2: Install GitLab Community Edition
On Ubuntu or Debian, the installation begins with adding the GitLab package repository. The official installation script at packages.gitlab.com handles repository setup, GPG key import, and dependency installation. After adding the repository, installing GitLab CE is a single package manager command.
Before running the configuration step, edit /etc/gitlab/gitlab.rb to set the external_url parameter to your domain (for example, https://gitlab.yourdomain.com). This URL determines how GitLab generates links, configures its web server, and sets up SSL. Getting this right from the start avoids reconfiguration later.
Running gitlab-ctl reconfigure applies the configuration, initializes the PostgreSQL database, creates the necessary directories, starts all services, and generates the initial root password. The first reconfigure takes several minutes as it sets up the entire stack. Once complete, you can access the GitLab web interface at your configured URL and log in with the root account.
The initial root password is stored in /etc/gitlab/initial_root_password and is automatically deleted after 24 hours for security. Log in and change it immediately, then create your personal admin account and disable direct root login.
Step 3: Configure SSL and DNS
HTTPS is essential for protecting Git credentials, session cookies, and CI/CD secrets in transit. GitLab's Omnibus package includes built-in Let's Encrypt support that automatically provisions and renews SSL certificates.
First, create a DNS A record pointing your GitLab domain (e.g., gitlab.yourdomain.com) to your server's public IP address. DNS propagation typically completes within minutes to hours depending on your registrar and TTL settings.
In /etc/gitlab/gitlab.rb, ensure your external_url starts with https:// and add the Let's Encrypt configuration: set letsencrypt['enable'] to true and letsencrypt['contact_emails'] to your email address. Run gitlab-ctl reconfigure, and GitLab will automatically request a certificate from Let's Encrypt, configure Nginx to use it, and set up a cron job for automatic renewal every 90 days.
If you use your own certificates (from an internal CA or a commercial provider), place the certificate and key files in /etc/gitlab/ssl/ with filenames matching your domain, then set nginx['ssl_certificate'] and nginx['ssl_certificate_key'] in gitlab.rb to point to them.
Step 4: Set Up GitLab Runner for CI/CD
GitLab Runner is the agent that executes CI/CD pipeline jobs. It is a separate application from GitLab itself and should ideally run on a different machine to keep build workloads from affecting your GitLab server's performance. However, for small teams, running the runner on the same server is acceptable.
Install GitLab Runner from its own package repository (also hosted at packages.gitlab.com). After installation, register the runner with your GitLab instance using the registration token found in your GitLab admin area under CI/CD settings. During registration, you choose an executor type.
The Docker executor is the most popular choice. Each CI/CD job runs in a fresh Docker container, providing isolation between jobs and a clean environment for every build. This requires Docker to be installed on the runner machine. The shell executor runs jobs directly on the runner's operating system, which is simpler but provides no isolation between jobs. The Kubernetes executor launches each job as a Kubernetes pod, which is ideal if your runner infrastructure is a Kubernetes cluster.
You can register multiple runners with different tags, allowing pipelines to route specific jobs to specific runners. For example, you might have runners tagged "docker" for standard builds, "gpu" for machine learning jobs, and "deploy" for deployment tasks that need access to production credentials.
Step 5: Enable the Container Registry
GitLab's built-in container registry lets your CI/CD pipelines build Docker images and push them to a registry that is tightly integrated with your source code. Images are organized per-project, and access control follows the same permissions as the Git repository.
In /etc/gitlab/gitlab.rb, set registry_external_url to a URL on your GitLab domain (commonly https://registry.yourdomain.com or https://gitlab.yourdomain.com:5050). If you use Let's Encrypt, GitLab handles the certificate for the registry domain automatically. Run gitlab-ctl reconfigure to apply the changes.
Once enabled, every project in your GitLab instance has a container registry accessible from the project sidebar. CI/CD pipelines can authenticate to the registry using the built-in CI_REGISTRY_USER and CI_REGISTRY_PASSWORD variables, build images, and push them without any external registry configuration. The container registry supports garbage collection to reclaim storage from deleted image tags.
Step 6: Configure Backups and Monitoring
GitLab includes a built-in backup tool that creates a tar archive of repositories, database content, uploads, CI/CD artifacts, and other data. Run gitlab-backup create to produce a backup file in /var/opt/gitlab/backups/. For automated daily backups, add this command to cron (the Omnibus package does not set this up automatically).
Configure backup retention in gitlab.rb with the gitlab_rails['backup_keep_time'] parameter (in seconds). A common setting is 604800 for seven days of retention. Store backups off-server by configuring GitLab to upload them to an S3-compatible bucket, Google Cloud Storage, or Azure Blob Storage using the backup upload settings in gitlab.rb.
Important: the backup does not include the configuration files (/etc/gitlab/gitlab.rb and /etc/gitlab/gitlab-secrets.json). Back these up separately, as they contain encryption keys needed to restore the database. Without gitlab-secrets.json, encrypted data (CI/CD variables, two-factor authentication secrets) cannot be decrypted from the backup.
GitLab Omnibus bundles Prometheus and Grafana for monitoring. Prometheus collects metrics from all GitLab components, and Grafana dashboards visualize them. Access the monitoring at /-/grafana on your GitLab instance. Key metrics to watch include Puma worker saturation, Sidekiq queue depth, Gitaly request latency, and PostgreSQL connection pool usage.
Step 7: Tune Performance and Harden Security
GitLab's default configuration is optimized for a broad range of hardware. For your specific server, adjusting a few key parameters can significantly improve performance.
Puma, the web application server, defaults to a number of workers based on CPU cores. For a 4-core server with 8 GB RAM, reducing puma['worker_processes'] to 2 and adjusting puma['min_threads'] and puma['max_threads'] keeps memory usage manageable while maintaining responsiveness. Sidekiq concurrency controls how many background jobs run in parallel, and sidekiq['concurrency'] can be tuned based on available CPU and memory.
PostgreSQL tuning depends on available RAM. Key parameters include shared_buffers (typically 25% of total RAM), effective_cache_size (typically 50-75% of total RAM), and work_mem (typically 4-8 MB). These are set in gitlab.rb under the postgresql section.
For security hardening, enable two-factor authentication enforcement for all users or specific groups. Restrict sign-up to approved email domains or disable public sign-up entirely. Configure IP-based access restrictions if your GitLab instance should only be accessible from specific networks. Enable audit logging to track administrative actions. And keep GitLab updated regularly, as security patches are released frequently, typically on the 22nd of each month or as emergency patches for critical vulnerabilities.
Self-hosting GitLab CE gives you a complete DevOps platform (source control, CI/CD, container registry) on your own infrastructure at no licensing cost. The Omnibus package makes installation straightforward, but plan for adequate hardware, configure automated backups including the secrets file, and keep the instance updated for security.