Deployment8 min read

Install GitLab CE on a VPS: complete guide

GitHub and Bitbucket host your repositories, but they set the rules: metered CI minutes, uncertain code ownership, pricing that rises with team size. GitLab CE (Community Edition, MIT/EE Core licence) brings together on a single server a complete git forge, a CI/CD engine with no imposed minute quotas, a private Docker registry and wikis — all without a SaaS subscription. This guide shows you how to install it in under thirty minutes on a VPS with Docker Compose, configure SMTP, connect a runner and avoid the classic pitfalls: a misconfigured GITLAB_OMNIBUS_CONFIG, SSL that expires for lack of ACME, an unregistered runner.

Why GitLab CE instead of GitHub or Bitbucket

GitHub and Bitbucket are managed services: convenient to start with, but their pricing evolves with team size, CI minutes are capped on free plans, and your code lives on third-party infrastructure. GitLab CE reverses this equation: you deploy the forge on your VPS, you retain full control of the code and data, and CI/CD is included without minute quotas imposed by a third party — only your machine's capacity limits the number of parallel jobs. For an agency or a development team that bills clients or handles sensitive data, this control is often non-negotiable. GitLab CE is today one of the most widely deployed self-hosted git forges: its codebase is public, its core is free, and ten years of existence have earned it an active contributor community.

What GitLab CE includes out of the box

  • Complete git forge: private and public repositories, merge requests, code review, branch protection and CODEOWNERS.
  • Integrated CI/CD with no imposed minute limit: YAML pipelines (.gitlab-ci.yml), environments, deploy tokens and artifacts.
  • Private Docker registry hosted on your domain: docker pull git.yourdomain.com/group/image:tag without a Docker Hub account.
  • Wikis and pages per project and per group, with full Markdown rendering.
  • Issue tracking and milestones: kanban board, labels, iterations and burndown charts included.
  • Parallel runners: register as many runners as you want on your infrastructure or CI workers.
  • Webhooks to notify a third-party tool (Slack, PagerDuty, your deployment server) on every push or merge.
  • Granular RBAC with five access levels (Guest, Reporter, Developer, Maintainer, Owner) and optional LDAP/SAML support.

Prerequisites: what you need before you start

GitLab CE is memory-intensive — it is one of its most cited drawbacks compared to Forgejo or Gitea. Plan for 4 GB of RAM minimum for light usage (fewer than ten active users); 8 GB are recommended as soon as you add runners on the same machine or enable the Docker registry. Below 4 GB, Puma and Sidekiq compete for memory and GitLab responds with 502 under load. For storage, allow at least 20 GB for the installation and initial repositories — plan for 50 GB if you intend to store Docker images in the registry. You also need: a domain name pointing to the VPS IP (for example git.yourdomain.com) for Let's Encrypt to issue a TLS certificate; ports 80, 443 and 22 open in your firewall (port 22 is used by GitLab for SSH pushes — if your system SSH daemon also listens on 22, move it to another port); Docker Engine and the Compose v2 plugin installed (docker compose version should return v2.x).

From installation to your first project

01

Create the directory structure

Create a dedicated folder and the three persistent volumes GitLab uses: mkdir -p /opt/gitlab/{config,logs,data}. These directories will be mounted into the container; without them, configuration and repositories vanish on every docker compose down.

02

Write the docker-compose.yml file

Create /opt/gitlab/docker-compose.yml. The GITLAB_OMNIBUS_CONFIG key concentrates all instance-specific configuration — replace git.yourdomain.com with your actual domain. Define the gitlab service with image gitlab/gitlab-ce:17.2.1-ce.0, restart: unless-stopped, the hostname, environment variables (including GITLAB_OMNIBUS_CONFIG containing external_url, Let's Encrypt settings and SMTP parameters), ports 80, 443 and 22, volumes for /etc/gitlab, /var/log/gitlab and /var/opt/gitlab, shm_size: 256m and env_file: .env. Important note: the external_url value determines whether GitLab generates http:// or https:// URLs, and whether Let's Encrypt is attempted. It must exactly match a domain resolvable from the Internet — an incorrect value is the leading cause of access errors.

03

Set GITLAB_OMNIBUS_CONFIG

Inside GITLAB_OMNIBUS_CONFIG, define at minimum: external_url 'https://git.yourdomain.com'; letsencrypt['enable'] = true with letsencrypt['contact_emails'] = ['[email protected]']; SMTP parameters — gitlab_rails['smtp_enable'] = true, address, port 587, user name, gitlab_rails['smtp_password'] = ENV['GITLAB_SMTP_PASSWORD'], authentication login, smtp_enable_starttls_auto = true, gitlab_email_from; and if you enable the registry: registry_external_url 'https://registry.yourdomain.com'. All these lines must be inside the GITLAB_OMNIBUS_CONFIG block, not as separate environment variables — a key outside the block is silently ignored.

04

Create the .env file for secrets

Create /opt/gitlab/.env and restrict its permissions: touch /opt/gitlab/.env && chmod 600 /opt/gitlab/.env. Add your sensitive variables — at minimum GITLAB_SMTP_PASSWORD=your_smtp_password. Never put a password in plain text in the Compose file: it will end up versioned or shared. Add .env to your .gitignore if you version the configuration.

05

Start GitLab and wait for initialisation

Launch the stack: docker compose -f /opt/gitlab/docker-compose.yml up -d. The first start takes time: GitLab initialises the database, compiles assets and configures Nginx and Puma. Wait about 5 minutes before accessing the web interface. Follow progress with docker compose -f /opt/gitlab/docker-compose.yml logs -f gitlab and wait for the gitlab Reconfigured! message.

06

Retrieve the initial root password

On first initialisation, GitLab generates a temporary password for the root account. Retrieve it with: docker exec -it gitlab-gitlab-1 grep 'Password:' /etc/gitlab/initial_root_password. This file is automatically deleted 24 hours after the first start — note the password immediately.

07

Log in, change the password and disable open registration

Open https://git.yourdomain.com in your browser. Log in with root and the password retrieved above. Go to User Settings → Password and set a new strong password. Create your first non-root user: Admin Area → Users → New User. For internal use, disable public sign-up: Admin Area → Settings → General → Sign-up restrictions → uncheck 'Sign-up enabled'.

08

Register a GitLab Runner

CI/CD pipelines require at least one runner. Install gitlab-runner on the VPS or a dedicated machine (download the binary from the official GitLab Runner page). Retrieve the registration token from Admin Area → Runners (shared) or from your project's Settings → CI/CD → Runners (project runner). Register: gitlab-runner register --url https://git.yourdomain.com --registration-token YOUR_TOKEN --executor docker --docker-image alpine:latest. Once registered, the runner appears green in the interface and your .gitlab-ci.yml pipelines can execute.

09

Verify email delivery

Test SMTP from the GitLab Rails console: docker exec -it gitlab-gitlab-1 gitlab-rails console then type Notify.test_email('[email protected]', 'Test GitLab', 'Hello').deliver_now. If the email does not arrive, check that smtp_* keys are inside GITLAB_OMNIBUS_CONFIG and review the logs: docker exec -it gitlab-gitlab-1 tail -f /var/log/gitlab/gitlab-rails/production.log.

Automatic backups. GitLab ships a full backup command (repositories, database, uploads): docker exec -t gitlab-gitlab-1 gitlab-backup create. Schedule it in crontab -e with 0 3 * * * docker exec -t gitlab-gitlab-1 gitlab-backup create CRON=1. Archives are created in /var/opt/gitlab/backups (mounted at /opt/gitlab/data/backups on the host) and timestamped. Sync this folder to external storage (S3, rclone) — a local backup is not a backup.

Troubleshooting: the three most common failures

502 Bad Gateway at startup. GitLab takes about 5 minutes to become fully operational. If the 502 persists, check that Puma has started: docker exec gitlab-gitlab-1 gitlab-ctl status puma. Insufficient RAM is the most frequent cause — ensure you have at least 4 GB available. SMTP not working. Check first: is the gitlab_rails['smtp_*'] block inside GITLAB_OMNIBUS_CONFIG, not defined as a separate environment variable? Incorrect indentation in the YAML or a key outside the omnibus block is silent — GitLab starts normally but ignores the SMTP configuration. Test from the Rails console (step 9). Runner not connected. If the runner shows grey or offline, check it can reach your instance: gitlab-runner verify --url https://git.yourdomain.com. A self-signed TLS certificate or a domain not resolvable from the runner machine are the usual causes. Also check that the token used at registration is for the correct level (instance, group or project).

GitLab CE, Forgejo or Gitea: how to choose

CriterionGitLab CEForgejo / Gitea
LicenceMIT / EE CoreMIT
Idle RAM300–600 MB (Puma + Sidekiq)30–50 MB
Native CI/CDYes (`.gitlab-ci.yml`, runners)Forgejo: yes via Woodpecker CI; Gitea: no native CI
Integrated Docker registryYesForgejo: yes; Gitea: not native
Per-project wikisYesYes
LDAP / SAMLYes (CE)Forgejo: yes; Gitea: LDAP yes, SAML no
Learning curveHigh (feature-rich interface)Low to medium
Best forTeams of 5+ needing CI, registry and RBACLightweight teams, simple forge, low RAM footprint

When to choose GitLab CE, when to choose Forgejo

GitLab CE is the right choice if your team needs robust CI/CD directly integrated into the forge, a private Docker registry on your domain, deployment pipelines with environments and deploy tokens, or project management with issues, milestones and kanban without a third-party tool. Its memory footprint (4–8 GB) is the price of this functional richness. Forgejo or Gitea are the answer when RAM is the main constraint, when the forge is the only requirement (no integrated CI) and you run CI with an external tool (Woodpecker, Drone, GitHub Actions via a runner). On a 2 GB VPS, GitLab CE is not viable; Forgejo runs on 256 MB. On a VPS of 8 GB dedicated to a development team, GitLab CE provides a complete environment that GitHub puts on its SaaS platform — under your control and without a monthly per-user subscription.

A VPS ready for GitLab CE

GitLab CE needs a VPS with 4 to 8 GB of RAM, generous disk space and a dedicated IP. Our ServOrbit VPS are delivered with Debian 12 or Ubuntu 24.04 and immediate root access — enough to have your forge online in thirty minutes.

Need help?

Browse our help center and FAQ, or write to our team — support in French, English and Arabic.