Kubeasy LogoKubeasy

Creating Your First Challenge

Step-by-step guide to building a Kubeasy challenge from scratch.

Last updated: January 25, 2026GitHubView on GitHub

This guide walks you through creating a complete Kubeasy challenge. By the end, you'll have a working challenge ready for submission.

Before You Start

Make sure you have:

Step 1: Design Your Challenge

Before writing any YAML, answer these questions:

  1. What Kubernetes concept does this teach?

    • Example: Resource limits, RBAC, probes
  2. What's the broken scenario?

    • Example: Pod gets OOMKilled due to low memory limit
  3. How do we verify it's fixed?

    • Example: Pod runs without crash events
  4. Is this realistic?

    • Does this problem occur in production?

Example Design

Let's create a challenge about resource limits:

  • Concept: Kubernetes resource management
  • Problem: Pod keeps crashing due to insufficient memory
  • Success: Pod runs stably without OOM events
  • Realistic: Yes - very common in production

Step 2: Create the Directory Structure

# Clone the challenges repo
git clone https://github.com/kubeasy-dev/challenges.git
cd challenges

# Create your challenge folder
mkdir -p memory-pressure/manifests
mkdir -p memory-pressure/policies

You should have:

memory-pressure/
├── manifests/
└── policies/

Step 3: Write challenge.yaml

Create memory-pressure/challenge.yaml:

title: Memory Pressure
description: |
  A data processing application keeps crashing.
  The ops team says it worked fine in development.
theme: resources-scaling
difficulty: easy
estimated_time: 15
initial_situation: |
  A data processing pod is deployed.
  It starts but crashes within seconds.
  The pod enters CrashLoopBackOff state.
objective: |
  Make the application run stably.
  Understand why Kubernetes keeps killing the pod.

validations:
  - key: pod-ready
    title: "Pod Running"
    description: "The pod must be in Ready state"
    order: 1
    type: status
    spec:
      target:
        kind: Pod
        labelSelector:
          app: data-processor
      conditions:
        - type: Ready
          status: "True"

  - key: no-oom
    title: "Stable Operation"
    description: "No crash or eviction events"
    order: 2
    type: event
    spec:
      target:
        kind: Pod
        labelSelector:
          app: data-processor
      forbiddenReasons:
        - "OOMKilled"
        - "Evicted"
      sinceSeconds: 300

  - key: low-restarts
    title: "Low Restart Count"
    description: "Pod must not restart excessively"
    order: 3
    type: metrics
    spec:
      target:
        kind: Pod
        labelSelector:
          app: data-processor
      metricChecks:
        - metric: restartCount
          operator: LessThan
          value: 3

Key points:

  • Description mentions symptoms, not the cause
  • Objective states the goal, not the solution
  • Validation titles are generic (don't reveal the fix)

Step 4: Create the Broken Manifests

Create memory-pressure/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
        command:
        - python
        - -c
        - |
          import time
          # Allocate memory to simulate real app
          data = []
          for i in range(50):
              data.append("x" * 1024 * 1024)  # ~50MB
              time.sleep(0.1)
          print("Processing complete!")
          while True:
              time.sleep(60)
        resources:
          limits:
            memory: "32Mi"  # BUG: Too low for this workload!
            cpu: "100m"
          requests:
            memory: "32Mi"
            cpu: "50m"

This deployment will crash because:

  • The app needs ~50MB of memory
  • The limit is only 32Mi
  • Kubernetes kills the container when it exceeds the limit

Need a Custom Image?

If inline commands aren't enough, you can create a custom Docker image. Add an image/ directory with a Dockerfile in your challenge folder - the CI will automatically build and publish it to ghcr.io/kubeasy-dev/<challenge-name>:latest.

See Challenge Structure - Custom Images for details.

Step 5: Add Bypass Protection

Create memory-pressure/policies/protect.yaml:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: protect-memory-pressure
spec:
  validationFailureAction: Enforce
  rules:
    - name: preserve-image
      match:
        resources:
          kinds: ["Deployment"]
          names: ["data-processor"]
          namespaces: ["challenge-*"]
      validate:
        message: "Cannot change the container image"
        pattern:
          spec:
            template:
              spec:
                containers:
                  - name: processor
                    image: "python:3.11-slim"

This prevents users from replacing the application with a simpler one that uses less memory.

Step 6: Test Locally

Start the environment

# Initialize Kubeasy (creates Kind cluster + installs components)
kubeasy init

# Login to Kubeasy
kubeasy login

Deploy your challenge

kubeasy challenge start memory-pressure

Verify the problem exists

# Check pod status
kubectl get pods -n challenge-memory-pressure

# You should see CrashLoopBackOff
NAME                              READY   STATUS             RESTARTS   AGE
data-processor-xxx                0/1     CrashLoopBackOff   3          2m

# Check events
kubectl describe pod -n challenge-memory-pressure -l app=data-processor
# Look for OOMKilled

Apply the fix

kubectl patch deployment data-processor -n challenge-memory-pressure \
  --type='json' -p='[
    {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "128Mi"},
    {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "64Mi"}
  ]'

Verify the fix works

# Wait for pod to stabilize
sleep 30

# Check pod is running
kubectl get pods -n challenge-memory-pressure
# Should show Running, not CrashLoopBackOff

Submit and verify validations

kubeasy challenge submit memory-pressure

All validations should pass.

Clean up

kubeasy challenge reset memory-pressure

Step 7: Submit Your Challenge

Once testing is complete:

git checkout -b challenge/memory-pressure
git add memory-pressure/
git commit -m "feat: add memory-pressure challenge"
git push origin challenge/memory-pressure

Open a Pull Request on GitHub with:

## Challenge: Memory Pressure

### What does this challenge teach?
Understanding Kubernetes resource limits and how to debug OOMKilled pods.

### Difficulty and estimated time
- Difficulty: Easy
- Estimated time: 15 minutes

### Testing
- [x] Challenge deploys with broken state
- [x] OOMKilled events occur as expected
- [x] Fix (increase memory limit) resolves the issue
- [x] All validations pass after fix
- [x] Kyverno policy prevents image changes

Common Mistakes

1. Revealing the solution

Bad:

description: |
  The memory limit is too low. Increase it to fix the crash.

Good:

description: |
  The application keeps crashing. It worked fine in development.

2. Validation titles that spoil the fix

Bad:

- key: memory-check
  title: "Memory Limit Set to 128Mi"

Good:

- key: stable-operation
  title: "Stable Operation"

3. Missing bypass protection

Users can replace your broken app with one that works. Always add Kyverno policies to prevent this.

4. Too complex

Don't combine multiple unrelated issues. One concept per challenge.

Checklist Before Submitting

  • challenge.yaml has all required fields
  • Description doesn't reveal the root cause
  • Validation titles don't reveal the solution
  • Manifests create a reproducible broken state
  • Kyverno policies prevent obvious bypasses
  • Challenge tested locally end-to-end
  • All validations pass after applying the fix

Next Steps

On this page