The Setup

I run GitLab Runner on a ChromeOS thin client (Debian container, 2.7GB RAM, no swap). It builds my blog (Babashka static site generator) and deploys to GitLab Pages.

No GitHub. No shared runners. No 400-minute compute quotas. No noise.

Why Not GitHub?

GitHub Actions is fine, but: - Shared runners have quotas (400 minutes/month) - GitHub is social (recruiters, noise, distractions) - I wanted complete control

GitLab self-hosted runner = unlimited CI/CD. The runner is exempt from GitLab.com’s quotas because it’s my hardware.

The Hardware

ChromeOS thin client specs: - 2.7GB RAM (584MB available after OS) - 41GB storage - ARM64 (aarch64) - No swap

GitLab Runner requirements: - 512MB RAM minimum → I have 2.7GB ✅ - ARM64 support → Yes ✅ - 100MB disk → Yes ✅

Installation

# Download GitLab Runner for ARM64
curl -L --output /tmp/gitlab-runner \
  "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64"

sudo mv /tmp/gitlab-runner /usr/local/bin/gitlab-runner
sudo chmod +x /usr/local/bin/gitlab-runner

gitlab-runner --version
# Version: 19.2.0 (ARM64)

Binary size: 110MB (single file, no dependencies)

Configuration

Register the runner (needs token from GitLab):

gitlab-runner register \
  --url "https://gitlab.com" \
  --token "<YOUR_TOKEN>" \
  --executor "shell" \
  --description "homepage-self-hosted" \
  --tag-list "homepage-self-hosted"

Key settings: - executor: shell - runs directly on the host (no Docker overhead) - tag-list: homepage-self-hosted - GitLab routes jobs to this runner only

The Blog Build Pipeline

My blog uses Babashka (Clojure scripting) to: 1. Convert Markdown → HTML (pandoc) 2. Render D2 diagrams → SVG 3. Generate static site → public/

.gitlab-ci.yml:

pages:
  stage: deploy
  tags:
    - homepage-self-hosted
  script:
    - bb build
    - bb validate-links
  artifacts:
    paths:
      - public/
  timeout: 15m

Build time: ~30 seconds (Babashka is fast)

Keeping the Runner Alive

The runner dies when the shell session ends. Keep it alive with setsid:

setsid ~/.local/bin/gitlab-runner run \
  --config ~/.config/gitlab-runner/config.toml \
  > ~/.local/var/gitlab-runner.log 2>&1 < /dev/null &

Check if running:

ps aux | grep gitlab-runner

Why This Matters

Sovereignty: - My CI/CD runs on my hardware - No dependency on GitHub’s infrastructure - No shared runner queues - No compute quotas

Minimal: - Single binary (110MB) - No Docker (shell executor) - No JVM (Babashka is native) - No bloat

Collapse Entropy: - GitLab < GitHub (less noise, more focus) - Self-hosted < Cloud (control) - Thin client < Workstation (constraints force simplicity)

The Result

Every git push to main: 1. Triggers the runner (on this thin client) 2. Builds the blog (Babashka + pandoc + d2) 3. Deploys to GitLab Pages 4. Live at nurazhar.com in ~45 seconds

Zero maintenance. The runner just works.

Lessons

  1. Thin clients are enough - 2.7GB RAM runs CI/CD fine
  2. Self-hosted = freedom - No quotas, no queues, no noise
  3. Babashka is perfect - Native Clojure, fast builds, minimal deps
  4. GitLab > GitHub for solo devs who want control

Try It

Want your own self-hosted runner?

  1. Install GitLab Runner (single binary)
  2. Register with your GitLab token
  3. Tag your jobs
  4. Push code

Hardware needed: Any Linux box with 512MB RAM.

Time to set up: 15 minutes.

ROI: Unlimited CI/CD forever.


Written on a ChromeOS thin client with 584MB RAM available. If it can run GitLab Runner, so can you.