Skip to main content

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=1g is required on every scan invocation — headless Chromium crashes without it.
  • Default command is worker, so docker run … levoai/levoai-shadownet:stable with 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.

VariablePurpose
LEVOAI_AUTH_KEYAuth key (refresh token) from Settings → API Keys.
LEVOAI_ORG_IDOrganization ID.
LEVOAI_ENV_IDEnvironment 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_KEYLLM key for AI-guided crawling.
HTTPS_PROXY / HTTP_PROXY / NO_PROXYOutbound proxy.
LEVOAI_BASE_URLOptional. 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:

FormUse 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 targetWhen you need itPurpose
/work/levo-dast.ymlYAML-driven scansYour levo-dast.yml (read-only).
/work/session.jsonsession_transplant authLevo extension session export (read-only, credential).
/home/levo/.config/configstoreworker mode or repeated login runsPersists 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

Was this page helpful?