Tagihan GitLab.com-mu naik 40% bulan lalu? Atau mungkin kamu baru sadar bahwa setiap pipeline menit dihitung — bahkan untuk job yang gagal karena typo di .gitlab-ci.yml.
Saya pernah di posisi itu. Proyek freelance kecil, pipeline jalan 50-60 kali sehari, dan di akhir bulan muncul angka yang bikin dahi berkerut. Solusinya bukan pindah ke GitHub Actions (yang punya masalah sendiri), tapi hosting GitLab sendiri di VPS. Total biaya: sekitar $6/bulan untuk VPS 4GB RAM. Itu sudah termasuk runner, registry, dan semua fitur GitLab CE.
Artikel ini bukan overview teoritis. Ini setup yang saya pakai sekarang, dengan konfigurasi nyata, termasuk bagian yang sering bikin orang tersandung.
Kenapa Self-Hosted GitLab Masuk Akal untuk Solo Engineer
GitLab CE (Community Edition) gratis dan fiturnya lebih dari cukup. Kamu dapat CI/CD pipeline, container registry, issue tracker, merge request, dan bahkan Pages — tanpa bayar lisensi. Yang kamu butuhkan cuma server.
Perbandingan kasar:
| GitLab.com Free | GitLab.com Premium | Self-Hosted CE | |
|---|---|---|---|
| CI/CD menit/bulan | 400 menit | 10.000 menit | Unlimited |
| Storage | 5 GB | 50 GB | Sesuai disk |
| Harga | Gratis | $29/user/bulan | ~$6/bulan (VPS) |
| Container Registry | ✓ | ✓ | ✓ |
| Self-managed runner | ✓ | ✓ | ✓ |
400 menit gratis habis dalam hitungan hari kalau kamu aktif develop. Premium $29/user/bulan untuk solo engineer itu mahal. Self-hosted dengan VPS murah jauh lebih masuk akal.
Satu caveat: kamu perlu mau urus update, backup, dan maintenance. Kalau kamu tipe yang nggak mau pegang server sama sekali, self-hosted bukan buat kamu. Tapi kalau kamu sudah biasa self-host aplikasi lain, ini nggak jauh berbeda.
Persiapan Server dan Instalasi GitLab CE
Spesifikasi minimum yang saya rekomendasikan: 4GB RAM, 2 vCPU, 40GB SSD. Dengan 2GB RAM GitLab bisa jalan tapi sering swap, dan itu menyiksa. Saya pakai Hetzner CX22 (4GB RAM, 2 vCPU) seharga €3.79/bulan — salah satu yang paling value untuk workload ini.
Instalasinya lewat Docker Compose. Jauh lebih mudah di-update dan di-backup dibanding instalasi langsung di sistem.
Buat file docker-compose.yml:
version: '3.8'
services:
gitlab:
image: gitlab/gitlab-ce:16.11.0-ce.0
container_name: gitlab
restart: always
hostname: 'gitlab.domainmu.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.domainmu.com'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
gitlab_rails['time_zone'] = 'Asia/Jakarta'
# Matikan fitur yang nggak kamu pakai
gitlab_rails['gitlab_email_enabled'] = true
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = 'smtp.mailgun.org'
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = 'postmaster@mg.domainmu.com'
gitlab_rails['smtp_password'] = 'password-smtp-kamu'
gitlab_rails['smtp_domain'] = 'mg.domainmu.com'
gitlab_rails['smtp_authentication'] = 'plain'
gitlab_rails['smtp_enable_starttls_auto'] = true
# Batasi registrasi
gitlab_rails['gitlab_signup_enabled'] = false
# Nginx dan SSL via Let's Encrypt
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['email@domainmu.com']
nginx['listen_port'] = 80
nginx['listen_https_port'] = 443
ports:
- '80:80'
- '443:443'
- '2222:22'
volumes:
- gitlab_config:/etc/gitlab
- gitlab_logs:/var/log/gitlab
- gitlab_data:/var/opt/gitlab
shm_size: '256m'
volumes:
gitlab_config:
gitlab_logs:
gitlab_data:
Jalankan dengan:
docker compose up -d
Proses inisialisasi pertama butuh 3-5 menit. Cek progressnya:
docker logs -f gitlab | grep 'gitlab Reconfigured'
Setelah selesai, ambil password root:
docker exec gitlab cat /etc/gitlab/initial_root_password
Password ini hilang setelah 24 jam, jadi langsung ganti di Settings setelah login pertama.
Setup GitLab Runner untuk CI CD Pipeline
GitLab Runner adalah komponen yang benar-benar menjalankan job di pipeline-mu. Tanpa runner, pipeline cuma antri selamanya. Kamu bisa install runner di server yang sama atau server terpisah — saya pakai server yang sama untuk hemat biaya, tapi pisahkan container-nya.
Tambahkan service runner ke docker-compose.yml:
gitlab-runner:
image: gitlab/gitlab-runner:v16.11.0
container_name: gitlab-runner
restart: always
volumes:
- gitlab_runner_config:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
- gitlab
volumes:
gitlab_runner_config:
Setelah container runner jalan, daftarkan runner ke GitLab:
docker exec -it gitlab-runner gitlab-runner register
Kamu akan ditanya beberapa hal:
- GitLab instance URL:
https://gitlab.domainmu.com - Registration token: ambil dari GitLab → Admin → CI/CD → Runners
- Description:
docker-runner-01 - Tags:
docker,linux - Executor:
docker - Default Docker image:
alpine:latest
Setelah register, edit /etc/gitlab-runner/config.toml (di dalam volume) untuk tambahkan konfigurasi penting:
[[runners]]
name = "docker-runner-01"
url = "https://gitlab.domainmu.com"
executor = "docker"
[runners.docker]
tls_verify = false
image = "alpine:latest"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
pull_policy = "if-not-present"
shm_size = 0
[runners.cache]
MaxUploadedArchiveSize = 0
Satu hal penting: pull_policy = "if-not-present" menghemat bandwidth dan waktu dengan tidak pull image Docker setiap kali pipeline jalan kalau image sudah ada di cache lokal.
Menulis .gitlab-ci.yml yang Efisien
Ini bagian yang paling sering diabaikan. Orang setup GitLab, bikin pipeline, tapi konfigurasinya lambat dan boros resource.
Contoh pipeline untuk aplikasi Node.js:
image: node:20-alpine
variables:
NODE_ENV: test
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
stages:
- install
- test
- build
- deploy
install:deps:
stage: install
script:
- npm ci --prefer-offline
artifacts:
paths:
- node_modules/
expire_in: 1 hour
test:unit:
stage: test
script:
- npm run test:unit
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
test:lint:
stage: test
script:
- npm run lint
build:app:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 day
only:
- main
- develop
deploy:production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
script:
- ssh $DEPLOY_USER@$DEPLOY_HOST "cd /app && git pull && npm ci --production && pm2 restart app"
environment:
name: production
url: https://appmu.com
only:
- main
when: manual
Beberapa keputusan di konfigurasi ini yang sengaja:
- Cache berdasarkan
package-lock.json— cache invalide otomatis kalau dependency berubah. npm ci --prefer-offline— pakai cache npm lokal, nggak fetch dari internet kalau nggak perlu.deploy:productionpakaiwhen: manual— deploy ke production nggak pernah otomatis. Saya harus klik tombol di GitLab UI. Ini kebiasaan yang menyelamatkan nyawa (atau setidaknya uptime).- SSH key lewat CI/CD Variables — simpan
SSH_PRIVATE_KEY,DEPLOY_HOST,DEPLOY_USERdi GitLab → Settings → CI/CD → Variables, bukan di-hardcode di file.
Backup Otomatis yang Benar-benar Jalan
Ini bagian yang paling sering dilewati sampai disaster terjadi. GitLab CE punya built-in backup tool.
Buat script backup-gitlab.sh:
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backup/gitlab"
S3_BUCKET="s3://backup-bucket-kamu/gitlab"
DATE=$(date +%Y%m%d_%H%M%S)
# Jalankan backup GitLab
docker exec gitlab gitlab-backup create SKIP=artifacts,registry
# Backup config files (berisi secrets, JANGAN skip ini)
docker exec gitlab tar -czf /tmp/gitlab-config-${DATE}.tar.gz /etc/gitlab
docker cp gitlab:/tmp/gitlab-config-${DATE}.tar.gz ${BACKUP_DIR}/
# Upload ke S3 (pakai rclone atau aws cli)
rclone copy ${BACKUP_DIR}/ ${S3_BUCKET}/ --max-age 7d
# Hapus backup lokal lebih dari 3 hari
find ${BACKUP_DIR} -name "*.tar" -mtime +3 -delete
find ${BACKUP_DIR} -name "*.tar.gz" -mtime +3 -delete
echo "Backup selesai: ${DATE}"
Jadwalkan dengan cron:
0 2 * * * /opt/scripts/backup-gitlab.sh >> /var/log/gitlab-backup.log 2>&1
Satu hal yang sering dilupakan: backup GitLab dan backup config harus versi yang sama. Kalau GitLab-mu versi 16.11 dan kamu restore ke versi 17.x, prosesnya lebih rumit. Catat versi yang kamu pakai dan update secara bertahap.
Kalau kamu sudah setup monitoring untuk self-hosted services, integrasikan alert kalau script backup gagal. Saya tulis soal monitoring stack sederhana di panduan monitoring self-hosted — setup Uptime Kuma cukup untuk mulai.
Update GitLab Tanpa Downtime Berlebihan
GitLab update cukup sering — dan kamu wajib update secara bertahap, nggak boleh loncat versi mayor langsung. Misalnya dari 15.x ke 17.x harus lewat 16.x dulu.
Cek upgrade path di docs.gitlab.com/ee/update/upgrade_paths.html sebelum update.
Proses update dengan Docker Compose:
# Stop dulu
docker compose stop gitlab
# Backup sebelum update (wajib)
docker exec gitlab gitlab-backup create
# Edit docker-compose.yml, ganti versi image
# image: gitlab/gitlab-ce:16.11.0-ce.0 -> gitlab/gitlab-ce:17.0.0-ce.0
# Pull image baru dan restart
docker compose pull gitlab
docker compose up -d gitlab
# Monitor log
docker logs -f gitlab
Proses reconfigure setelah update biasanya 2-4 menit. GitLab akan unavailable selama itu. Untuk solo engineer, downtime 5 menit sekali beberapa bulan bukan masalah besar.
Kalau kamu manage beberapa project dan ingin nol-downtime lebih serius, lihat setup dengan load balancer — tapi itu sudah overkill untuk kebanyakan kasus di sini.
Kesimpulan: Apa yang Harus Kamu Lakukan Besok
Setup ci cd pipeline self-hosted gitlab bukan proyek weekend yang rumit. Dengan Docker Compose, kamu bisa punya instance GitLab CE yang fully functional dalam 30-60 menit — termasuk runner dan pipeline pertama yang jalan.
Investasi waktunya di awal, tapi hasilnya: pipeline unlimited, container registry sendiri, dan tagihan bulanan yang predictable di angka $6-10 — bukan angka yang berubah tergantung seberapa sering kamu push commit.
Langkah konkret untuk besok: spin up VPS 4GB RAM, clone konfigurasi Docker Compose dari artikel ini, dan jalankan. Kalau tersandung di bagian SSL atau runner registration, cek log dulu sebelum panik — 80% masalah ada di sana.
Kalau kamu sudah punya GitLab self-hosted dan mau optimasi lebih lanjut, baca juga cara mengatur secrets dan environment variables untuk multiple project tanpa kebocoran credential.
Satu hal yang nggak perlu kamu tunda: matikan auto-signup di GitLab instance-mu (gitlab_signup_enabled = false) sebelum instance-mu terindeks Google. Itu bukan saran — itu keharusan.