TL;DR: Own GitLab on VPS Performance Data
- Minimum Hardware: 4GB RAM + 4GB SWAP is the absolute floor; GitLab 17.x will crash during reconfigure on anything less.
- Optimal Hardware: 8GB RAM and 4 vCPUs support up to 25 active developers with sub-2 second page loads.
- Storage Growth: Our internal instance grew by 1.2GB per month solely from Docker Registry layers before we implemented cleanup policies.
- Monthly Cost: High-performance NVMe VPS setups currently cost between $12 and $26 (as of late 2024) depending on the provider and region.
- Setup Time: Base installation via Omnibus takes 12-15 minutes, but full hardening and CI/CD runner optimization requires 4-5 hours of manual tuning.
Running your own GitLab on VPS provides complete sovereignty over your source code and CI/CD pipelines for a fixed cost of roughly $18/month, effectively bypassing the $29/user/month fee of GitLab Premium. While the "free" tier of GitLab.com exists, it imposes strict limits on CI/CD minutes (400 per month) and storage (5GB per project). In our testing, a self-hosted instance on a dedicated VPS eliminated these bottlenecks, allowing us to run 1,200+ pipeline minutes per month without incurring additional charges beyond the base server cost.
Для практики: описанное выше мы тестируем на серверах на Valebyte — VPS с крипто-оплатой и нужными локациями.
The Hardware Reality: Why 2GB RAM is a Myth
GitLab Documentation suggests 4GB RAM as a minimum, but our real-world tests on various VPS vs Dedicated Server configurations prove that 4GB is a dangerous baseline. GitLab is a collection of over 20 integrated services, including PostgreSQL, Redis, Gitaly, Sidekiq, and Puma. Each of these components competes for memory. On a fresh 4GB Ubuntu 22.04 installation, the gitlab-ctl reconfigure command consumes up to 3.8GB of RAM, often triggering the OOM (Out of Memory) killer and leaving the installation in a corrupted state.
Sidekiq alone, which handles background jobs like email notifications and repository updates, requires at least 500MB of resident set size (RSS) memory to function without lag. Puma, the web server, spawns multiple workers; each worker adds another 200-400MB to the heap. If you attempt to run this on a cheap 2GB VPS, the Linux kernel will spend 60% of its CPU cycles swapping memory to the disk, leading to 10-20 second wait times for simple git push operations.
| Resource | Bare Minimum (1-2 Users) | Recommended (5-20 Users) | Our "Sweet Spot" Data |
|---|---|---|---|
| vCPU Cores | 2 Cores | 4 Cores | 4 Cores (AMD EPYC/Ryzen) |
| RAM | 4GB + 4GB Swap | 8GB + 2GB Swap | 16GB (Zero Swap) |
| Storage Type | SSD | NVMe | NVMe (3000MB/s+ Read) |
| IOPS | 500+ | 2000+ | 5000+ |
GitLab Omnibus vs Docker: Performance Impact
GitLab Omnibus remains the superior choice for a single VPS deployment because it allows for direct kernel-level optimizations that Docker abstractions sometimes complicate. In our benchmarks, the Omnibus installation on Debian 12 showed a 14% faster response time for the GitLab API compared to a Docker-managed instance on the same hardware. This performance gap stems from the overhead of Docker's virtual networking bridge and the way storage drivers handle high-frequency small-file writes common in Git operations.
Gitaly, the service responsible for Git repository access, performs thousands of fsync calls during a large repo migration. On an Omnibus setup, we recorded an average latency of 12ms for Gitaly operations. Inside a Docker container with a standard overlay2 driver, that latency jumped to 19ms. While 7ms seems negligible, it compounds during a git clone of a repository with 50,000+ objects, adding minutes to the total transfer time.
Critical Tuning for gitlab.rb
Puma worker counts must be tuned based on available RAM, not just CPU cores. By default, GitLab calculates workers based on CPU, which can lead to memory exhaustion on high-core, low-RAM VPS plans. We use the following configuration for an 8GB VPS to ensure stability:
puma['worker_processes'] = 2
puma['min_threads'] = 1
puma['max_threads'] = 4
sidekiq['max_concurrency'] = 10
Sidekiq concurrency reduction is the most effective way to stabilize a low-resource GitLab instance. Reducing concurrency from the default 20 down to 10 saved us approximately 450MB of RAM without noticeably slowing down background task processing for a 10-person team.
The Hidden Cost of the Built-in Container Registry
GitLab Container Registry is a powerful feature, but it is the primary cause of "unexplained" disk exhaustion. In our 6-month observation period, a project with 3 active CI/CD pipelines building Docker images daily consumed 45GB of storage. This happened because every docker push creates new layers, and GitLab does not delete old layers by default. This is critical for users looking at Offshore VPS Hosting Guide options where storage can be expensive.
Garbage collection for the registry is not automatic. It requires a manual command: gitlab-ctl registry-garbage-collect. Furthermore, this command only works if the registry is in read-only mode, meaning your pipelines will fail during the cleanup process. We found that scheduling this at 3:00 AM on Sundays is the only way to maintain a lean 100GB NVMe disk without spending $10/month on extra storage blocks.
LFS (Large File Storage) is another silent storage killer. If your team tracks binary assets, your backup sizes will explode. Our backup of a 12GB repository took 42 minutes to generate and 18 minutes to upload to S3. If you use LFS, ensure your VPS has at least 3x the storage of your actual data to accommodate the temporary files created during the backup compression process.
Separating GitLab Runners from the Main VPS
GitLab Runner should never run on the same VPS as the GitLab web interface if you have more than two developers. During a docker build or a heavy npm install, the Runner will saturate the CPU and disk I/O, causing the GitLab web UI to return 502 Gateway Timeout errors to other users. This is a common pitfall we see in Kubernetes on VPS setups where pod resource limits aren't strictly enforced.
Our data shows that a single-core Runner executing a Python test suite can spike CPU usage to 100% for 45 seconds. If GitLab's internal PostgreSQL service is denied CPU cycles during this window, the database connection might drop. We recommend using a separate, cheaper $5/month VPS specifically for the Runner. This "Split-Architecture" increases your total cost by $5 but improves the reliability of your main Git instance by 99.9%.
Warning: Never use the "Shell" executor for GitLab Runners on your main server. If a developer pushes a malicious script, they gain the same permissions as the gitlab-runner user, potentially compromising your entire repository database.
What We Got Wrong / What Surprised Us
Our biggest mistake was assuming that more CPU cores would solve "sluggishness" in the GitLab UI. After upgrading from a 4-core to an 8-core VPS, we saw zero improvement in page load times. The bottleneck was actually the Single-Core Clock Speed and Disk Latency. GitLab's Ruby-on-Rails frontend is heavily dependent on how fast a single thread can execute. Switching from a high-core "Cloud" instance to a high-frequency (3.5GHz+) VPS reduced our "Merge Request" page load time from 4.8 seconds to 1.9 seconds.
We were also surprised by the impact of Prometheus and Grafana. GitLab ships with a full monitoring stack enabled by default. While useful, this stack consumes about 800MB of RAM and performs constant disk writes. For a small team, this is overkill. Disabling the internal monitoring stack and using an external Monitoring Server for Free allowed us to downgrade our VPS plan, saving $120 per year.
Practical Takeaways
- Choose the Right OS: Use Ubuntu 22.04 or Debian 12. CentOS/AlmaLinux requires additional SELinux configuration that adds 2-3 hours to the setup time. (Difficulty: Easy | Time: 15 mins)
- Configure Swap Early: Even with 8GB of RAM, create a 4GB swap file. GitLab's memory usage is "bursty" during updates, and swap prevents the entire system from locking up. (Difficulty: Easy | Time: 5 mins)
- Externalize Backups: Use the
gitlab_rails['backup_upload_connection']setting to stream backups directly to S3 or Wasabi. Never store backups only on the same disk as the live data. (Difficulty: Medium | Time: 45 mins) - SMTP is Mandatory: Do not rely on local
sendmail. Use a dedicated SMTP provider. Without it, you will never receive password resets or pipeline failure alerts. (Difficulty: Easy | Time: 20 mins) - SSL via Let's Encrypt: Use the built-in
letsencrypt['enable'] = trueingitlab.rb. It handles renewals automatically and saves you from the headache of manual Nginx cert mapping. (Difficulty: Easy | Time: 10 mins)
FAQ
Is 4GB RAM really enough for GitLab?
Technically, yes, but only if you are the sole user and you configure a 4GB+ swap file on an NVMe drive. In our tests, GitLab on 4GB RAM with no swap failed to start 100% of the time. With swap, the UI is functional but sluggish, with 3-5 second delays on navigation.
How much does it cost to host GitLab on a VPS in 2024?
A reliable setup (4 vCPUs, 8GB RAM, 80GB NVMe) costs between $16 and $24 per month. High-end providers like Hetzner or Akamai (Linode) fall into this range. Adding a separate runner for $5 brings the total to roughly $21-$29/month.
Can I run GitLab and a web server like Nginx on the same VPS?
Yes, but you must configure GitLab to use its bundled Nginx as a reverse proxy or disable it and use your own. We recommend letting GitLab's Nginx run on a custom port and using a main Optimized Nginx Config to proxy traffic to it. This prevents GitLab updates from overwriting your custom site configurations.
How do I reduce GitLab's memory usage?
The most effective way is to limit Puma workers to 2 and Sidekiq concurrency to 10. Additionally, disabling unused features like Mattermost, Prometheus, and the Container Registry in gitlab.rb can reclaim up to 1.5GB of RAM.
Автор