GitLab Self-Hosted: Installation, Administration, and Upgrades Turnkey
Storing code on GitLab.com is convenient until legal or compliance demands repositories inside your perimeter. Or when the bill for 500 users exceeds the budget. Self-hosted GitLab gives full control but burdens your team with operational responsibility: performance, security, upgrades. And if an upgrade fails, the service can be down for hours. We solve this turnkey: from choosing a deployment scheme to regular updates without downtime.
Over 5 years of work, we have deployed GitLab for more than 50 projects — from startups with 10 developers to enterprises with thousands of users. Each case required an individual approach to infrastructure, backups, and the update process. Below are specific schemes, configs, and pitfalls we encountered.
How to Choose an Installation Method?
Three official ways to deploy GitLab — Omnibus, Helm chart, and Docker Compose — each suited for different loads.
Omnibus (gitlab-ee deb/rpm) is the simplest option for a single server. Everything in one: Nginx, PostgreSQL, Redis, Sidekiq, Workhorse, Gitaly. Configuration via /etc/gitlab/gitlab.rb. Ideal for teams up to 500 people on a single powerful host. We recommend Omnibus for 90% of clients: it is 10 times easier to administer than Helm.
Helm chart (gitlab/gitlab) is for Kubernetes. Each component deploys as a separate Deployment: webservice, sidekiq, gitaly, kas, registry. Horizontal scaling comes at the cost of complexity: minimum resources for production — 16 CPU / 32 GB RAM just for GitLab. Choose Helm if your standard is Kubernetes and you are willing to invest in its administration.
Docker Compose — only for dev environments and testing. We do not use it in production. Example tuning of gitlab.rb for high load:
# Puma performance
puma['worker_processes'] = 4
puma['min_threads'] = 1
puma['max_threads'] = 4
# PostgreSQL
postgresql['shared_buffers'] = "2048MB"
postgresql['work_mem'] = "32MB"
postgresql['max_connections'] = 200
# Gitaly
gitaly['configuration'] = {
concurrency: [
{
rpc: "/gitaly.SmartHTTPService/PostUploadPack",
max_per_repo: 20
},
]
} Hardening: How to Protect Your Instance from Attacks
After installation, GitLab is more open to the internet than necessary. Minimum security steps:
- Disable public registration (
gitlab_rails['gitlab_signup_enabled'] = false) — otherwise the instance becomes a target for spam bots within hours. - Configure SMTP for notifications — without this, 2FA via email won't work.
- Enable HTTPS: in
gitlab.rbsetletsencrypt['enable'] = true,external_url 'https://git.company.com'. - Limit rate limiting:
gitlab_rails['rate_limit_requests_per_period']. - Configure LDAP/SAML for integration with your corporate IdP.
Separately — moving Gitaly to a separate server with fast NVMe. This reduces clone and push latency by 30% and lowers load on the main server.
Why Upgrades Break (and How to Avoid It)
GitLab requires sequential updates through intermediate versions. A direct jump from 15.x to 17.x is not possible. Each major update contains breaking changes. A typical case: upgrading from 16.x to 17.0 broke installations with custom PostgreSQL settings due to unfinished background migrations. Error: PG::UndefinedTable: ERROR: relation "ci_runners" does not exist. Solution — before upgrading, wait for all migrations to finish with gitlab-rake gitlab:background_migrations:finalize.
Our upgrade process (we do this for clients quarterly):
- Check for unfinished background migrations.
- Make a full backup:
gitlab-backup create SKIP=artifacts(artifacts separately in object storage). - Sequentially upgrade according to the GitLab compatibility matrix.
- After each step, run
gitlab-ctl statusandgitlab-rake gitlab:check.
We guarantee no downtime: all operations are performed in rolling mode or with a minimal maintenance window.
Backup and Restore
By default, gitlab-backup stores the archive locally — on the same disk as the data. That's not a backup, it's an illusion. We configure Object Storage:
gitlab_rails['backup_upload_connection'] = { 'provider' => 'AWS', 'region' => 'eu-central-1', 'aws_access_key_id' => ENV['AWS_ACCESS_KEY_ID'], 'aws_secret_access_key' => ENV['AWS_SECRET_ACCESS_KEY'] }
gitlab_rails['backup_upload_remote_directory'] = 'my-gitlab-backups'
gitlab_rails['backup_keep_time'] = 604800 # 7 days
Cron: 0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1. We test restoration every quarter — this is a mandatory procedure.
Timelines and Process
| Task | Duration |
|---|---|
| Basic Omnibus installation + HTTPS + SMTP | 1 day |
| Hardening + LDAP/SAML integration | 1-2 days |
| Backup configuration + Object Storage | half day |
| Migration of existing repositories | from 1 day |
| Planned upgrade (major version) | 1 day |
Full cycle "from scratch to production" takes 3-5 days. Cost is calculated individually after auditing your requirements.
What the Work Includes
| Component | Description |
|---|---|
| Current infrastructure audit | Load, security, SLA assessment |
| Deployment scheme selection | Omnibus / Helm / cloud |
| Installation and configuration | All components, tuning for load |
| IdP integration | LDAP/SAML/OAuth |
| Backup setup | Object Storage, rotation, restoration testing |
| Upgrade plan | Regular updates without downtime |
| Documentation and training | Administrator instructions |
We work with GitLab constantly: 5+ years, 50+ projects. We will assess your project in 1 day. Contact us for a consultation — we will analyze your situation and offer a solution.







