A local dashboard to spin up (and tear down) a full set of deliberately
vulnerable practice labs — one click per lab, live logs, no memorizing
docker run flags.
LabForge wraps 12 well-known vulnerable web/API training apps (OWASP Juice Shop, WebGoat, crAPI, DVWA, and more) behind a single Flask + vanilla JS dashboard. Toggle a lab on, watch it pull/build in real time, click through to it, toggle it off when you're done.
⚠️ These are intentionally vulnerable applications. Only run LabForge on a machine/network you control, never expose these ports to the public internet, and tear labs down when you're not actively using them. This project automates starting known-vulnerable apps for learning purposes — it doesn't harden or sandbox them beyond normal Docker container isolation.
- One-click start/stop per lab, plus "Engage All" / "Abort All" for the whole set.
- Non-blocking — starting one lab (even a slow multi-GB pull) never freezes the dashboard or blocks other labs from starting at the same time.
- Live log streaming per lab, right on its card — see what's actually happening during a pull/build instead of staring at a spinner.
- Live status polling against
docker ps, so the dashboard reflects reality even if you start/stop something from your own terminal. - Info cards for every lab — stack, difficulty, a rough first-run time estimate, and specific things to go practice (e.g. "BOLA", "JWT algorithm confusion", "NoSQL injection").
- Search + filters (by category, or "active only").
- Built to be readable and hackable — it's ~2 files, no build step, no framework lock-in.
| Lab | Stack | Difficulty | Default URL |
|---|---|---|---|
| crAPI | Node.js / Python / Go microservices | Intermediate | localhost:8888 |
| VAmPI | Python / Flask | Beginner | localhost:5000 |
| DVGA (GraphQL) | Python / Flask / GraphQL | Intermediate | localhost:5013 |
| OWASP Juice Shop | Node.js / Angular | Beginner–Advanced | localhost:3000 |
| WrongSecrets | Java / Spring | Intermediate | localhost:8085 |
| DVWA | PHP / MySQL | Beginner | localhost:8081 |
| WebGoat + WebWolf | Java / Spring Boot | Beginner–Intermediate | localhost:8082/WebGoat |
| NodeGoat | Node.js / Express / MongoDB | Intermediate | localhost:4000 |
| Mutillidae II | PHP / MySQL | Beginner | localhost:8084/mutillidae |
| bWAPP | PHP / MySQL | Beginner–Intermediate | localhost:8086/install.php |
| XVWA | PHP / MySQL | Intermediate | localhost:8087/xvwa |
| RailsGoat | Ruby on Rails | Intermediate | localhost:3001 |
- Docker (running, with enough disk space — expect 15-20 GB total if you pull everything)
- Python 3.9+
- ~2 GB+ free RAM to run more than a couple of labs concurrently
git clone <your-repo-url> labforge
cd labforge
pip install -r requirements.txt --break-system-packages # drop the flag if using a venv
python3 app.pyOpen http://localhost:7070.
First time using a lab, run install for the ones that need a git clone (crAPI, VAmPI, DVGA, NodeGoat, RailsGoat) — everything else pulls a prebuilt image directly:
./setup_all_labs.sh install allThen just use the dashboard toggles, or drive it from the CLI directly:
./setup_all_labs.sh start <lab> # or 'all'
./setup_all_labs.sh stop <lab> # or 'all'
./setup_all_labs.sh status
./setup_all_labs.sh clean # nuke all containers + cloned reposapp.py is a thin Flask layer over setup_all_labs.sh — every button in
the UI just calls the same script you could run from a terminal. The one
thing worth knowing about the implementation:
Every start/stop runs in a background thread and returns immediately.
Early versions of this ran subprocess.run() synchronously and made the
HTTP request wait for the whole docker pull/build to finish — for
multi-GB pulls that's minutes, and since Flask's dev server isn't
threaded by default, the entire app froze for that whole window: other
lab toggles, the status poll, everything queued up behind it. Now each
action spawns a thread, the server runs threaded=True, and the
frontend polls a small /api/job/<lab> endpoint for live output. You can
start several labs at once and the UI stays responsive throughout.
A handful of these labs have upstream setup quirks that aren't obvious until something silently doesn't work. LabForge already works around all of these — noting them here in case you extend the script further or hit something new:
- "Engage All" used to silently skip labs after the first failure —
the script runs under
set -e, and calling each lab's start function bare meant one failure killed the whole batch. Every call in theallsequence is now individually guarded. - crAPI's
docker-compose.ymllives underdeploy/docker/, not the repo root — running compose from the wrong directory fails withno configuration file provided. - DVGA binds to
127.0.0.1inside its container by default — needs-e WEB_HOST=0.0.0.0or Docker's port-forward has nothing to reach, giving "connection reset." - RailsGoat needs a database setup step (
rails db:prepare/db:setup) between build and up, or the Rails process crashes immediately on boot from a missing schema. Its compose file also publishes to host port 3000, which collides with Juice Shop — this gets remapped to 3001 automatically, even for a repo cloned before this fix. - Old/interrupted clones used to get stuck forever — install steps only checked whether the target folder existed, so a broken partial clone would report "already cloned, skip" on every future run. They now check for the actual file each lab needs and re-clone if it's missing.
- XVWA's upstream repo has no
docker-compose.ymlat all (manual LAMP setup only), and the first replacement image tried (tuxotron/xvwa) turned out to be built in a Docker image format modern Docker refuses to pull. It now usesbrightsec/xvwa, a same-source rebuild in a current format. It'slinux/amd64-only, so it runs under emulation (slower) on Apple Silicon. - Security Shepherd was removed. Its image doesn't auto-launch its own services and proved unreliable even after scripting the documented manual startup sequence. If you want it, OWASP's official setup builds it from source via Maven — see the SecurityShepherd wiki.
If a lab still won't behave, check its container's actual logs before assuming the dashboard is at fault:
docker ps -a --filter "name=<container>"
docker logs --tail 80 <container>labforge/
├── app.py # Flask backend + background job engine
├── setup_all_labs.sh # does the actual docker/git work, callable standalone
├── requirements.txt
└── templates/
└── index.html # the whole frontend (HTML/CSS/JS, no build step)
Issues and PRs welcome — especially if an upstream lab image changes
again and something in here breaks. If you add a lab, please match the
existing pattern: a start_<lab>/stop_<lab> pair in
setup_all_labs.sh, a matching entry in LABS in app.py, and a guard
in the all sequence.
MIT — see LICENSE.
LabForge just orchestrates other people's excellent work. All credit for the actual vulnerable applications goes to their respective maintainers: OWASP (crAPI, Juice Shop, WebGoat, NodeGoat, Mutillidae II, RailsGoat, Security Shepherd), erev0s (VAmPI), dolevf (DVGA), and the WrongSecrets, DVWA, bWAPP, and XVWA projects. Go star their repos too.
