GO • DOCKER SDK • WEBSOCKETS • LINUX LABS

Read the lesson. Then prove it in a real terminal beside it.

An in-house cybersecurity LMS for a school's own students: every lesson has a real, disposable Linux environment at its side. Read the concept, then actually use it — locked, verified, and made airtight to run safely on one shared production box.

GODOCKER SDKWEBSOCKETSGITHUB ACTIONSDOKPLOYSQLITE / SQLC
SESSION / 8F2A● ACTIVE
practice-session / bash LIVE SIMULATION
01020304050607080910111213
$ ./lmsctl start --exercise linux-basics-1
  → publish: 127.0.0.1:5000/linux-basics-1
  → creating disposable container ...... ok
  → network mode: none
  → limits: 256MiB · 1 CPU · 64 PIDs
  → websocket bridge ................... connected
 
$ find /opt/exercise -name "flag.txt"
/opt/exercise/.hidden/flag.txt
$ cat /opt/exercise/.hidden/flag.txt
CYCLONE{read_then_prove}
 
✓ flag verified · progress recorded · cleanup complete
Lesson loaded
Practice container ready
WebSocket connected
Flag verified
Progress recorded
1 loginguards the whole site — pages and practice sessions
256 MiBmemory ceiling, per practice container
64max processes per container
2concurrent sessions — matched to a 2-core host
nonenetwork access per container
20 minidle timeout before cleanup

One learning loop,
two connected systems.

The read side and the practice side sit beside each other, joined by a live pipe. Log in once, read the concept, then flip to a real shell and prove it — the platform keeps the two connected without sharing state between independent exercises.

ISOLATED EXECUTION

A real shell, with deliberate boundaries.

The Go server creates a fresh Docker container, attaches a browser terminal through WebSockets, then stops and removes the environment when the session ends.

Cybersecurity concepts
are not enough.

Public platforms like HTB and TryHackMe prove the format — but a school handing real students shells needs it in-house: its own curriculum, its own progress data, no per-seat licensing. And generally, reading about commands is different from using them; learners need a safe way to experiment with real shell environments without access to the host machine, session interference, or abandoned containers.

01

Passive learning

does not create operational confidence.

02

Shared environments

create state leakage between learners.

03

Unbounded containers

can exhaust host CPU and memory.

From lesson page to
isolated practice session.

Click a node to inspect its responsibility. Login sits in front of everything; every layer after it is a deliberate boundary — least privilege, ephemeral lifecycles, resource bounds — so a shell-spawning app can run beside four other production apps on one host.

Application logic Least-privilege boundary Runtime & storage
SELECTED COMPONENT / SERVER

Go lms-server

Owns routing, validation, sessions, and container lifecycle orchestration.

01Start Practice
02Acquire slot
03Create container
04Attach shell
05Verify flag
06Record progress

Every measure exists
because we thought about it.

Nothing here is best-effort. Each control answers one question — what happens on a public box if this goes wrong? A shell-spawning app earns the right to sit beside four other production apps through least privilege, not good intentions.

WITHOUT CONTROLS
Unlimited sessions
Container network access
Orphaned containers
WITH CONTROLS
Maximum concurrent sessions
NetworkMode: none
Automatic cleanup + shutdown hooks
containerPrivileged: falseblocked by default
resourcesMemory: 256 MiB · NanoCPUs: 1 · PidsLimit: 64bounded
lifecycleIdle timeout · stop · remove · shutdown cleanupowned

Production came for it —
and the design held.

The nightly Docker Cleanup on the shared box deletes any image nothing currently references. Practice containers are disposable by design — so the exercise images were unanchored, and sessions briefly hit No such image. Because images were already published to the local registry as permanent spares, the daemon pulls them back on demand: a pruned copy costs one extra pull, never a broken session.

before
⚠ create error: No such image: linux-basics-1:latest
after
✓ created container → session ended → cleanup complete

Reading becomes
operational confidence.

Implementation outcomes, not invented metrics. A school's students read a lesson and immediately prove it in a real shell — safely, on the box the school already pays for.

1 loginguards everything, from lesson pages to live sessions
256 MiBper practice container memory limit
2concurrent sessions — matched to the 2-core host
20 mindefault idle timeout
REAL OUTCOMES
Real browser-based Linux practice, beside every lesson
In-house curriculum with in-house progress data — no per-seat licensing
Fresh state for independent exercises
Automatic cleanup of temporary environments
Curriculum connected to verification
Airtight boundaries: no root, no network, least-privilege socket
Self-healing image storage (registry spare)
Practical Docker + Dokploy deployment path
Clear SQLite → PostgreSQL migration path

Follow one
practice session.

Move through the lifecycle to see what the application is doing at each boundary. The terminal above is a clearly labeled simulation of the flow.

CURRENT EVENT / 08

Server rate-limits and validates submission.

constant-time compare · accepted
$ sessionctl verify --constant-time
→ event acknowledged · next boundary ready

How a lesson becomes a disposable Linux lab in the browser.

Architecture walkthrough by Cynthia Wahome

Watch walkthrough
00:00 Read the lesson01:15 Start Practice02:40 Live shell, side by side04:08 Flag verified

Small system,
deliberate choices.

Every choice keeps the current learning loop understandable while leaving a clear path for the next constraint.

01Login gate in front of everything

A public demo box invites unauthorized access. One username/password guards every route — pages, assets, the practice WebSocket — compared via hashed constant-time compare, behind a TLS-terminating proxy.

02Docker socket proxy — the clerk

A shared box means the app must never hold the real socket. The clerk forwards only container create/start/stop/remove — images, volumes, secrets, and Swarm stay unreachable.

03Ephemeral everything

Containers are created fresh per session and removed afterward — nothing persists, nothing leaks between students, nothing lingers to be exploited.

04Concurrency matched to measured cores

Code default is 5 sessions; production runs 2 because the host actually has 2 CPU cores.

05Go Docker SDK

The application needed to create, attach to, stop, and remove containers programmatically.

06Local registry + Dokploy pipeline

Same build → tag → push → verify → auto-deploy path the other four apps on this host already use.

07SQLite + sqlc

Local setup stayed lightweight while a typed data layer keeps a future PostgreSQL move bounded.

08Server-rendered HTML

Learning pages are content-focused and do not require a large frontend framework.

09In-memory rate limiting

Appropriate for demo-scale protection, with a clear path to shared storage later.

010Go standard library HTTP server

Routing and rendering needs were small enough to avoid unnecessary framework complexity.

011Single Docker host

The project proves the learning loop before introducing multi-host orchestration.

Honest about
the path forward.

A focused single-host system is the right starting point. These are deliberate next steps, not promises hidden behind a polished interface.

01Full accounts and roles
02Shared rate limits with Redis
03SQLite → PostgreSQL
04Stronger sandboxing boundary
05Broader integration tests
06Instructor analytics
07Exercise authoring tools
08Multi-host scheduling when needed