Laptop Bastion Host
────── ────────────
export OCP_API_URL=... export OPENSHIFT_CLUSTER_INGRESS_DOMAIN=...
grade_lab lab user1 --podman grade_lab lab user1 --ansible
│ │
▼ ▼
ghcr.io/rhpds/ftl ansible-playbook
(container) (direct)
│ │
└──────────────┬────────────────────┘
▼
OCP / AAP APIs
User passwords auto-discovered from Showroom ConfigMap — no need to set manually.
# OCP-based labs (--podman from laptop)
# User passwords are discovered automatically from the cluster — no PASSWORD needed
export OCP_API_URL="https://api.cluster-xxx.dynamic.redhatworkshops.io:6443"
export OCP_ADMIN_PASSWORD="<admin-password>"
export OPENSHIFT_CLUSTER_INGRESS_DOMAIN="apps.cluster-xxx.dynamic.redhatworkshops.io"
grade_lab <lab-name> user1 --podman # single user
grade_lab <lab-name> all --podman # all users in parallel (load test)# AAP-based labs (RIPU)
export AAP_HOSTNAME="https://controller-xxx.apps.example.com"
export AAP_PASSWORD="<aap-password>"
grade_lab automating-ripu-with-ansible 1 --podmanSolve then verify:
solve_lab <lab-name> user1 --podman
grade_lab <lab-name> user1 --podman # expect: SUCCESS 0 ErrorsNo FTL repo needed — just podman and your cluster credentials:
# See available labs
podman run --rm ghcr.io/rhpds/ftl list
# Show help and auth options
podman run --rm ghcr.io/rhpds/ftl --help
# Grade with username/password
podman run --rm \
-e OPENSHIFT_API_URL=https://api.cluster-xxxxx.dynamic.redhatworkshops.io:6443 \
-e OPENSHIFT_USERNAME=admin \
-e OPENSHIFT_PASSWORD=<admin-password> \
-e OPENSHIFT_CLUSTER_INGRESS_DOMAIN=apps.cluster-xxxxx.dynamic.redhatworkshops.io \
-v ~/ftl-reports:/tmp/grading_dir \
ghcr.io/rhpds/ftl grade mcp-with-openshift user1 1
# Grade with mounted kubeconfig
podman run --rm \
-v ~/.kube/config:/home/runner/.kube/config:ro \
-e OPENSHIFT_CLUSTER_INGRESS_DOMAIN=apps.cluster-xxxxx.dynamic.redhatworkshops.io \
-v ~/ftl-reports:/tmp/grading_dir \
ghcr.io/rhpds/ftl grade mcp-with-openshift user1 1
# Grade with token
podman run --rm \
-e OPENSHIFT_API_URL=https://api.cluster-xxxxx.dynamic.redhatworkshops.io:6443 \
-e OCP_TOKEN=sha256~xxx \
-e OPENSHIFT_CLUSTER_INGRESS_DOMAIN=apps.cluster-xxxxx.dynamic.redhatworkshops.io \
-v ~/ftl-reports:/tmp/grading_dir \
ghcr.io/rhpds/ftl grade mcp-with-openshift user1 1Reports are saved to ~/ftl-reports/ on your laptop via the -v mount.
Uses the grade_lab/solve_lab wrapper scripts — handles credential discovery from Showroom ConfigMaps, multi-user parallel grading, and report saving automatically.
# Clone FTL and add to PATH (first time only)
git clone https://github.com/rhpds/ftl.git ~/ftl
echo 'export PATH="$HOME/ftl/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
# Set credentials — user passwords are auto-discovered from Showroom ConfigMaps
export OCP_API_URL="https://api.cluster-xxx.dynamic.redhatworkshops.io:6443"
export OCP_ADMIN_PASSWORD="<admin-password>"
export OPENSHIFT_CLUSTER_INGRESS_DOMAIN="apps.cluster-xxx.dynamic.redhatworkshops.io"
grade_lab mcp-with-openshift user1 1 --podmanRequires ansible-playbook. Runs FTL directly on the bastion host.
# Install FTL (first time only)
git clone https://github.com/rhpds/ftl.git ~/ftl
bash ~/ftl/bin/setup_ftl
echo 'export PATH="$HOME/ftl/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
export OPENSHIFT_CLUSTER_INGRESS_DOMAIN="apps.cluster-xxx.example.com"
grade_lab mcp-with-openshift user1 1 --ansibleUse all as the user — automatically discovers users from showroom namespaces and runs in parallel:
# All users, single module
grade_lab mcp-with-openshift all 1 --podman
# All users, all modules
grade_lab mcp-with-openshift all --podmanUsers are discovered from showroom-*-userN namespaces in the cluster.
Each user’s password is read from their showroom-userdata ConfigMap automatically.
| Lab | Description | Modules | Checkpoints |
|---|---|---|---|
MCP servers, LibreChat, Gitea, LiteMaaS |
4 |
35 |
|
Parksmap, S2I builds, MongoDB, Tekton pipelines |
3 |
30 |
|
RHEL in-place upgrades via AAP 2.6 |
3 |
57 |
Each lab README has the exact export + grade_lab/solve_lab commands for that specific lab.
| Variable | Description | Required for |
|---|---|---|
|
OCP API URL — used to discover user credentials from cluster |
|
|
OCP admin password — only for credential discovery |
|
|
OCP cluster apps domain |
OCP labs |
|
User password — auto-discovered from Showroom ConfigMap when |
Bastion only |
|
Student username (auto-set from arg) |
Auto |
|
AAP Controller URL |
RIPU |
|
AAP password |
RIPU |
|
AAP username (default: |
RIPU |
|
Gitea admin username — auto-discovered from Showroom ConfigMap |
Override only |
|
Gitea admin password — auto-discovered from Showroom ConfigMap |
Override only |
|
Override container image (default: |
Dev only |
|
Local directory for grading reports (default: |
|
-
Grader Roles Reference — all 22+ roles with parameters and examples
-
Lab Template — starting point for new labs
cp -r labs/lab-template labs/my-new-lab
# Edit grade_module_01.yml and solve_module_01.yml
grade_lab my-new-lab student 1 --podman-
Use
kubernetes.core.k8s_info— neverocCLI (crashes on arm64 emulation) -
Provide full Deployment spec on creation — partial patches fail if object doesn’t exist
-
For S2I: create ImageStream BEFORE BuildConfig
See Grader Roles Reference for full details.
Based on ubi9/ubi-minimal with multi-arch oc client support (amd64 + arm64).
What’s baked in: Generic grader/solver roles (roles/), plugins, ansible config.
What’s cloned at runtime: Lab content (labs/) — no image rebuild needed when graders change.
Primary registry: ghcr.io/rhpds/ftl — also mirrored to quay.io/rhpds/ftl.
Images are tagged with both latest and a date tag (e.g. 2026-02-25) on every release.
# Build and push to both registries with latest + date tag
DATE_TAG=$(date +%Y-%m-%d)
podman build -t ghcr.io/rhpds/ftl:latest -t ghcr.io/rhpds/ftl:${DATE_TAG} \
-t quay.io/rhpds/ftl:latest -t quay.io/rhpds/ftl:${DATE_TAG} .
podman push ghcr.io/rhpds/ftl:latest
podman push ghcr.io/rhpds/ftl:${DATE_TAG}
podman push quay.io/rhpds/ftl:latest
podman push quay.io/rhpds/ftl:${DATE_TAG}
# Test with local labs (no push needed)
grade_lab <lab> user1 1 --podman --local
# Pin to a specific date tag
FTL_IMAGE=ghcr.io/rhpds/ftl:2026-02-25 grade_lab <lab> user1 --podman