GitLab ISC installation runbook
This document is meant to be dropped as CLAUDE.md on the target server, where Claude Code will execute the installation.
You are Claude, running on the server (Claude Code). Your mission: install the ISC GitLab infrastructure described below, phase by phase, stopping at every checkpoint for human validation.
General rules
- Work phase by phase, in order. Never skip a checkpoint.
- Before any destructive command (ZFS pool creation, disk wipe), list what will be destroyed and ask for explicit confirmation.
- Log everything in
/root/install-log.md: commands executed, important outputs, decisions taken. - If a value is marked
<PLACEHOLDER>, ask the human for it before continuing. - On error: diagnose, propose a fix, do not retry in a loop.
Values to ask for before starting
| Variable | Description | Value |
|---|---|---|
GITLAB_FQDN | e.g. git.isc.hevs.ch | <PLACEHOLDER> |
REGISTRY_FQDN | e.g. registry.isc.hevs.ch | <PLACEHOLDER> |
ADMIN_EMAIL | Let's Encrypt + admin contact | <PLACEHOLDER> |
OIDC_ISSUER | SWITCH edu-ID issuer | <PLACEHOLDER> |
OIDC_CLIENT_ID / OIDC_SECRET | Registered OIDC client | <PLACEHOLDER> |
SMTP_* | Institutional mail relay (host, port, credentials) | <PLACEHOLDER> |
PBS_HOST | IP/name of the Proxmox Backup Server | <PLACEHOLDER> |
| VM network | Bridge, possible VLAN, static IPs of both VMs, gateway, DNS | <PLACEHOLDER> |
Phase 0 — Prerequisites (MANUAL, out of your reach)
The human has already: installed Proxmox VE from the ISO on the dual-EPYC server, configured the management network. You start on the Proxmox host as root. Verify:
pveversion # Proxmox VE 9.x expected (9.2+, Debian 13 Trixie base)
ip a # management network OK
lsblk -o NAME,SIZE,MODEL,SERIAL # NVMe inventory
CHECKPOINT 0: present the disk inventory to the human and have them validate which disks go into the ZFS pool (careful not to include the Proxmox system disk).
Phase 1 — ZFS pool
Goal: a tank pool in mirrors (RAID10), lz4, for the VM disks.
# ⚠️ DESTRUCTIVE — mandatory human confirmation with the exact disk list
zpool create -o ashift=12 tank \
mirror /dev/disk/by-id/<nvme-A1> /dev/disk/by-id/<nvme-A2> \
mirror /dev/disk/by-id/<nvme-B1> /dev/disk/by-id/<nvme-B2>
# ... add the remaining pairs
zfs set compression=lz4 atime=off tank
- Always use
/dev/disk/by-id/, never/dev/nvmeXnY. - Add the pool as Proxmox storage:
pvesm add zfspool tank -pool tank -content images,rootdir. - Install automatic snapshots:
apt install zfs-auto-snapshot(24 hourly / 7 daily by default — check the systemd timers).
CHECKPOINT 1: zpool status healthy, storage visible in Proxmox.
Phase 2 — VM creation
Two Ubuntu Server 24.04 LTS VMs (download the ISO via pveam/wget into /var/lib/vz/template/iso/):
| VM | vmid | vCPU | RAM | Disk (tank) | Role |
|---|---|---|---|---|---|
gitlab | 101 | 8 | 32 GB | 500 GB | GitLab CE + registry |
runner-01 | 102 | 32 | 64 GB | 300 GB | gitlab-runner Docker |
Options: CPU type host, virtio-scsi, disk discard=on,ssd=1, virtio NIC on the indicated bridge, qemu-guest-agent enabled. Use cloud-init if available for the static IP and SSH key, otherwise human-assisted ISO installation.
CHECKPOINT 2: both VMs boot, reachable over SSH, qm agent <vmid> ping responds.
Phase 3 — GitLab CE (in the gitlab VM)
apt update && apt upgrade -y
apt install -y curl openssh-server ca-certificates tzdata perl
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash
EXTERNAL_URL="https://<GITLAB_FQDN>" apt install -y gitlab-ce
Then edit /etc/gitlab/gitlab.rb:
external_url "https://<GITLAB_FQDN>"
registry_external_url "https://<REGISTRY_FQDN>"
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['<ADMIN_EMAIL>']
# Institutional SMTP
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "<SMTP_HOST>"
# ... complete according to the provided relay
# SWITCH edu-ID OIDC
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_providers'] = [{
name: "openid_connect",
label: "SWITCH edu-ID",
args: {
name: "openid_connect",
scope: ["openid", "profile", "email"],
response_type: "code",
issuer: "<OIDC_ISSUER>",
discovery: true,
client_auth_method: "query",
uid_field: "sub",
client_options: {
identifier: "<OIDC_CLIENT_ID>",
secret: "<OIDC_SECRET>",
redirect_uri: "https://<GITLAB_FQDN>/users/auth/openid_connect/callback"
}
}
}]
# Default artifact retention: managed in the admin UI (see Phase 6)
gitlab-ctl reconfigure
gitlab-ctl status
Retrieve the initial root password: cat /etc/gitlab/initial_root_password (valid 24 h — hand it to the human immediately).
CHECKPOINT 3: login page reachable over HTTPS, root login OK, SWITCH edu-ID login tested by the human, test mail received (gitlab-rails console → Notify.test_email(...)).
Phase 4 — Runner (in the runner-01 VM)
# Docker
curl -fsSL https://get.docker.com | sh
# gitlab-runner
curl -fsSL https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
apt install -y gitlab-runner
The human creates an instance-wide runner in the GitLab admin (tags: linux,docker) and gives you the token. Then:
gitlab-runner register --non-interactive \
--url "https://<GITLAB_FQDN>" --token "<RUNNER_TOKEN>" \
--executor docker --docker-image "ubuntu:24.04"
In /etc/gitlab-runner/config.toml: concurrent = 8. Never mount /var/run/docker.sock in jobs (arbitrary student code). If docker-in-docker is required later, use the docker:dind service with privileged = true knowingly, or prefer kaniko/buildah.
CHECKPOINT 4: a test project with a minimal .gitlab-ci.yml goes green.
Phase 5 — Backups
5a. GitLab application backup (gitlab VM)
Root cron, /etc/cron.d/gitlab-backup:
0 2 * * * root /opt/gitlab/bin/gitlab-backup create CRON=1
15 2 * * * root tar czf /var/opt/gitlab/backups/etc-gitlab-$(date +\%F).tar.gz /etc/gitlab
In gitlab.rb: gitlab_rails['backup_keep_time'] = 604800 (7 days). Export the backups folder off the VM (rsync to the PBS host or a NAS — ask the human for the destination).
5b. Proxmox Backup Server (Proxmox host)
- PBS installed by the human on
<PBS_HOST>(or install it yourself if given access). - Add the PBS datastore in Proxmox (
Datacenter → Storage → PBS), fingerprint provided by the human. - Nightly backup job (03:00) of VMs 101 and 102, snapshot mode, retention: 7 daily / 4 weekly / 6 monthly, weekly verification enabled.
CHECKPOINT 5: a full PBS backup has run and is verified; a manual gitlab-backup create produced an archive; restore test: restore the application backup into a disposable VM (vmid 199) and check that a project is accessible. Destroy VM 199 afterwards.
Phase 6 — ISC configuration (GitLab admin)
Via the admin UI (or gitlab-rails console if scriptable):
- Groups:
isc/staff,isc/teaching,isc/students/2026. - Disable open sign-up (Admin → Settings → Sign-up restrictions) — only OIDC creates accounts; restrict to institutional mail domains if the option is available.
- Default CI artifact expiry: 2 weeks (Admin → Settings → CI/CD).
- Per-namespace storage quota: propose 20 GB students / 50 GB staff, to be validated.
- Default project visibility: private.
- Registry: check that
docker login <REGISTRY_FQDN>works.
CHECKPOINT 6 (final): full review with the human, handover of /root/install-log.md, reminder of the recurring tasks:
- Monthly GitLab upgrade (Proxmox snapshot before,
apt upgrade gitlab-ce, official upgrade path if skipping versions) - Semestrial restore test
- Archiving of the graduating cohort every summer (
isc/students/<year>→ projects archived, accounts blocked)