Self-Hosting Git & DevSecOps Platform: GitLab CE on Debian Linux (Omnibus, Container Registry & CI Runners)
Executive Summary: GitLab Community Edition (CE) is a complete, single-application Enterprise DevSecOps lifecycle platform bundling Git repository management (
Gitaly), Merge Request code review, integrated CI/CD pipelines (.gitlab-ci.yml), a private OCI Container & Package Registry (PyPI, Maven, npm), and security scanning. Because default GitLab Omnibus settings are sized for 1,000+ user clusters (consuming6 GB–8 GBof RAM at idle), this guide shows how to deploy GitLab CE on Debian Linux with memory-tuned Puma/Sidekiq/Prometheus parameters (~2.8 GBRAM), external reverse proxy TLS, and isolated GitLab CI Runners.
-
Complete DevSecOps Lifecycle
Unify Git hosting, Merge Requests, Epics/Issues, Wiki, CI/CD pipelines, and Terraform state management in one self-hosted platform.
-
RAM-Optimized
GITLAB_OMNIBUS_CONFIG
Slash idle RAM consumption by 55% for small teams (
1–25users) by tuningpuma['worker_processes']andsidekiq['concurrency']. -
Private OCI Container Registry
Host your own private Docker/Podman container images (
registry.nejat-hakan.de) and Python/Rust packages with zero Docker Hub rate limits. -
Isolated
gitlab-runnerExecutors
Execute
.gitlab-ci.ymlbuild,pytest, and deployment stages on a dedicated runner container without granting CI jobs root access to the GitLab host.
1. GitLab CE Internal Architecture & Memory Footprint
Unlike lightweight single-binary Git servers (Forgejo), GitLab CE is a distributed microservice suite packaged inside the Omnibus container:
%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
PROXY["🔒 Host Reverse Proxy<br/>gitlab.nejat-hakan.de (443)<br/>& SSH Port 2222"]
subgraph Omnibus["🦊 GitLab CE Omnibus Stack (127.0.0.1:8929)"]
WORK["GitLab Workhorse<br/>(Go Reverse Router)"]
PUMA["Puma Rails App<br/>(Web UI & REST API)"]
SIDE["Sidekiq Workers<br/>(Async CI & Webhooks)"]
GIT["Gitaly RPC Daemon<br/>(Git Repository Storage)"]
DATA["PostgreSQL + Redis<br/>(Internal State Store)"]
WORK --> PUMA
WORK --> GIT
PUMA <--> DATA
SIDE <--> DATA
end
RUN["🤖 GitLab CI Runner<br/>(Isolated Executor Node)"]
PROXY <--> WORK
RUN <-->|"Polls CI Jobs (HTTPS)"| WORK
2. Production compose.yaml (Memory-Tuned for 4–8 GB RAM Servers)
Notice how we map Git SSH to port 2222 (so your Debian host's own administrative SSH daemon remains untouched on port 22) and tune GITLAB_OMNIBUS_CONFIG for lean RAM consumption:
# File: /srv/containers/gitlab/compose.yaml
services:
gitlab:
image: docker.io/gitlab/gitlab-ce:latest
container_name: gitlab-ce
hostname: gitlab.nejat-hakan.de
restart: unless-stopped
shm_size: "256m"
ports:
- "127.0.0.1:8929:8929/tcp" # HTTP forwarded from host Reverse Proxy
- "0.0.0.0:2222:22/tcp" # Git over SSH (ssh://git@gitlab.nejat-hakan.de:2222/...)
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.nejat-hakan.de'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
gitlab_rails['time_zone'] = 'Europe/Berlin'
# Terminate TLS at upstream host reverse proxy (Caddy / Nginx / Traefik)
nginx['listen_port'] = 8929
nginx['listen_https'] = false
nginx['proxy_set_headers'] = {
"X-Forwarded-Proto" => "https",
"X-Forwarded-Ssl" => "on"
}
# --- Memory Optimization for Small Teams (Saves ~3.5 GB RAM!) ---
puma['worker_processes'] = 2
puma['per_worker_max_memory_mb'] = 1024
sidekiq['concurrency'] = 8
postgresql['shared_buffers'] = "256MB"
prometheus_monitoring['enable'] = false
volumes:
- /srv/containers/gitlab/config:/etc/gitlab:rw
- /srv/containers/gitlab/logs:/var/log/gitlab:rw
- /srv/containers/gitlab/data:/var/opt/gitlab:rw
Retrieve the auto-generated initial root password within 24 hours of first boot:
# File: /opt/gitlab-ops/get_initial_root_password.sh
docker exec -it gitlab-ce grep 'Password:' /etc/gitlab/initial_root_password
3. Registering an Isolated GitLab CI Runner (gitlab-runner)
Never run untrusted CI build jobs directly on your primary database or production host without isolation. Deploy gitlab/gitlab-runner and register it with a project or group Runner Authentication Token (glrt-...):
# File: /opt/gitlab-ops/register_ci_runner.sh
#!/usr/bin/env bash
set -euo pipefail
docker run -d \
--name gitlab-runner \
--restart unless-stopped \
-v /srv/containers/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/gitlab/gitlab-runner:alpine
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url "https://gitlab.nejat-hakan.de" \
--token "${GITLAB_RUNNER_AUTH_TOKEN}" \
--executor "docker" \
--docker-image "python:3.12-slim-bookworm" \
--description "debian-docker-runner-01"
4. Automated Backup & Secret Key Protection
Back Up /etc/gitlab/gitlab-secrets.json Separately!
Running gitlab-backup create backs up all Git repositories, PostgreSQL tables, and CI artifacts—but for security reasons it excludes /etc/gitlab/gitlab-secrets.json and /etc/gitlab/gitlab.rb. Without gitlab-secrets.json, encrypted 2FA TOTP secrets and CI/CD runner variables in the database cannot be decrypted during a restore!
# File: /opt/gitlab-ops/backup_gitlab_full.sh
#!/usr/bin/env bash
set -euo pipefail
# 1. Create consistent application & repository backup archive in /var/opt/gitlab/backups
docker exec -t gitlab-ce gitlab-backup create CRON=1
# 2. Back up the critical encryption keys & configuration
docker exec -t gitlab-ce gitlab-ctl backup-etc
Where to Go Next
Compare heavyweight GitLab CE with featherweight Forgejo (< 100 MB RAM) and off-site BorgBackup:
-
Forgejo Lightweight Git Server
Want Git + Pull Requests + GitHub-Actions-compatible CI/CD in
< 100 MBof RAM instead of3 GB? Choose Forgejo. -
BorgBackup + Borgmatic
Ship your
/srv/containers/gitlab/data/backupsand/etc/gitlabsecrets to an encrypted off-site Borg repository. -
Automated Pytest Quality Gates
Configure your GitLab CI Runner to enforce
pytestandruffchecks on every Merge Request.