Docker & Podman reference
Container-runtime operations for the DAST scanner: worker mode, volume mounts, networking, and environment variables. Examples use docker, but every command works with podman — see Podman below for the small differences.
For your first scan, see Scan from the CLI — this page assumes you already have the image pulled and have logged in.
Image
docker pull levoai/levoai-shadownet:stable
--shm-size=1gis required on everyscaninvocation — headless Chromium crashes without it.- Default command is
worker, sodocker run … levoai/levoai-shadownet:stablewith no command starts worker mode.
Worker mode
A long-lived container that picks up scan jobs scheduled or triggered from the Levo dashboard. Use this when you want scans initiated from the UI (or on a schedule) rather than the CLI.
Basic worker
docker run -d \
--name levoai-shadownet-worker \
--shm-size=1g \
-e LEVOAI_AUTH_KEY=<your-auth-key> \
-e LEVOAI_ORG_ID=<your-org-id> \
--restart unless-stopped \
levoai/levoai-shadownet:stable
Scheduled-only worker
Runs exclusively the scans marked "Scheduled" in the dashboard:
docker run -d \
--name levoai-shadownet-scheduled \
--shm-size=1g \
-e LEVOAI_AUTH_KEY=<your-auth-key> \
-e LEVOAI_ORG_ID=<your-org-id> \
--restart unless-stopped \
levoai/levoai-shadownet:stable \
worker --key <your-auth-key> --organization <your-org-id> --scheduled
Environment variables
Pass these with -e on every docker run (or export them and use the bare -e VAR form). Full list in Configuration → Environment variables.
| Variable | Purpose |
|---|---|
LEVOAI_AUTH_KEY | Auth key (refresh token) from Settings → API Keys. |
LEVOAI_ORG_ID | Organization ID. |
LEVOAI_ENV_ID | Environment UUID. Alternative: pass --env-name <env_name> to shadownet login once (see Persisted login below) — the env-id is resolved server-side and cached in the configstore. |
ANTHROPIC_API_KEY / OPENAI_API_KEY | LLM key for AI-guided crawling. |
HTTPS_PROXY / HTTP_PROXY / NO_PROXY | Outbound proxy. |
LEVOAI_BASE_URL | Optional. Override the Levo API endpoint — defaults to https://api.levo.ai. Only needed on regional / dev / on-prem deployments. |
Env vars and CLI flags are interchangeable for Levo platform values (e.g. LEVOAI_ENV_ID ≡ --env-id); flags win when both are set.
Persisted login pattern
Instead of passing LEVOAI_AUTH_KEY / LEVOAI_ORG_ID on every docker run, log in once and mount a configstore volume for subsequent scans:
# Once — caches auth tokens + selected env-id under ~/.levo-docker-home
docker run --rm \
-v "$HOME/.levo-docker-home:/home/levo/.config/configstore" \
levoai/levoai-shadownet:stable \
login -k <auth-key> -o <org-id> --env-name <env_name>
Three ways to bind the environment at login time — pick whichever you have at hand:
| Form | Use when |
|---|---|
login -k <auth-key> -o <org-id> --env-name <env_name> | You know the env's name. Server resolves to UUID, validates, caches. |
login -k <auth-key> -o <org-id> --env-id <uuid> | You know the UUID. Same caching behaviour. |
login -k <auth-key> -o <org-id> (no env) | Interactive only. Picks default env from your workspace. |
If your auth key was issued by a non-SaaS deployment (e.g. a regional cloud), add -e LEVOAI_BASE_URL=<your-endpoint> to the login invocation above. The endpoint is cached together with the tokens.
Subsequent scans use the cached env automatically — no LEVOAI_ENV_ID env var needed:
docker run --rm --shm-size=1g \
-v "$HOME/.levo-docker-home:/home/levo/.config/configstore" \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml
Volume mounts
A scan needs up to three host directories / files mounted into the container. Mount only the ones a given scan uses.
| Mount target | When you need it | Purpose |
|---|---|---|
/work/levo-dast.yml | YAML-driven scans | Your levo-dast.yml (read-only). |
/work/session.json | session_transplant auth | Levo extension session export (read-only, credential). |
/home/levo/.config/configstore | worker mode or repeated login runs | Persists the levo login session across container runs. |
Recipe — passive-only scan, no auth
docker run --rm --shm-size=1g \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml
Recipe — form-authenticated scan
docker run --rm --shm-size=1g \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
-e SCAN_USERNAME -e SCAN_PASSWORD \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml --password "$SCAN_PASSWORD"
SCAN_USERNAME reaches the scanner via the ${SCAN_USERNAME} interpolation inside levo-dast.yml. --password is passed as a flag because the YAML loader rejects password: keys.
Recipe — session-transplant (browser session)
Hand the scanner a logged-in session captured by the Levo extension. Two mounts: the YAML and the JSON.
docker run --rm --shm-size=1g \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-v "$PWD/session.json:/work/session.json:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml \
--auth-session-file /work/session.json
session.json is a credential — keep it out of git, mount read-only, rotate when the session expires. See Browser session for capture instructions.
Recipe — API scan with bearer token
docker run --rm --shm-size=1g \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
-e SCAN_TOKEN \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml --token "$SCAN_TOKEN"
Recipe — regional / dev / on-prem deployment
If your auth key is for a Levo deployment other than api.levo.ai, set LEVOAI_BASE_URL once. The pattern is identical otherwise:
docker run --rm --shm-size=1g \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
-e LEVOAI_BASE_URL=https://api.us-east.your-deployment.com \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml
"Target URL domain is not in the list of owned domains"
If the platform rejects scan creation with:
✗ Error: Scan creation rejected by platform: 9 FAILED_PRECONDITION:
Target URL domain 'example.com' is not in the list of owned domains.
Your workspace's owned domains list (Settings → API Discovery) doesn't include the target. Add the domain there, or scan an already-owned domain. This is a platform-side gate; nothing in your levo-dast.yml will bypass it.
Full set of recipes — paired YAML + CLI: see YAML examples & recipes.
Networking
Host network (Linux only)
docker run --rm -it --shm-size=1g --network host \
levoai/levoai-shadownet:stable \
scan https://internal.example.com
Shared Docker network
docker network create mynet
docker run --rm -d --network mynet --name myapp your-app:latest
docker run --rm -it --shm-size=1g --network mynet \
levoai/levoai-shadownet:stable \
scan http://myapp:8080
AI-guided crawling
Pass an LLM key to enable AI crawl mode:
docker run --rm -it --shm-size=1g \
-e ANTHROPIC_API_KEY=<key> \
levoai/levoai-shadownet:stable \
scan https://example.com --crawl-mode ai
Supported providers: ANTHROPIC_API_KEY, OPENAI_API_KEY. See LLM provider precedence if both are set.
Podman
Podman is a drop-in alternative to Docker — the CLI is compatible, so every example on this page works after substituting podman for docker. The differences below come from running rootless and from SELinux-enforcing hosts (RHEL, CentOS Stream, Fedora).
Pull and run
# Use the fully-qualified image to skip Podman's short-name resolution prompt.
podman pull docker.io/levoai/levoai-shadownet:stable
podman run --rm -it --shm-size=1g \
levoai/levoai-shadownet:stable \
scan https://example.com
Volume mounts (rootless)
Rootless Podman maps your host UID to a different UID inside the container. The levo user inside the image (UID 1000) needs to own mounted directories like ~/.config/configstore, or the container will fail to write its session file. Append :U to the mount and Podman chowns the volume to the in-container user automatically:
podman run --rm -it \
-v $HOME/.config/configstore:/home/levo/.config/configstore:U \
levoai/levoai-shadownet:stable \
login -k <your-auth-key> -o <your-org-id>
SELinux relabeling
If your host enforces SELinux and you see permission denied on bind mounts, add :Z (private label) so Podman relabels the directory for the container. Combine flags with a comma:
podman run --rm -it --shm-size=1g \
-v $HOME/.config/configstore:/home/levo/.config/configstore:U,Z \
levoai/levoai-shadownet:stable \
scan https://example.com
Worker mode under systemd
Generate a user-level systemd unit so the worker survives logout:
podman run -d \
--name levoai-shadownet-worker \
--shm-size=1g \
-e LEVOAI_AUTH_KEY=<your-auth-key> \
-e LEVOAI_ORG_ID=<your-org-id> \
--restart unless-stopped \
levoai/levoai-shadownet:stable
podman generate systemd --new --name levoai-shadownet-worker \
> ~/.config/systemd/user/levoai-shadownet-worker.service
systemctl --user enable --now levoai-shadownet-worker.service
loginctl enable-linger "$USER" # keep the unit running after logout
Next
- Scan from the CLI — first-scan walkthrough.
- CLI reference — every flag.
- Configuration — environment variables and advanced settings.
- Kubernetes worker — run worker mode in a cluster.