Skip to content

Commit d570aff

Browse files
Mirko Haaserclaude
andcommitted
Initial commit: GitLab Runner for Unraid
Auto-registering GitLab Runner Docker image with Unraid CA template, multi-platform CI/CD pipeline (GHCR + Docker Hub), and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0 parents  commit d570aff

7 files changed

Lines changed: 535 additions & 0 deletions

File tree

.dockerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.git
2+
.github
3+
.gitignore
4+
.dockerignore
5+
.claude
6+
.DS_Store
7+
README.md
8+
LICENSE
9+
unraid-template/
10+
test-config/
11+
*.md
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Build and Publish Docker Image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ['v*']
7+
pull_request:
8+
branches: [main]
9+
10+
env:
11+
GHCR_IMAGE: ghcr.io/mcgo/unraid-gitlab-runner
12+
DOCKERHUB_IMAGE: mcgo/unraid-gitlab-runner
13+
14+
jobs:
15+
build-and-push:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Set up QEMU
26+
uses: docker/setup-qemu-action@v3
27+
28+
- name: Set up Docker Buildx
29+
uses: docker/setup-buildx-action@v3
30+
31+
- name: Log in to GitHub Container Registry
32+
if: github.event_name != 'pull_request'
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ghcr.io
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Log in to Docker Hub
40+
if: github.event_name != 'pull_request'
41+
uses: docker/login-action@v3
42+
with:
43+
username: ${{ secrets.DOCKERHUB_USERNAME }}
44+
password: ${{ secrets.DOCKERHUB_TOKEN }}
45+
46+
- name: Extract metadata
47+
id: meta
48+
uses: docker/metadata-action@v5
49+
with:
50+
images: |
51+
${{ env.GHCR_IMAGE }}
52+
${{ env.DOCKERHUB_IMAGE }}
53+
tags: |
54+
type=ref,event=branch
55+
type=semver,pattern={{version}}
56+
type=semver,pattern={{major}}.{{minor}}
57+
type=semver,pattern={{major}}
58+
type=sha
59+
60+
- name: Build and push
61+
uses: docker/build-push-action@v6
62+
with:
63+
context: .
64+
platforms: linux/amd64,linux/arm64
65+
push: ${{ github.event_name != 'pull_request' }}
66+
tags: ${{ steps.meta.outputs.tags }}
67+
labels: ${{ steps.meta.outputs.labels }}
68+
cache-from: type=gha
69+
cache-to: type=gha,mode=max

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# OS
2+
.DS_Store
3+
Thumbs.db
4+
5+
# IDE
6+
.idea/
7+
.vscode/
8+
*.swp
9+
*.swo
10+
11+
# Docker
12+
test-config/
13+
14+
# Environment
15+
.env
16+
*.env.local

Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM gitlab/gitlab-runner:alpine
2+
3+
COPY entrypoint.sh /entrypoint.sh
4+
RUN chmod +x /entrypoint.sh
5+
6+
ENTRYPOINT ["/usr/bin/dumb-init", "/entrypoint.sh"]
7+
CMD ["run", "--user=gitlab-runner", "--working-directory=/home/gitlab-runner"]

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# GitLab Runner for Unraid
2+
3+
A GitLab Runner Docker image designed for Unraid, with automatic registration on first start.
4+
5+
Based on `gitlab/gitlab-runner:alpine` — adds auto-registration via environment variables and ships with an Unraid Community Applications template.
6+
7+
## Quick Start
8+
9+
```bash
10+
docker run -d \
11+
--name gitlab-runner \
12+
--restart unless-stopped \
13+
-e CI_SERVER_URL=https://gitlab.com \
14+
-e RUNNER_TOKEN=glrt-xxxxxxxxxxxx \
15+
-e RUNNER_EXECUTOR=docker \
16+
-v /var/run/docker.sock:/var/run/docker.sock \
17+
-v /mnt/user/appdata/gitlab-runner:/etc/gitlab-runner \
18+
mcgo/unraid-gitlab-runner
19+
```
20+
21+
## Installation on Unraid
22+
23+
1. Go to the **Apps** tab in Unraid
24+
2. Search for **GitLab Runner**
25+
3. Click **Install** and fill in the template variables
26+
4. The runner will automatically register itself at your GitLab instance
27+
28+
### Manual Template Install
29+
30+
If the template is not yet available in Community Applications:
31+
32+
1. In Unraid, go to **Docker > Add Container > Template Repositories**
33+
2. Add: `https://github.com/mcgo/unraid-gitlab-runner`
34+
3. The template will appear under **Docker > Add Container**
35+
36+
## Creating a Runner Token
37+
38+
### Modern Tokens (GitLab 16.0+, recommended)
39+
40+
1. Go to your GitLab project/group/instance **Settings > CI/CD > Runners**
41+
2. Click **New project runner** (or group/instance runner)
42+
3. Configure the runner settings (tags, description, etc.) in the UI
43+
4. Copy the token (starts with `glrt-`)
44+
45+
With modern tokens, tags and other settings are configured in GitLab's UI — the `RUNNER_TAG_LIST`, `RUNNER_UNTAGGED`, and `RUNNER_LOCKED` environment variables have no effect.
46+
47+
### Legacy Registration Tokens (deprecated)
48+
49+
If you're using a legacy registration token, you can still pass `RUNNER_TAG_LIST`, `RUNNER_UNTAGGED`, and `RUNNER_LOCKED` as environment variables.
50+
51+
## Environment Variables
52+
53+
### Required
54+
55+
| Variable | Description | Default |
56+
|---|---|---|
57+
| `CI_SERVER_URL` | URL of your GitLab instance ||
58+
| `RUNNER_TOKEN` | Runner authentication token ||
59+
60+
### Basic Configuration
61+
62+
| Variable | Description | Default |
63+
|---|---|---|
64+
| `RUNNER_NAME` | Display name in GitLab | `unraid-runner` |
65+
| `RUNNER_EXECUTOR` | `docker` or `shell` | `docker` |
66+
| `DOCKER_IMAGE` | Default image for CI jobs | `alpine:latest` |
67+
| `RUNNER_CONCURRENT` | Max parallel jobs | `1` |
68+
69+
### Advanced
70+
71+
| Variable | Description | Default |
72+
|---|---|---|
73+
| `RUNNER_TAG_LIST` | Comma-separated tags (legacy tokens only) ||
74+
| `RUNNER_UNTAGGED` | Run untagged jobs (legacy tokens only) ||
75+
| `RUNNER_LOCKED` | Lock to project (legacy tokens only) ||
76+
| `DOCKER_PRIVILEGED` | Enable privileged mode for Docker executor | `false` |
77+
| `DOCKER_VOLUMES` | Additional volumes, comma-separated ||
78+
| `UNREGISTER_ON_STOP` | Unregister runner on container stop | `false` |
79+
| `CA_CERTIFICATES_PATH` | Path to custom CA cert inside container ||
80+
81+
## Volumes
82+
83+
| Container Path | Recommended Host Path | Description |
84+
|---|---|---|
85+
| `/etc/gitlab-runner` | `/mnt/user/appdata/gitlab-runner` | Runner configuration (persists registration) |
86+
| `/var/run/docker.sock` | `/var/run/docker.sock` | Docker socket (required for Docker executor) |
87+
88+
## Docker Images
89+
90+
This image is available on:
91+
92+
- **GHCR**: `ghcr.io/mcgo/unraid-gitlab-runner`
93+
- **Docker Hub**: `mcgo/unraid-gitlab-runner`
94+
95+
## License
96+
97+
MIT

entrypoint.sh

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ──────────────────────────────────────────────
5+
# GitLab Runner Entrypoint
6+
# Auto-registers the runner on first start and
7+
# handles graceful shutdown with optional unregister.
8+
# ──────────────────────────────────────────────
9+
10+
CONFIG_DIR="/etc/gitlab-runner"
11+
CONFIG_FILE="${CONFIG_DIR}/config.toml"
12+
13+
# ── CA Certificate handling ───────────────────
14+
setup_ca_certificate() {
15+
local ca_file="${CA_CERTIFICATES_PATH:-}"
16+
if [[ -z "$ca_file" ]]; then
17+
return
18+
fi
19+
20+
if [[ ! -f "$ca_file" ]]; then
21+
echo "[entrypoint] WARNING: CA_CERTIFICATES_PATH set but file not found: ${ca_file}"
22+
return
23+
fi
24+
25+
echo "[entrypoint] Installing custom CA certificate from ${ca_file} ..."
26+
cp "$ca_file" /usr/local/share/ca-certificates/custom-ca.crt
27+
update-ca-certificates --fresh >/dev/null 2>&1 || true
28+
}
29+
30+
# ── Registration check ────────────────────────
31+
is_registered() {
32+
[[ -f "$CONFIG_FILE" ]] && grep -q '\[\[runners\]\]' "$CONFIG_FILE" 2>/dev/null
33+
}
34+
35+
# ── Register runner ───────────────────────────
36+
register_runner() {
37+
if [[ -z "${CI_SERVER_URL:-}" ]]; then
38+
echo "[entrypoint] ERROR: CI_SERVER_URL is required but not set."
39+
exit 1
40+
fi
41+
if [[ -z "${RUNNER_TOKEN:-}" ]]; then
42+
echo "[entrypoint] ERROR: RUNNER_TOKEN is required but not set."
43+
exit 1
44+
fi
45+
46+
local executor="${RUNNER_EXECUTOR:-docker}"
47+
48+
echo "[entrypoint] Registering runner at ${CI_SERVER_URL} ..."
49+
50+
local args=(
51+
--non-interactive
52+
--url "$CI_SERVER_URL"
53+
--token "$RUNNER_TOKEN"
54+
--name "${RUNNER_NAME:-unraid-runner}"
55+
--executor "$executor"
56+
)
57+
58+
# Docker-executor specific options
59+
if [[ "$executor" == "docker" ]]; then
60+
args+=(--docker-image "${DOCKER_IMAGE:-alpine:latest}")
61+
62+
if [[ "${DOCKER_PRIVILEGED:-false}" == "true" ]]; then
63+
args+=(--docker-privileged)
64+
fi
65+
66+
if [[ -n "${DOCKER_VOLUMES:-}" ]]; then
67+
IFS=',' read -ra vols <<< "$DOCKER_VOLUMES"
68+
for vol in "${vols[@]}"; do
69+
vol="$(echo "$vol" | xargs)" # trim whitespace
70+
[[ -n "$vol" ]] && args+=(--docker-volumes "$vol")
71+
done
72+
fi
73+
fi
74+
75+
# Legacy token options (ignored by modern runner tokens glrt-*)
76+
if [[ -n "${RUNNER_TAG_LIST:-}" ]]; then
77+
args+=(--tag-list "$RUNNER_TAG_LIST")
78+
fi
79+
if [[ -n "${RUNNER_UNTAGGED:-}" ]]; then
80+
args+=(--run-untagged="$RUNNER_UNTAGGED")
81+
fi
82+
if [[ -n "${RUNNER_LOCKED:-}" ]]; then
83+
args+=(--locked="$RUNNER_LOCKED")
84+
fi
85+
86+
gitlab-runner register "${args[@]}"
87+
echo "[entrypoint] Runner registered successfully."
88+
}
89+
90+
# ── Patch global config ───────────────────────
91+
patch_global_config() {
92+
local concurrent="${RUNNER_CONCURRENT:-1}"
93+
94+
if [[ ! -f "$CONFIG_FILE" ]]; then
95+
return
96+
fi
97+
98+
if grep -q '^concurrent' "$CONFIG_FILE"; then
99+
sed -i "s/^concurrent *=.*/concurrent = ${concurrent}/" "$CONFIG_FILE"
100+
else
101+
sed -i "1i concurrent = ${concurrent}" "$CONFIG_FILE"
102+
fi
103+
104+
echo "[entrypoint] Set concurrent = ${concurrent}"
105+
}
106+
107+
# ── Graceful shutdown ─────────────────────────
108+
shutdown() {
109+
echo "[entrypoint] Received shutdown signal, stopping runner ..."
110+
gitlab-runner stop 2>/dev/null || true
111+
112+
if [[ "${UNREGISTER_ON_STOP:-false}" == "true" ]]; then
113+
echo "[entrypoint] Unregistering runner ..."
114+
gitlab-runner unregister --all-runners 2>/dev/null || true
115+
fi
116+
117+
exit 0
118+
}
119+
120+
trap shutdown SIGQUIT SIGTERM SIGINT
121+
122+
# ── Main ──────────────────────────────────────
123+
setup_ca_certificate
124+
125+
if ! is_registered; then
126+
register_runner
127+
patch_global_config
128+
else
129+
echo "[entrypoint] Runner already registered, skipping registration."
130+
# Still patch concurrent in case the env var changed
131+
patch_global_config
132+
fi
133+
134+
echo "[entrypoint] Starting gitlab-runner ..."
135+
exec gitlab-runner "$@"

0 commit comments

Comments
 (0)