dfd.academy lists the courses I write. Three of them work today and you can open any of them right now: Kubernetes, the Luxembourg citizenship test, and Luxembourgish vocabulary. None asks for payment, and only the Kubernetes one asks for an account.
Neither is finished. Every number on this page was counted from the repository it describes, with the commands printed at the bottom; where a count disagrees with the project's own README, the counted number is the one shown here. Each course also lists what is broken in it today, in the words I would use in a code review.
Both repositories are private right now, so this page does not link into them — those URLs 404 for anyone who is not on the repository. The paths are printed below as text; write to a@alftech.space and I will add you.
Courses
KubeLearn — Kubernetes
Open it and start
An interactive Kubernetes course in the Duolingo shape: a path of units, a handful of questions at each step, XP and a daily streak. Be clear about what it is: a question bank, not a textbook. A lesson is a title, one line of context and one to three questions — there is no reading material, so bring your own source and use this to find out what you have not understood. Get one wrong and it tells you why. Node, Express and SQLite on the back, React and Vite on the front. Make an account and work through it — no payment, no waiting list.
4: multiple choice, fill in the blank, true/false, code
Pass mark
70% of a lesson's questions
Cost
none
What was wrong, and what was done about it
Most questions sat on the wrong lesson. They referenced lessons by array position against an intended 84-lesson layout; only 81 exist, so everything after the first short unit had drifted and six questions fell off the end unnoticed. Each question now names its unit and lesson explicitly, and the loader refuses to start rather than silently dropping a row. Checked against the live data: every question sits on the lesson it was written for.
Seven lessons had no question at all — a score of 0/0 fails the 70% mark, so they were dead ends you could never clear. They have questions now. 21 of the 143 questions were written to fill those gaps and are not from the original repository; the other 122 are. Worth knowing before you treat every answer as the author's own.
The answer key was sent to the browser with the question. It now arrives only in the reply to your answer, together with the explanation.
The production build failed on two type errors, and every page refresh signed you out because the route guard redirected while the profile request was still in flight. Both fixed.
Still rough
Command answers are compared as text. Case and stray spaces are forgiven, but two ways of writing the same command — --selector=app=nginx against -l app=nginx — are not known to be treated as equal.
Achievements are listed in the interface and never awarded.
Run it yourself
git clone git@github.com:alexff91/learn-kubernetes.git
cd learn-kubernetes/backend && npm install && npm run build && npm start
cd ../frontend && npm install && npm run build
The backend defaults to port 5000, which macOS holds for the AirPlay receiver; change it in backend/.env and in the proxy in frontend/vite.config.ts first.
Source: github.com/alexff91/learn-kubernetes — private, and not linked, because the URL 404s without access. Ask for access.
Vivre ensemble — Luxembourg citizenship test
Open it and start
Everyone sitting the Luxembourg citizenship test reads the same official booklet and then has nowhere to practise. This is the practice: every question carries the reasoning behind the right answer, not just a tick, and there is a mock paper in the real format.
Not official and not affiliated with anyone. The state runs the course and the exam itself, free, including online in English. This is practice alongside that, not a replacement for it.
The questions were written by hand from public material. If one has drifted from the current syllabus, the address in the footer is there for exactly that.
Progress is tied to this browser. Clear its data, or open the site on your phone instead, and you start from nothing.
LuxFlash — Luxembourgish vocabulary
Open it and start
Luxembourgish is required for the Sproochentest and there is very little to study from. A deck, graded by level: what you already know comes back less often, what you forget comes back sooner.
There is no audio, and the Sproochentest is spoken. A silent deck builds vocabulary and cannot prepare you for the oral part on its own.
The vocabulary is community-maintained. Spelling, gender and declensions are worth checking against lod.lu — every card links to it.
Not a course
learn-elastic
Not listed above
A third repository has an Elastic-flavoured name and is not a course: 3 study paths, 0 lessons, 0 questions. It is a study-hours tracker with a title, a hero line and a footer that mention Elastic; nothing in it teaches anything, so it is named here rather than dressed up as a third course.
How these numbers were counted
Run from a clone of each repository. Both scripts parse the seed files and evaluate the array literals, rather than trusting the README.
# KubeLearn
node count_content.js learn-kubernetes
UNITS : 14
LESSONS : 81
QUESTIONS (objects in seedQuestions.ts): 122
lessons with ZERO questions : 10
questions dropped at seed time (lesson_idx out of range): 6
node count_misplaced.js learn-kubernetes
actually seeded : 116
on the lesson they were written for : 41
on a DIFFERENT lesson : 75 (65% of seeded questions)
# Learn Luxembourgish
grep -c "LevelUnit(categoryId:" lib/data/level_data.dart # 32
grep "LevelUnit(categoryId:" lib/data/level_data.dart \
| grep -oE "lessonCount: [0-9]+" | awk -F': ' '{s+=$2} END {print s}' # 202
grep -o "WordModel(" lib/data/vocabulary_data.dart \
lib/data/vocabulary_extended.dart | wc -l # 602
grep -o "ipa: '" lib/data/vocabulary_*.dart | wc -l # 602
grep -o "WordModel(" lib/data/phrases_data.dart | wc -l # 38
grep -o "GrammarExercise(" lib/data/grammar_*.dart | wc -l # 25
# learn-elastic
grep -c "targetHours:" src/main.js # 3
grep -riE "lesson|quiz|question|answer|exercise" src index.html | wc -l # 0