The registry is the source of truth for all Kubeasy challenges. It serves challenge metadata, objectives, and manifests to the CLI and the web app via a simple HTTP API.
challenges/ # All challenge definitions (Git = source of truth)
pod-evicted/
challenge.yaml # Metadata + objectives
manifests/ # Kubernetes manifests (broken state)
policies/ # Kyverno policies (bypass prevention)
...
pkg/challenges/ # Shared Go library — imported by the CLI
types.go # Challenge, Objective, all spec types
parse.go # YAML → typed structs
validate.go # Structural validation
loader.go # Scan a directory and load all challenges
internal/
server/server.go # HTTP handlers (chi router)
store/store.go # In-memory challenge store
cmd/
root.go # --challenges-dir flag
serve.go # registry serve
validate.go # registry validate [slug]
main.go # Entrypoint
| Endpoint | Description |
|---|---|
GET /challenges |
List all challenges |
GET /challenges/{slug} |
Full challenge JSON (metadata + objectives) |
GET /challenges/{slug}/yaml |
Raw challenge.yaml (used by CLI) |
GET /challenges/{slug}/manifests |
.tar.gz of manifests/ + policies/ |
mise install
go run . serve
# CHALLENGES_DIR defaults to ./challengesOverride the challenges directory:
go run . serve --challenges-dir /path/to/challengesgo run . validate # all challenges
go run . validate pod-evicted # single challenge- Create a folder under
challenges/(folder name = slug) - Add
challenge.yaml,manifests/, and optionallypolicies/ - Push — Railway redeploys automatically, CI validates on PR
See CHALLENGE_GUIDELINES.md for the full spec.
The registry is consumed by:
- kubeasy-cli — imports
pkg/challengesfor types, calls/challenges/{slug}/yamlfor runtime data - monorepo API — calls the registry HTTP API and fan-outs challenge data alongside user progress
See the Kubeasy CLAUDE.md for the full system architecture.