Files
homelab-design/decisions/0031-gitea-cicd-strategy.md

13 KiB

Gitea CI/CD Pipeline Strategy

  • Status: accepted
  • Date: 2026-02-04
  • Deciders: Billy
  • Technical Story: Establish CI/CD patterns for building and publishing container images via Gitea Actions

Context and Problem Statement

The homelab uses Gitea as the Git hosting platform. Applications need automated CI/CD pipelines to build container images, run tests, and publish artifacts. Gitea Actions provides GitHub Actions-compatible workflow execution.

How do we configure CI/CD pipelines that work reliably with the homelab's self-hosted infrastructure including private container registry, rootless Docker-in-Docker runners, and internal services?

Decision Drivers

  • Self-hosted - no external CI/CD dependencies
  • Container registry integration - push to Gitea's built-in registry
  • Rootless security - runners don't require privileged containers
  • Internal networking - leverage cluster service discovery
  • Semantic versioning - automated version bumps based on commit messages

Considered Options

  1. Gitea Actions with rootless DinD runners
  2. External CI/CD (GitHub Actions, GitLab CI)
  3. Self-hosted Jenkins/Drone
  4. Tekton Pipelines

Decision Outcome

Chosen option: Option 1 - Gitea Actions with rootless DinD runners

Gitea Actions provides GitHub Actions compatibility, runs inside the cluster with access to internal services, and supports rootless Docker-in-Docker for secure container builds.

Positive Consequences

  • GitHub Actions syntax familiarity
  • In-cluster access to internal services
  • Built-in container registry integration
  • No external dependencies
  • Rootless execution for security

Negative Consequences

  • Some GitHub Actions may not work (org-specific actions)
  • Rootless DinD has some limitations
  • Self-hosted maintenance burden

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Developer Push                                  │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              Gitea Server                                    │
│                    (git.daviestechlabs.io)                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        Actions Trigger                               │    │
│  │  • Push to main branch                                              │    │
│  │  • Pull request                                                     │    │
│  │  • Tag creation                                                     │    │
│  │  • workflow_dispatch                                                │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Gitea Actions Runner                                 │
│                    (rootless Docker-in-Docker)                              │
│                                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐                     │
│  │  Checkout   │───▶│   Buildx    │───▶│    Push     │                     │
│  │             │    │   Build     │    │  Registry   │                     │
│  └─────────────┘    └─────────────┘    └──────┬──────┘                     │
│                                               │                             │
└───────────────────────────────────────────────┼─────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Gitea Container Registry                             │
│              (gitea-http.gitea.svc.cluster.local:3000)                      │
│                                                                             │
│  Images:                                                                    │
│  • daviestechlabs/ray-worker-nvidia:v1.0.1                                 │
│  • daviestechlabs/ray-worker-rdna2:v1.0.1                                  │
│  • daviestechlabs/ray-worker-strixhalo:v1.0.1                              │
│  • daviestechlabs/ray-worker-intel:v1.0.1                                  │
│  • daviestechlabs/ntfy-discord:latest                                      │
└─────────────────────────────────────────────────────────────────────────────┘

Runner Configuration

Rootless Docker-in-Docker

The runner uses rootless Docker for security:

# Runner deployment uses rootless DinD
# No privileged containers required
# No sudo access in workflows

Runner Registration

Runners must be registered with project-scoped tokens, not instance tokens:

  1. Go to Repository → Settings → Actions → Runners
  2. Create new runner with project token
  3. Use token for runner registration

Common mistake: Using instance-level token causes jobs not to be picked up.

Registry Authentication

Internal HTTP Endpoint

Use internal cluster DNS for registry access. This avoids:

  • Cloudflare tunnel 100MB upload limit
  • TLS certificate issues
  • External network latency
env:
  REGISTRY: gitea-http.gitea.svc.cluster.local:3000/daviestechlabs
  REGISTRY_HOST: gitea-http.gitea.svc.cluster.local:3000

Buildx Configuration

Configure buildx to use HTTP for internal registry:

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3
  with:
    buildkitd-config-inline: |
      [registry."gitea-http.gitea.svc.cluster.local:3000"]
        http = true
        insecure = true

Credential Configuration

For rootless DinD, create docker config directly (no docker login - it defaults to HTTPS):

- name: Configure Gitea Registry Auth
  if: github.event_name != 'pull_request'
  run: |
    AUTH=$(echo -n "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" | base64 -w0)
    mkdir -p ~/.docker
    cat > ~/.docker/config.json << EOF
    {
      "auths": {
        "${{ env.REGISTRY_HOST }}": {
          "auth": "$AUTH"
        }
      }
    }
    EOF

Important: Buildx reads ~/.docker/config.json for authentication during push. Do NOT use docker login for HTTP registries as it defaults to HTTPS.

Required Secrets

Configure in Repository → Settings → Actions → Secrets:

Secret Purpose
REGISTRY_USER Gitea username with package write access
REGISTRY_TOKEN Gitea access token with write:package scope
DOCKERHUB_TOKEN (Optional) Docker Hub token for rate limit bypass

Semantic Versioning

Commit Message Conventions

Version bumps are determined from commit message prefixes:

Prefix Bump Type Example
major: or BREAKING CHANGE Major (x.0.0) major: Remove deprecated API
minor:, feat:, feature: Minor (0.x.0) feat: Add new endpoint
(anything else) Patch (0.0.x) fix: Correct typo

Version Calculation

- name: Calculate semantic version
  id: version
  run: |
    LATEST=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
    VERSION=${LATEST#v}
    IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
    
    MSG="${{ github.event.head_commit.message }}"
    if echo "$MSG" | grep -qiE "^major:|BREAKING CHANGE"; then
      MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0
    elif echo "$MSG" | grep -qiE "^(minor:|feat:|feature:)"; then
      MINOR=$((MINOR + 1)); PATCH=0
    else
      PATCH=$((PATCH + 1))
    fi
    
    echo "version=v${MAJOR}.${MINOR}.${PATCH}" >> $GITHUB_OUTPUT

Automatic Tagging

After successful builds, create and push a git tag:

- name: Create and push tag
  run: |
    git config user.name "gitea-actions[bot]"
    git config user.email "actions@git.daviestechlabs.io"
    git tag -a "$VERSION" -m "Release $VERSION ($BUMP)"
    git push origin "$VERSION"

Notifications

ntfy Integration

Send build status to ntfy for notifications:

- name: Notify on success
  run: |
    curl -s \
      -H "Title: ✅ Images Built: ${{ gitea.repository }}" \
      -H "Priority: default" \
      -H "Tags: white_check_mark,docker" \
      -d "Version: ${{ needs.determine-version.outputs.version }}" \
      http://ntfy.observability.svc.cluster.local:80/gitea-ci

Skip Patterns

Commit Message Skip Flags

Flag Effect
[skip images] Skip all image builds
[ray-serve only] Skip worker images
[skip ci] Skip entire workflow

Path-based Triggers

Only run on relevant file changes:

on:
  push:
    paths:
      - 'dockerfiles/**'
      - '.gitea/workflows/build-push.yaml'

Troubleshooting

Common Issues

Issue Cause Solution
Jobs not picked up Instance token instead of project token Re-register with project-scoped token
401 Unauthorized Missing or wrong registry credentials Check REGISTRY_USER and REGISTRY_TOKEN secrets
"http: server gave HTTP response to HTTPS client" Using docker login with HTTP registry Create config.json directly, don't use docker login
Cloudflare 100MB upload limit Using external endpoint for large images Use internal HTTP endpoint
TLS certificate error Using HTTPS with self-signed cert Use internal HTTP endpoint with buildkitd http=true
sudo not found Rootless DinD has no sudo Use user-space configuration methods
"must contain at least one job without dependencies" All jobs have needs Ensure at least one job has no needs clause

Debugging

  1. Check runner logs in Gitea Actions UI
  2. Add debug output: echo "::debug::Variable=$VAR"
  3. Use actions/debug-output step for verbose logging

Workflow Template

See kuberay-images/.gitea/workflows/build-push.yaml for complete example.

Future Enhancements

  1. Caching improvements - Persistent layer cache across builds
  2. Multi-arch builds - ARM64 support for Raspberry Pi
  3. Security scanning - Trivy integration in CI
  4. Signed images - Cosign for image signatures
  5. SLSA provenance - Supply chain attestations

References