{
"$type": "site.standard.document",
"content": "This is one of those \"surely somebody already built this\" projects that, somehow, did not exist.\n\n## The problem\n\nI work in security, specifically in a SOC. A lot of my time is spent on night shifts staring at Wazuh alerts and following suspicious traffic patterns.\n\nIn security, you need practice targets. You cannot test skills on production systems, so most of us run intentionally vulnerable apps in homelabs: DVWA, WebGoat, Juice Shop, and the usual suspects.\n\nThe pain starts right there.\n\nI kept hearing the same questions from coworkers:\n\n- How do I set up DVWA again?\n- Which port is this one running on?\n- Why is Docker complaining about conflicts?\n- Where is the compose file?\n- Wait, do I have to set up MySQL first?\n\nThese are skilled security engineers. People who can analyze weird memory bugs and spot dangerous logic flaws. But local Docker plumbing still slows everyone down.\n\nIt felt like something we should be able to simplify.\n\n## The idea\n\nOne boring night shift, a thought popped up:\n\nWhat if vulnerable lab setup felt like `npm install`?\n\n```bash\nvuln-pkg run dvwa\n# That's it. DVWA is now at http://dvwa.127.0.0.1.sslip.io\n```\n\nNo compose files. No manual port mapping. No DNS setup. Just run a command.\n\nThat is where the name came from: `vuln-pkg`. A package manager for vulnerable apps. Basically npm, but for your homelab.\n\n## Making it zero-config\n\nThe messiest part of running many local apps is usually ports and naming.\n\nYou end up with a mental map like this:\n\n- `localhost:8080` -> DVWA\n- `localhost:8081` -> WebGoat\n- `localhost:3000` -> Juice Shop\n- `localhost:????` -> no idea anymore\n\nI wanted cleaner URLs. Something like:\n\n- `http://dvwa.127.0.0.1.sslip.io`\n- `http://webgoat.127.0.0.1.sslip.io`\n\nwithout touching `/etc/hosts` or running local DNS services.\n\nThat is where [sslip.io](https://sslip.io) helped a lot. It resolves hostnames that contain an IP address:\n\n- `dvwa.127.0.0.1.sslip.io` -> `127.0.0.1`\n- `webgoat.192.168.1.50.sslip.io` -> `192.168.1.50`\n\nNo machine-level DNS setup needed. This was the piece that made the rest click.\n\n## Traefik does the routing\n\nOnce clean hostnames are in place, you need a reverse proxy. I used Traefik because it reads Docker labels and configures routes automatically.\n\nWhen you run `vuln-pkg run dvwa`, the flow is:\n\n1. Pull image if needed.\n2. Ensure `vuln-pkg` Docker network exists.\n3. Start Traefik (if not already running).\n4. Start the app container with the right labels.\n5. Print the URL.\n\nExample output:\n\n```text\n[*] Ensuring vuln-pkg network exists\n[*] Starting Traefik reverse proxy\n[+] Traefik running (dashboard: http://traefik.127.0.0.1.sslip.io)\n[*] Creating container for dvwa\n[+] Started dvwa\n\n -> http://dvwa.127.0.0.1.sslip.io\n```\n\nNeed multiple labs at once?\n\n```bash\nvuln-pkg run dvwa\nvuln-pkg run webgoat\nvuln-pkg run juice-shop\n```\n\nEverything stays on clean subdomains with no port collisions.\n\n## Custom manifests\n\nThe default manifest includes common training apps (DVWA, WebGoat, Juice Shop, bWAPP, and others), but I also wanted people to define their own labs.\n\nYou can create a YAML manifest like this:\n\n```yaml\nmeta:\n author: \"Your Name\"\n description: \"Custom training labs\"\n\napps:\n - name: sqli-lab\n version: \"1.0\"\n type: dockerfile\n dockerfile: |\n FROM php:8.0-apache\n RUN docker-php-ext-install mysqli\n COPY <<EOF /var/www/html/index.php\n <?php\n $conn = new mysqli(\"db\", \"root\", \"root\", \"vuln\");\n $id = $_GET['id'];\n $result = $conn->query(\"SELECT * FROM users WHERE id = $id\");\n ?>\n EOF\n ports: [80]\n description: \"Simple SQL injection lab\"\n```\n\nRun with:\n\n```bash\nvuln-pkg --manifest-url file://./my-labs.yml run sqli-lab\n```\n\nYou can also use Git-based sources:\n\n```yaml\n- name: dvwa-custom\n version: \"1.0\"\n type: git\n repo: https://github.com/digininja/DVWA.git\n ref: master\n ports: [80]\n```\n\nThen pull updates with:\n\n```bash\nvuln-pkg rebuild dvwa-custom\n```\n\n## Why Rust?\n\nI wrote it in Rust. Not because I had to, but because I wanted to.\n\nThe binary is self-contained, and the only real runtime dependency is Docker. It cross-compiles nicely for Linux, macOS, and Windows. The install script handles platform detection.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/neutrino2211/vuln-pkg/main/install.sh | bash\n```\n\nCLI commands are intentionally simple:\n\n```text\nvuln-pkg list # Available apps\nvuln-pkg search sqli # Filter apps by keyword/tag\nvuln-pkg run dvwa # Start app\nvuln-pkg status # Show running apps\nvuln-pkg stop dvwa # Stop app\nvuln-pkg remove --purge # Remove everything\n```\n\n## Closing thoughts\n\n`vuln-pkg` came from repeatedly watching people burn time on setup friction that had nothing to do with learning security.\n\nIt is not some revolutionary invention. It is mostly Traefik + sslip.io + a cleaner CLI experience. But sometimes that is exactly what makes a tool useful.\n\nIf you run training labs, host CTF exercises, or just want quick vulnerable targets without Docker busywork, give it a shot:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/neutrino2211/vuln-pkg/main/install.sh | bash\nvuln-pkg run dvwa\n```\n\nRepository: [neutrino2211/vuln-pkg](https://github.com/neutrino2211/vuln-pkg)",
"description": "How watching teammates wrestle with Docker inspired a one-command way to run vulnerable labs.",
"path": "/posts/vuln-pkg-the-npm-for-your-homelab",
"publishedAt": "2026-03-12T06:00:00.000Z",
"site": "https://blog.mainasara.dev",
"tags": [
"rust",
"security",
"docker",
"projects"
],
"textContent": "This is one of those \"surely somebody already built this\" projects that, somehow, did not exist.\n\n## The problem\n\nI work in security, specifically in a SOC. A lot of my time is spent on night shifts staring at Wazuh alerts and following suspicious traffic patterns.\n\nIn security, you need practice targets. You cannot test skills on production systems, so most of us run intentionally vulnerable apps in homelabs: DVWA, WebGoat, Juice Shop, and the usual suspects.\n\nThe pain starts right there.\n\nI kept hearing the same questions from coworkers:\n\n- How do I set up DVWA again?\n- Which port is this one running on?\n- Why is Docker complaining about conflicts?\n- Where is the compose file?\n- Wait, do I have to set up MySQL first?\n\nThese are skilled security engineers. People who can analyze weird memory bugs and spot dangerous logic flaws. But local Docker plumbing still slows everyone down.\n\nIt felt like something we should be able to simplify.\n\n## The idea\n\nOne boring night shift, a thought popped up:\n\nWhat if vulnerable lab setup felt like `npm install`?\n\n```bash\nvuln-pkg run dvwa\n# That's it. DVWA is now at http://dvwa.127.0.0.1.sslip.io\n```\n\nNo compose files. No manual port mapping. No DNS setup. Just run a command.\n\nThat is where the name came from: `vuln-pkg`. A package manager for vulnerable apps. Basically npm, but for your homelab.\n\n## Making it zero-config\n\nThe messiest part of running many local apps is usually ports and naming.\n\nYou end up with a mental map like this:\n\n- `localhost:8080` -> DVWA\n- `localhost:8081` -> WebGoat\n- `localhost:3000` -> Juice Shop\n- `localhost:????` -> no idea anymore\n\nI wanted cleaner URLs. Something like:\n\n- `http://dvwa.127.0.0.1.sslip.io`\n- `http://webgoat.127.0.0.1.sslip.io`\n\nwithout touching `/etc/hosts` or running local DNS services.\n\nThat is where [sslip.io](https://sslip.io) helped a lot. It resolves hostnames that contain an IP address:\n\n- `dvwa.127.0.0.1.sslip.io` -> `127.0.0.1`\n- `webgoat.192.168.1.50.sslip.io` -> `192.168.1.50`\n\nNo machine-level DNS setup needed. This was the piece that made the rest click.\n\n## Traefik does the routing\n\nOnce clean hostnames are in place, you need a reverse proxy. I used Traefik because it reads Docker labels and configures routes automatically.\n\nWhen you run `vuln-pkg run dvwa`, the flow is:\n\n1. Pull image if needed.\n2. Ensure `vuln-pkg` Docker network exists.\n3. Start Traefik (if not already running).\n4. Start the app container with the right labels.\n5. Print the URL.\n\nExample output:\n\n```text\n[*] Ensuring vuln-pkg network exists\n[*] Starting Traefik reverse proxy\n[+] Traefik running (dashboard: http://traefik.127.0.0.1.sslip.io)\n[*] Creating container for dvwa\n[+] Started dvwa\n\n -> http://dvwa.127.0.0.1.sslip.io\n```\n\nNeed multiple labs at once?\n\n```bash\nvuln-pkg run dvwa\nvuln-pkg run webgoat\nvuln-pkg run juice-shop\n```\n\nEverything stays on clean subdomains with no port collisions.\n\n## Custom manifests\n\nThe default manifest includes common training apps (DVWA, WebGoat, Juice Shop, bWAPP, and others), but I also wanted people to define their own labs.\n\nYou can create a YAML manifest like this:\n\n```yaml\nmeta:\n author: \"Your Name\"\n description: \"Custom training labs\"\n\napps:\n - name: sqli-lab\n version: \"1.0\"\n type: dockerfile\n dockerfile: |\n FROM php:8.0-apache\n RUN docker-php-ext-install mysqli\n COPY <<EOF /var/www/html/index.php\n <?php\n $conn = new mysqli(\"db\", \"root\", \"root\", \"vuln\");\n $id = $_GET['id'];\n $result = $conn->query(\"SELECT * FROM users WHERE id = $id\");\n ?>\n EOF\n ports: [80]\n description: \"Simple SQL injection lab\"\n```\n\nRun with:\n\n```bash\nvuln-pkg --manifest-url file://./my-labs.yml run sqli-lab\n```\n\nYou can also use Git-based sources:\n\n```yaml\n- name: dvwa-custom\n version: \"1.0\"\n type: git\n repo: https://github.com/digininja/DVWA.git\n ref: master\n ports: [80]\n```\n\nThen pull updates with:\n\n```bash\nvuln-pkg rebuild dvwa-custom\n```\n\n## Why Rust?\n\nI wrote it in Rust. Not because I had to, but because I wanted to.\n\nThe binary is self-contained, and the only real runtime dependency is Docker. It cross-compiles nicely for Linux, macOS, and Windows. The install script handles platform detection.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/neutrino2211/vuln-pkg/main/install.sh | bash\n```\n\nCLI commands are intentionally simple:\n\n```text\nvuln-pkg list # Available apps\nvuln-pkg search sqli # Filter apps by keyword/tag\nvuln-pkg run dvwa # Start app\nvuln-pkg status # Show running apps\nvuln-pkg stop dvwa # Stop app\nvuln-pkg remove --purge # Remove everything\n```\n\n## Closing thoughts\n\n`vuln-pkg` came from repeatedly watching people burn time on setup friction that had nothing to do with learning security.\n\nIt is not some revolutionary invention. It is mostly Traefik + sslip.io + a cleaner CLI experience. But sometimes that is exactly what makes a tool useful.\n\nIf you run training labs, host CTF exercises, or just want quick vulnerable targets without Docker busywork, give it a shot:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/neutrino2211/vuln-pkg/main/install.sh | bash\nvuln-pkg run dvwa\n```\n\nRepository: [neutrino2211/vuln-pkg](https://github.com/neutrino2211/vuln-pkg)",
"title": "vuln-pkg: The Boredom-Fueled Package Manager for Your Homelab"
}