Files
2026-06-29 17:33:15 -05:00

16 KiB

AGENT.md

Project

Name: custom-gitea-runner

Purpose: Build a self-owned, versioned, Docker-first Gitea Actions runner platform that avoids opaque upstream behavior, is easy to debug, and is safe to operate on Unraid and general Linux hosts. This project should produce a maintainable runner image, startup logic, configuration templates, and documentation that make runner behavior explicit and reproducible.

This project exists because the stock/self-hosted runner path introduced hard-to-diagnose issues around nested containers, action runtime dependencies, container image behavior, and host/runtime compatibility. The replacement should favor observability, deterministic builds, explicit tooling, and low-surprise operations.


Mission

Create a production-ready custom Gitea runner solution that:

  • Runs reliably in Docker on Unraid and standard Linux hosts.
  • Supports JavaScript-based actions by shipping the required runtime dependencies explicitly.
  • Supports Docker build/push workflows without hidden image assumptions.
  • Uses pinned versions wherever practical.
  • Separates image build concerns from runtime registration/config concerns.
  • Makes debugging first-class through logs, health checks, self-tests, and clear failure modes.
  • Keeps all project knowledge versioned in-repo through AGENT.md, README.md, ROADMAP.md, CHANGELOG.md, and example configs.

Product goals

  1. Build a custom Docker image for a Gitea-compatible runner.
  2. Provide an entrypoint/bootstrap script that prepares the runtime deterministically.
  3. Support runner registration through environment variables and/or mounted config.
  4. Support common CI workloads for the target environment: checkout, Node-based actions, Docker login, Docker build, Docker push, shell scripts, and future extensibility for additional tooling.
  5. Provide a clean path for image tagging, publishing, rollback, and host deployment.
  6. Minimize dependence on mutable upstream defaults like latest, floating action images, or runtime package installs during startup.
  7. Make the image and scripts inspectable, testable, and easy to extend.

Non-goals

  • Do not build a full replacement for the Gitea server.
  • Do not invent a proprietary CI protocol if standard Gitea runner registration is sufficient.
  • Do not optimize first for Kubernetes.
  • Do not assume multi-tenant hostile workloads at v1; this is a controlled self-hosted environment.
  • Do not rely on runtime apk add or ad hoc container mutation as a permanent solution.

Development philosophy

Treat this like an internal platform product, not a one-off container hack.

Principles:

  • Version everything. Image versions, dependency versions, docs, sample configs, and release notes should all be explicit.
  • Document while building. If behavior changes, update README.md and ROADMAP.md in the same workstream.
  • Prefer deterministic builds. Install dependencies at image build time, not container startup.
  • Prefer explicit over magical. If Node, Git, Docker CLI, certificates, or helper binaries are required, declare and verify them.
  • Design for diagnosis. Every major startup step should emit useful logs.
  • Keep the bootstrap thin. Complex logic should live in versioned scripts/modules, not giant one-line container commands.
  • Bias for maintainability. Small, composable scripts beat clever entrypoints.

User and environment assumptions

Primary operator profile:

  • Advanced technical operator.
  • Comfortable with Docker, Unraid, Node.js/TypeScript, Bash, and self-hosted infrastructure.
  • Prefers documented roadmaps, structured project files, and reusable internal tooling.

Primary target environment:

  • Unraid Docker host first.
  • Standard Linux Docker host second.
  • Gitea instance is self-hosted.
  • Registry may be self-hosted Gitea Container Registry.
  • Project repositories are hosted in Gitea.

Required deliverables

The agent should scaffold and maintain at minimum:

custom-gitea-runner/
├── AGENT.md
├── README.md
├── ROADMAP.md
├── CHANGELOG.md
├── LICENSE
├── .gitignore
├── .env.example
├── docker/
│   ├── runner/
│   │   ├── Dockerfile
│   │   ├── docker-entrypoint.sh
│   │   ├── healthcheck.sh
│   │   └── smoke-test.sh
│   └── compose/
│       ├── docker-compose.example.yml
│       └── unraid-template-notes.md
├── config/
│   ├── runner.example.yaml
│   ├── labels.example.env
│   └── trusted-ca/
├── scripts/
│   ├── register-runner.sh
│   ├── deregister-runner.sh
│   ├── verify-runtime.sh
│   ├── build-image.sh
│   ├── publish-image.sh
│   └── test-local-runner.sh
├── docs/
│   ├── architecture.md
│   ├── operations.md
│   ├── troubleshooting.md
│   ├── unraid.md
│   └── decisions/
│       └── 0001-base-image.md
├── test/
│   ├── fixtures/
│   ├── sample-workflows/
│   └── smoke/
└── src/
    └── optional/

Notes:

  • src/ is optional unless the project grows into a TypeScript control plane or helper CLI.
  • If a CLI/helper app is introduced, prefer TypeScript for maintainability and consistency with the project stack.

Baseline architecture

Start with a Docker image that wraps or embeds the official act_runner binary but controls:

  • base image selection,
  • runtime package set,
  • startup/registration flow,
  • mounted config handling,
  • Docker socket strategy,
  • health checks,
  • and diagnostics.

Preferred v1 design

  • Debian or Ubuntu slim base image, not an ultra-minimal image that requires runtime mutation.
  • Install at build time:
    • ca-certificates
    • bash
    • curl
    • git
    • jq
    • nodejs
    • npm
    • docker CLI
    • tini
    • timezone data
  • Copy in a pinned act_runner binary or install from a pinned release artifact.
  • Use tini as PID 1.
  • Use a real multi-line docker-entrypoint.sh.
  • Validate runtime prerequisites before daemon start.
  • Prefer mounted /var/run/docker.sock for v1 if the host is trusted and this matches the deployment model.
  • Expose health check status through a lightweight script.

Why this design

This directly addresses the observed failure class:

  • runtime packages missing in the runner image,
  • overreliance on upstream defaults,
  • brittle nested-container behavior,
  • and low observability when setup fails.

Runner behavior requirements

The custom runner must:

  1. Start deterministically with a documented set of environment variables.
  2. Fail fast when required inputs are missing.
  3. Verify and log the presence of key tools:
    • git
    • node
    • npm
    • docker
    • act_runner
  4. Emit version information at startup.
  5. Support both pre-registered config files and first-boot registration flows.
  6. Avoid mutating the filesystem in surprising ways at runtime beyond known writable paths.
  7. Support Docker-based build/push jobs cleanly.
  8. Provide optional label mapping guidance for common targets like ubuntu-latest.
  9. Make workspace and temp directories explicit and configurable.
  10. Provide a smoke-test path that validates the runner image before production use.

Configuration model

Support two modes.

Mode A: Pre-generated config

Operator mounts:

  • runner config yaml
  • optional registration metadata
  • optional CA bundle

This is the preferred stable production mode.

Mode B: Bootstrap registration

Operator provides env vars such as:

  • GITEA_INSTANCE_URL
  • GITEA_RUNNER_TOKEN
  • GITEA_RUNNER_NAME
  • GITEA_RUNNER_LABELS
  • GITEA_RUNNER_WORKDIR
  • GITEA_RUNNER_CONFIG_PATH

Entrypoint performs:

  1. validation,
  2. one-time registration if config absent,
  3. daemon startup.

The agent should design this so reboots are idempotent.


Docker and security model

v1 security posture

This runner is intended for a trusted, self-hosted environment. Simplicity and reliability are more important than extreme sandbox isolation in v1.

v1 approach

  • Support Docker socket mount for build/push workloads.
  • Document the trust implications clearly.
  • Keep the runner container itself as minimal and explicit as possible.
  • Avoid privileged mode unless a specific feature requires it.
  • Prefer read-only mounts where practical except for config/work directories.
  • Run as non-root where feasible, but do not force complexity if Docker socket access or upstream tooling makes that unrealistic in v1.

Future hardening track

Document as roadmap items:

  • rootless variants,
  • DinD variant,
  • dedicated buildkit integration,
  • ephemeral one-job runners,
  • restricted labels by repo or org,
  • network isolation strategies.

Documentation requirements

The agent must keep these files current as the project evolves.

README.md

Should include:

  • project purpose,
  • why this exists,
  • supported features,
  • architecture overview,
  • quick start,
  • environment variables,
  • Docker compose example,
  • Unraid deployment notes,
  • sample workflow guidance,
  • troubleshooting links,
  • versioning and release model.

ROADMAP.md

Must track phases, status, and next actions.

Recommended sections:

  • Phase 0: Research and design
  • Phase 1: Base image and bootstrap
  • Phase 2: Registration/config flows
  • Phase 3: Docker workflow support
  • Phase 4: Test harness and smoke validation
  • Phase 5: Unraid packaging and docs
  • Phase 6: Hardening and enhancements

CHANGELOG.md

Use Keep a Changelog style:

  • Added
  • Changed
  • Fixed
  • Removed

docs/troubleshooting.md

Must be practical and symptom-driven.

Include sections for issues like:

  • runner not registering,
  • node not found,
  • Docker socket permission errors,
  • workflow checkout failures,
  • registry login failures,
  • container job image issues,
  • CA/certificate trust issues,
  • Unraid-specific gotchas.

Functional scope for v1

Must support

  • actions/checkout
  • docker/login-action
  • docker build
  • docker push
  • shell-based build scripts
  • Gitea registry auth patterns
  • branch/tag based image builds

Nice to support in v1 if straightforward

  • buildx
  • cache mounts
  • custom CA installation
  • repo/org label strategies
  • self-test action workflow

Defer unless easy

  • autoscaling
  • UI dashboard
  • metrics exporter
  • multi-runner orchestrator
  • web control plane

Testing strategy

The project should include real validation, not just build success.

Required tests

  1. Image build test

    • Docker image builds successfully from scratch.
  2. Runtime verification test

    • Container starts and confirms key binaries exist.
  3. Registration smoke test

    • Runner can register against a test Gitea instance or mocked flow.
  4. Workflow smoke tests

    • checkout-only workflow,
    • Node-based action workflow,
    • Docker login workflow,
    • Docker build/push workflow against a test registry.
  5. Regression tests

    • specifically capture previously observed failure classes around missing Node and fragile container behavior.

Testing philosophy

Every bug that costs real debugging time should become:

  • a documented issue,
  • a troubleshooting entry,
  • and ideally a regression test.

Logging and observability

Startup logs should clearly print:

  • image version,
  • git commit if embedded,
  • act_runner version,
  • node version,
  • docker version,
  • registration mode,
  • config path,
  • work directory,
  • labels,
  • target Gitea URL.

Do not log secrets.

Health check should verify at minimum:

  • runner process exists,
  • config is present,
  • Docker socket availability if required,
  • core binaries exist.

Optional future enhancements:

  • structured JSON logs,
  • /healthz sidecar or helper,
  • metrics export.

Versioning and release policy

Use semantic-ish tags for the custom image.

Recommended tagging:

  • v0.1.0
  • v0.1.1
  • v0.2.0
  • latest only as a convenience alias to the newest stable release

Rules:

  • Never deploy unpinned latest in production docs as the primary recommendation.
  • Every release should map to a Git tag and CHANGELOG entry.
  • Embed image metadata labels such as version, vcs ref, build date, and source URL.

Agent operating instructions

When working on this project, the agent must:

  1. Update ROADMAP.md when a phase changes or a major task is added/closed.
  2. Update README.md when usage, env vars, image behavior, or deployment steps change.
  3. Update CHANGELOG.md for user-visible changes.
  4. Prefer small, reviewable commits/changesets.
  5. Ask for clarification before making irreversible architecture choices when multiple valid paths exist.
  6. Keep scripts POSIX/Bash-friendly and easy to audit.
  7. Avoid hidden runtime package installation unless explicitly implementing a temporary debug path.
  8. Prefer pinned versions in Dockerfile and examples.
  9. Add troubleshooting notes whenever a new failure mode is discovered.
  10. Preserve portability between Unraid and standard Linux where reasonable.

Implementation preferences

Preferred languages and tooling:

  • Bash for bootstrap and operational scripts.
  • Dockerfile for image definition.
  • TypeScript only if a helper CLI/control plane becomes necessary.
  • Markdown for all project documentation.
  • Git-based versioning hosted in Gitea.

Preferred project style:

  • Modular files.
  • Clean naming.
  • Self-explanatory scripts.
  • No giant inline command strings in container templates.
  • Examples should be copy-paste friendly.

Suggested first milestones

Milestone 1: Foundation

  • Initialize repository structure.
  • Write README.md skeleton.
  • Write ROADMAP.md.
  • Write Dockerfile using pinned base image.
  • Write entrypoint with startup validation.
  • Add healthcheck script.

Milestone 2: Registration and config

  • Add env-driven bootstrap registration flow.
  • Add mounted-config mode.
  • Add .env.example.
  • Add example runner config.

Milestone 3: CI workload support

  • Ensure Node, Git, Docker CLI, CA certs all present.
  • Validate actions/checkout and docker/login-action support.
  • Build sample workflow fixtures.

Milestone 4: Unraid packaging

  • Create compose example.
  • Document Unraid template fields and recommended mounts.
  • Add upgrade and rollback instructions.

Milestone 5: Quality and regression safety

  • Add smoke-test script.
  • Add local test harness.
  • Add troubleshooting guide.
  • Capture prior bug classes as regression cases.

Decision log expectations

Create ADR-style notes under docs/decisions/ for major decisions such as:

  • base image choice,
  • root vs non-root default,
  • socket mount vs DinD,
  • bootstrap registration design,
  • bundled tools list,
  • release strategy.

Each decision note should include:

  • context,
  • decision,
  • consequences,
  • alternatives considered.

Success criteria

This project is successful when:

  • a fresh operator can build and run the image with documented steps,
  • the runner reliably executes checkout + Docker login + build + push workflows,
  • Node-based actions work without runtime hacks,
  • failures are diagnosable from logs and docs,
  • upgrades are explicit and reversible,
  • and the entire runner behavior is version-controlled and understandable.

Open questions for early clarification

Before heavy implementation, clarify:

  1. Should v1 wrap the official act_runner binary or fork/build from source?
  2. Is the preferred base image Debian slim or Ubuntu minimal?
  3. Should v1 default to socket mount only, or support both socket mount and DinD variants from day one?
  4. Should the repository include an Unraid XML/template artifact, or just docs plus compose examples?
  5. Should the first release include a small helper CLI for registration/testing, or keep everything shell-only?
  6. Should runner labels be opinionated by default, or minimal and operator-supplied?
  7. Should custom CA trust be a first-class feature in v1?

If these are unanswered, the agent should pause and ask before overcommitting to a direction.


Build order

When starting implementation, follow this order:

  1. AGENT.md
  2. README.md
  3. ROADMAP.md
  4. Dockerfile
  5. entrypoint/bootstrap scripts
  6. healthcheck/smoke tests
  7. config examples
  8. compose/unraid examples
  9. troubleshooting docs
  10. regression fixtures/tests

Final instruction

Optimize for reliability, debuggability, and maintainability over cleverness.

This project should feel like a stable internal platform component that the operator can trust, inspect, extend, and version for years.