Challenge Structure
The complete anatomy of a Kubeasy challenge — every file, every field, and how they work together.
Every Kubeasy challenge is a folder in the challenges repository following a consistent structure. This page is the reference for everything a challenge can contain.
Directory layout
challenge-slug/
├── challenge.yaml # Metadata, description, and validation objectives
├── manifests/ # Kubernetes resources (the initial state)
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ...
├── policies/ # Kyverno policies (bypass prevention)
│ └── protect.yaml
└── image/ # Optional: custom Docker image
└── Dockerfile1. challenge.yaml
The challenge.yaml file is the single source of truth for a challenge. It defines everything: metadata, the narrative description, and the validation objectives.
Metadata fields
Prop
Type
Challenge types
The type field defines the nature of the problem:
| Type | Starting state | What the user does |
|---|---|---|
fix | Broken setup | Investigate and repair |
build | Empty or minimal | Create the required resources |
migrate | Working but outdated | Transform to a new pattern |
See Challenge Types for a detailed explanation of each.
Writing good descriptions
description — describe symptoms, not causes. The user should not know what's wrong before they investigate.
# BAD: reveals the root cause
description: |
The memory limit is too low for this workload. Increase it to fix the crash.
# GOOD: describes symptoms
description: |
A data processing service keeps restarting. The team says it worked fine last week.
No code changes were made — something else must be wrong.initialSituation — describe what the user will find when they start, not why it's happening.
objective — state the goal, not the method.
# BAD: tells the user what to do
objective: |
Increase the memory limit to at least 256Mi.
# GOOD: describes the desired outcome
objective: |
Make the pod run stably without being killed by Kubernetes.Full example
title: Memory Pressure
description: |
A data processing service keeps restarting unexpectedly.
The team says the workload hasn't changed, but something is killing the pod.
theme: resources-scaling
difficulty: easy
type: fix
estimatedTime: 15
initialSituation: |
A data processing pod is deployed in the cluster.
It starts successfully but crashes within a few seconds.
The pod enters CrashLoopBackOff state and keeps restarting.
objective: |
Make the application run stably without being killed.
objectives:
- key: pod-ready
title: "Pod Ready"
description: "The pod must be running and ready"
order: 1
type: condition
spec:
target:
kind: Pod
labelSelector:
app: data-processor
checks:
- type: Ready
status: "True"
- key: stable-operation
title: "Stable Operation"
description: "No crash or eviction events in the past 5 minutes"
order: 2
type: event
spec:
target:
kind: Pod
labelSelector:
app: data-processor
forbiddenReasons:
- "OOMKilled"
- "Evicted"
sinceSeconds: 3002. manifests/ directory
Contains the Kubernetes resources that are deployed when a user starts the challenge. These define the initial state — typically broken, minimal, or outdated depending on the challenge type.
Design principles:
- Minimal — only include what's relevant to the challenge
- Realistic — mirror real production problems, not artificial puzzles
- Self-contained — don't reference external resources or services
# manifests/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-processor
spec:
replicas: 1
selector:
matchLabels:
app: data-processor
template:
metadata:
labels:
app: data-processor
spec:
containers:
- name: processor
image: python:3.11-slim
resources:
limits:
memory: "32Mi" # BUG: too low for this workload
cpu: "100m"3. policies/ directory
Contains Kyverno policies that prevent bypasses — stopping users from taking shortcuts that avoid solving the actual problem.
Policies use kind: Policy (namespace-scoped) with metadata.namespace set to the challenge slug. A namespace-scoped Policy enforces only within its own namespace, so it never affects the rest of the cluster.
What to protect:
- Container images (prevent swapping the broken app for a working one)
- Critical volume configurations
- Labels used by validation (so resources remain findable)
What NOT to protect:
- Resource limits/requests (the user needs to change these)
- Environment variables
- Probe configurations
- Most annotations
# policies/protect.yaml
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: protect-data-processor
namespace: pod-evicted # must match the challenge slug (the namespace name)
spec:
validationFailureAction: Enforce
rules:
- name: preserve-image
match:
resources:
kinds: ["Deployment"]
names: ["data-processor"]
validate:
message: "Cannot change the container image"
pattern:
spec:
template:
spec:
containers:
- name: processor
image: "python:3.11-slim"Use kind: Policy (namespace-scoped), not kind: ClusterPolicy. Set metadata.namespace to the challenge slug — this is the namespace where the challenge is deployed. A namespace-scoped Policy automatically enforces only within its own namespace.
4. image/ directory (optional)
If your challenge requires custom application behavior that a standard image can't provide, add an image/ directory with a Dockerfile.
my-challenge/
├── challenge.yaml
├── manifests/
├── policies/
└── image/
├── Dockerfile
└── app.pyAutomatic CI build
When your challenge is merged to main, CI automatically:
- Detects challenges with an
image/directory - Builds a multi-arch image (
linux/amd64,linux/arm64) - Pushes to
ghcr.io/kubeasy-dev/<challenge-slug>:latest
Reference it in your manifests:
containers:
- name: app
image: ghcr.io/kubeasy-dev/my-challenge:latestLocal development
During local development with kubeasy dev apply, the CLI automatically builds the image from image/ and loads it into the Kind cluster — no manual docker build or kind load needed.
Best practices
- Use slim or alpine base images to keep sizes small
- Keep the Dockerfile simple and deterministic
- Don't include secrets or credentials
- Add a
.dockerignoreto exclude unnecessary files
Themes
The theme field groups challenges in the UI:
| Theme | Description |
|---|---|
rbac-security | Permissions, roles, security contexts |
networking | Services, Ingress, NetworkPolicies |
volumes-secrets | Storage, ConfigMaps, Secrets |
resources-scaling | Limits, requests, HPA, scaling |
monitoring-debugging | Probes, logging, events |
How it all fits together
- User runs
kubeasy challenge start <slug>→ CLI creates a namespace and deploysmanifests/andpolicies/ - User investigates and fixes the problem using
kubectl - User runs
kubeasy challenge submit <slug>→ CLI runs theobjectivesfromchallenge.yamlagainst the cluster - If all objectives pass → XP awarded, challenge marked complete
Next steps
- Creating Your First Challenge — step-by-step tutorial
- Validation Overview — how to write effective objectives