fastapi · react · docker · my main project

d's labs

browser lab environments, served from a server at home.

d's labs gives you a linux desktop, a security target or a data-engineering stack in a browser tab, isolated from everyone else's. i build it and i run it. anyone can sign up.

open d's labs ↗


what you can do in it

labs
40 one-click environments: kali, ubuntu desktop and terminal, jenkins, gitlab, gitea, airflow, kafka, nifi, keycloak, minio, n8n, grafana, prometheus, jupyter, juice shop, dvwa, webgoat, metasploitable and more. or build your own stack from a palette; it becomes one compose project.
skills
guided, timed courses in disposable containers. a "check my progress" engine verifies the work inside the container, not a quiz about it. 9 tracks and 6 timed challenges.
ctf
timed events with flags, dynamic scoring, first-blood bonuses, hints, teams and a scoreboard.
ranges
multi-machine attack scenarios on networks with no internet: a dmz host, an internal app and database, and a samba active directory domain controller. the internal segment is only reachable through the dmz host, so the pivot is real.

also there: an ssh gateway for terminal labs, managed mysql, postgresql, mongodb, cockroachdb, redis and rabbitmq, an optional ai mentor that reads and proposes but never changes anything, and an mcp server with 50 tools so claude, cursor or vs code can drive the platform.

start a pretend lab

press the button and step through what happens when you start a lab. the steps are the real ones; the log lines are written for this page, not copied from production.

  1. request accepted

    the api checks your session and your limits, records the lab as starting, and returns 202 accepted straight away. nothing slow happens inside the request.

    POST /labs/kali/start  →  202 accepted  (job queued)
  2. job queued

    the start becomes a job on a redis queue. a separate worker process picks it up. your browser starts polling for readiness.

    worker  picked up start_lab  user=7f3a  lab=kali
  3. lab host woken

    the machine that runs student containers is a separate virtual machine, and it's only switched on while someone has a lab running. if it's asleep, the worker wakes it first.

    labhost  power on requested  →  docker daemon reachable
  4. private network created

    every user gets their own docker network. your containers can see each other and nobody else's.

    network  private bridge for user 7f3a  created
  5. container started

    the lab image starts on your network, capped at 2 cpu and 4 gb of memory, with your volume attached so your files outlive the container.

    container  kali started  cpus=2  memory=4g  volume attached
  6. login gate ready

    the lab gets a random address with a login in front of it. the poll comes back ready and the tab opens. the username and password are on your lab's page.

    GET /labs/kali/ready  →  200 ready  (login gate up)

how it's built

architecture
  browser
     │  rest + websocket
  ┌──┴───────────────────────────────────────┐
  │ frontend   react 19 · typescript · vite  │
  └──┬───────────────────────────────────────┘
  ┌──┴───────────────────────────────────────┐
  │ api        fastapi, fully async          │
  │            auth · labs · skills · ctf    │
  │            ranges · services · mcp       │
  └──┬──────────────┬──────────────┬─────────┘
  mongodb        redis          docker
  accounts,      cache, locks,  ├─ labs
  progress       pub/sub,       ├─ services
                 job queue      ├─ skills
                    │           └─ ranges
  ┌─────────────────┴────────────────────────┐
  │ worker     provisioning · snapshots ·    │
  │            cleanup · nightly backup      │
  ├──────────────────────────────────────────┤
  │ ssh gateway   one port, docker exec      │
  └──────────────────────────────────────────┘
  admin console: a separate server, not shown

stack: fastapi, react 19, typescript, mongodb, redis, an arq worker, traefik and docker.

decisions i'd defend

the admin console is a different server
accounts, groups and sessions are managed from a separate console with its own operators. the public api has no admin endpoints at all, so breaking into it gives you no administrative power.
slow work never blocks a request
starting a lab, taking a snapshot and cleaning up all run in the worker. the api answers 202 at once and the frontend polls. if a worker crashes mid-job, a reconciler expires the abandoned claim.
no lab runs sshd
the ssh gateway is the only ssh implementation. it docker execs into your lab as your own account, never as root. one authentication boundary instead of one per image.
the frontend trusts no storage
the access token lives in memory only. refresh goes through an httponly cookie, with one refresh in flight at a time. nothing sensitive sits in localstorage.
labs and skills are separate systems
a skill is disposable and is purged when its timer ends. a lab lives until you stop it. different collections, different cleanup paths, so one can't eat the other's data.

it runs on a server at home

there's no cloud account behind d's labs. it runs on one machine in my family's home, two thousand kilometres from where i study. the public front door is always on. the lab host only wakes when someone needs it. when the power goes, the site says so and comes back by itself.

how i built that box →

plans

there's a free plan and paid plans. the current ones are on d's labs itself; i'd rather not copy prices here and let them go stale.