A modern, self-contained learning management system.
Courses, quizzes, SCORM, progress tracking, analytics and user management, built with Next.js (App Router), TypeScript, Tailwind CSS and SQLite. No external services required: the database is a local file, created and seeded automatically on first run.
| Course catalogue | Lesson player |
|---|---|
![]() |
![]() |
| Course builder | Analytics and reports |
![]() |
![]() |
Running a small course library usually means choosing between a hosted LMS — priced per active user, with your content in someone else's database — and one of the self-hosted veterans, which want a web server, a database server, a PHP runtime and a weekend to configure.
Lumina is one process and one SQLite file. npm install && npm run dev gives
you a working LMS with demo content, and the Docker image needs nothing beside
it. Back up one directory and you have backed up everything: the database, the
extracted SCORM packages and the uploaded images.
It is batteries-included on purpose — courses, quizzes, SCORM, progress, analytics and user management are all in the box, with no plugins to hunt down.
- Course catalogue — browse published courses with search and category filters. Each course page shows the full content outline, instructor and estimated time before you enrol.
- Distraction-free lesson player — rich markdown lessons with code blocks, optional YouTube and Vimeo embeds, a course sidebar with completion ticks, mark-complete tracking, and previous/next navigation.
- Auto-graded quizzes — multiple-choice checks per module with pass thresholds, per-question feedback and attempt history. Correct answers never leave the server before submission.
- Personal dashboard — enrolled courses with progress bars, completion stats, and a "pick up where you left off" flow.
- Course builder — create courses, structure them into modules, and add lessons and quizzes inline. Publish and unpublish with one click and view per-student progress. Course banners can be a solid colour or an uploaded image.
- Markdown lesson editor — a formatting toolbar plus a live Preview tab that renders exactly like the lesson player, so what you write is what students see.
- SCORM support — upload SCORM 1.2 or SCORM 2004 zip packages (Articulate, Captivate, iSpring and others) as lessons. The built-in runtime exposes the SCORM JavaScript API, persists the CMI data model (completion, score, suspend data) for resume, and mirrors completion into normal lesson progress.
- SCORM library — upload a package once, attach it to any number of courses, see per-package usage, and safely delete unused packages. Deletion is blocked while lessons still reference a package.
- Course review schedules — give each course a review period (3, 6, 12 or 24 months). A staff review dashboard flags what is overdue or due soon, tracks last-reviewed and last-updated timestamps, and offers one-click "Mark reviewed".
- Enrolment policies — courses are either open to self-enrolment or "by allocation only": staff assign individual students or whole account groups, and allocation-only courses hide the enrol button behind an explanatory note.
- Recycle bin — deleting a course soft-deletes it. Content and student progress are preserved, and courses can be restored or permanently purged later.
- Staff preview — instructors and admins never enrol: every course on the platform is viewable directly in a clearly marked preview mode, while managing stays limited to the owning instructor or an admin.
- User management — create users, change roles, enable and disable accounts (live sessions end immediately), reset passwords, and organise users into account groups for bulk course allocation.
- Analytics and reports — platform activity at a glance: active users, logins, lesson views, enrolments, completions, quiz pass rates, trend charts, and per-course engagement — each downloadable as CSV.
- Auth and roles — email and password accounts with HMAC-signed cookie sessions; student, instructor and admin roles enforced server-side.
- Single-file database — SQLite via
better-sqlite3, with schema migration and demo seeding handled automatically on first request. - No client-heavy stack — server components fetch data directly; small client components handle interactivity; a dependency-free markdown renderer displays lesson content.
The published image means you can deploy on any server with Docker — no source
checkout, no local build. You need just two files side by side: compose.yaml
and a .env holding your SESSION_SECRET.
curl -fsSL https://raw.githubusercontent.com/authorTom/lumina-lms/main/compose.yaml -o compose.yaml
printf 'SESSION_SECRET=%s\n' "$(openssl rand -base64 32)" > .env
docker compose pull
docker compose up -dOpen http://localhost:3000 and sign in with a
demo account. Run docker compose ps — the container shows
healthy once it is ready. The database is created and seeded on first request.
Deploying through a UI such as Dockge or Portainer? Paste the contents
of compose.yaml into the stack editor and add SESSION_SECRET in the stack's
environment field. The stack pulls ghcr.io/authortom/lumina-lms:latest
directly — nothing is built on the server.
Updating:
docker compose pull && docker compose up -dTo build the image locally rather than pull it, comment out the image: line in
compose.yaml, uncomment the build: block, then run from a full checkout:
cp .env.example .env # then set SESSION_SECRET in .env
docker compose up -d --buildOr with plain Docker:
docker build -t lumina-lms .
docker run -d --name lumina \
-p 3000:3000 \
-e SESSION_SECRET="$(openssl rand -base64 32)" \
-v lumina-data:/app/data \
lumina-lmsThe app ships as a self-contained image built from Next.js
standalone output:
a multi-stage build that compiles the native SQLite module, then copies only the
runtime server, static assets and public/ into a slim Debian base running as
an unprivileged user.
npm install
npm run devOpen http://localhost:3000. The SQLite database (data/lms.db) is created and
seeded with demo courses and users on first request.
For production: npm run build && npm start.
All demo accounts use the password password123:
| Role | |
|---|---|
student@lms.dev |
Student (enrolled in two courses with some progress) |
sarah@lms.dev |
Instructor (owns two courses) |
james@lms.dev |
Instructor (owns two courses) |
admin@lms.dev |
Admin |
| Variable | Default | What it does |
|---|---|---|
SESSION_SECRET |
— | Signs session cookies. Required in production |
PORT |
3000 |
Port the server listens on |
SESSION_SECRET is required. In production (NODE_ENV=production) the app
refuses to sign sessions without it — the container starts, but logging in
throws. Generate one with openssl rand -base64 32, and keep the value stable
across deploys, or all existing sessions are invalidated.
Uploads are capped at 200 MB (SCORM) via the server action body-size limit. Put a reverse proxy in front for TLS and its own request-size limits in production.
Everything else — courses, users, roles, review schedules, enrolment policies — is managed inside the app.
lib/db.ts— SQLite connection, schema migration, and first-run seedinglib/auth.ts— HMAC-signed cookie sessions and role guardslib/data.ts— typed read querieslib/actions.ts— server actions for every mutation (auth, enrolment, progress, quiz grading, authoring, admin)app/— App Router pages; server components fetch data directly, small client components handle interactivitycomponents/— shared UI, including a dependency-free markdown renderer for lesson content
All state — the SQLite database, extracted SCORM packages and uploaded images —
lives under /app/data, exposed as a volume so it survives container restarts
and image upgrades.
Back up the volume, not the container — everything persistent is in
/app/data:
docker run --rm -v lumina-data:/data -v "$PWD":/backup alpine \
tar czf /backup/lumina-backup.tar.gz -C /data .MIT — see LICENSE.







