Repository navigation
Deployment
Complete guide to deploying PentestAI in a local environment.
Suitable for: development only. Docker is the supported runtime; bare metal installs security tools on the host.
PENTESTAI_ALLOW_LOCAL_TOOLS=1 bash install.shWithout that variable, install.sh runs in Docker-only mode and skips host
security tools, Python dependencies, and Node docx.
# 1. Clone or extract the source
cd pentestai/
# 2. Install security tools
sudo bash install.sh
# 3. Configure
cp .env.example .env
nano .env
# → Fill in ANTHROPIC_API_KEY (or another provider)
# 4. Run (from inside the pentestai/ directory)
python3 __main__.py
# Or from the parent directory:
# python3 -m pentestaiPentestAI> llm
PentestAI> tools
PentestAI> scope add 10.0.0.1/32
PentestAI> target 10.0.0.1
PentestAI> recon
Suitable for: isolated environments, reproducibility, team sharing.
- Latest Docker Desktop (tested on 4.96.0, Engine 29.9.1, Compose v5.6.0) or the latest Docker Engine on Linux
- Disk space ≥ 20 GB (image includes Kali tools + Chromium; final image ≈7.8 GB)
cp .env.example .env
nano .env # fill in API keys
# Build (Kali apt + Metasploit/Burp: 10-20 minutes on the first build)
docker build -t pentestai .
docker run -it --rm \
--env-file .env \
-v $(pwd)/sessions:/app/sessions \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/reports/output:/app/reports/output \
pentestaiImage notes:
- Runs as the non-root user
pentester. -
.envis not included in the image —.dockerignoreexcludes all.envfiles. Credentials only enter at runtime via--env-file. - Tools already available: nmap, sqlmap, nikto, gobuster, ffuf, hydra, whatweb, msfconsole, node, python3.
- Headless Chromium (Playwright) is available for JavaScript-heavy pages, authenticated flows, screenshots, and PDF export.
docker run -d \
--name pentestai-api \
--env-file .env \
-p 8080:8080 \
-v $(pwd)/sessions:/app/sessions \
pentestai python3 __main__.py --api --host 0.0.0.0
# Test
curl http://localhost:8080/healthSuitable for: production-like setups, Neo4j + Qdrant + Metasploit.
# 1. Preparation
cp .env.example .env
nano .env
# 2. Build + start all services (including the msf profile)
docker compose --profile msf up -d
# 3. Wait until healthy (30-60 seconds on first start)
docker compose --profile msf ps
# 4. Attach to the running CLI
docker attach pentestai-pentestai-1
# exit without stopping the container: Ctrl-p then Ctrl-qdocker-compose.yml uses two paths for .env:
| Path | Function |
|---|---|
| Compose interpolation |
${ANTHROPIC_API_KEY}, ${NEO4J_PASS}, etc. in the environment: block |
env_file: .env |
Passes all variables (15 LLM providers, PENTESTAI_MODE, REPORT_*, TEST_*) to the pentestai container |
Without env_file, only the keys listed explicitly in environment: reach the container, and providers such as DeepSeek/Gemini/OpenRouter will not be detected.
| Service | Container | Port | Description |
|---|---|---|---|
| pentestai | pentestai-pentestai-1 |
- | Main CLI shell (interactive, detached) |
| neo4j | pentestai-neo4j-1 |
7474, 7687 | Graph database (healthcheck enabled) |
| qdrant | pentestai-qdrant-1 |
6333, 6334 | Vector database |
| msf | pentestai-msf-1 |
55553 | Metasploit RPC (msf profile) |
All services are on the pentestnet bridge network. Internal hostnames: neo4j, qdrant, msf.
The pentestai service does not publish ports. Run a separate API instance:
docker compose run --rm -p 8080:8080 pentestai \
python3 __main__.py --api --host 0.0.0.0
curl http://localhost:8080/health# View status
docker compose --profile msf ps
# Container logs
docker compose logs -f pentestai
docker compose logs msf | grep MSGRPC
# Separate shell inside the container (without disturbing the main CLI)
docker compose exec -T pentestai sh
# Run a single command
docker compose exec -T pentestai python3 -c "from core.llm import build_router_from_env; print(build_router_from_env().status())"
# Stop (data retained)
docker compose --profile msf down
# Stop + remove Neo4j/Qdrant volumes (graph & embedding data is lost)
docker compose --profile msf down -vThe scripts/env_test.py script checks all .env parameters from inside the container without printing secret values (masked):
docker cp scripts/env_test.py pentestai-pentestai-1:/tmp/env_test.py
docker compose exec -T pentestai python3 /tmp/env_test.pyWhat is checked:
- Presence/format of each variable
- Live auth probe of each LLM provider that has a key (HTTP status)
- Neo4j (Basic auth + bolt), Qdrant, Metasploit RPC
-
config.settings—PENTESTAI_MODE→SKILL_MODE,REPORT_* - Binary tools in the image
-
TEST_LOGIN_URL: DNS + TCP only (never sends credentials)
Browser availability (should print True):
docker compose exec -T pentestai python3 -c "from tools.browser import BrowserTool; print(BrowserTool().available)"Exit code 0 = all pass, 1 = something failed. Save machine-readable results: python3 /tmp/env_test.py --json → /tmp/env_test_report.json.
Deployment baseline 2026-10-20: 38/53 pass. All 15 failures are because keys in .env are still empty (see CONFIG.md).
# Check the installed version
defaults read /Applications/Docker.app/Contents/Info.plist CFBundleShortVersionString
# Docker Desktop must be RUNNING for this command
docker desktop update --check-only
docker desktop updateIf the CLI update fails (e.g. brew requires interactive sudo), download the official DMG and replace the app manually:
# Get the latest DMG URL from the official appcast
DMG_URL=$(curl -s https://desktop.docker.com/mac/main/arm64/appcast.xml \
| grep -oE 'https://[^"]*Docker\.dmg' | head -1)
echo "$DMG_URL"
curl -L -o /tmp/Docker.dmg "$DMG_URL"
hdiutil attach -nobrowse -mountpoint /tmp/dockerdmg /tmp/Docker.dmg
defaults read /tmp/dockerdmg/Docker.app/Contents/Info.plist CFBundleShortVersionString
rm -rf /Applications/Docker.app
ditto /tmp/dockerdmg/Docker.app /Applications/Docker.app
hdiutil detach /tmp/dockerdmg
rm -f /tmp/Docker.dmg
open -a Dockerbrew upgrade --cask docker-desktop may fail at the old helper uninstall stage (com.docker.socket, com.docker.vmnetd) because sudo prompts for a password in an interactive terminal — run it manually in Terminal if you choose this path.
Used when you want to discard all images/volumes or Docker is unstable:
# 1. Stop Docker Desktop (force if hung)
docker desktop stop || pkill -9 -f 'Docker Desktop'
pkill -9 -f 'com.docker'; pkill -9 -f 'docker-sandbox'
# 2. Delete all VM data: images, containers, build cache, volumes
rm -f ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
# 3. Start again (a new VM disk is created)
open -a Docker
until curl -s --unix-socket ~/.docker/run/docker.sock http://localhost/_ping; do sleep 5; done
# 4. Rebuild the application image without cache
docker compose --profile msf build --no-cache
# 5. Pull supporting base images
docker compose --profile msf pull neo4j qdrant msf
# 6. Start stack
docker compose --profile msf up -d
# 7. Clean build cache (freed ±7 GB in this session)
docker builder prune -f
⚠️ Step 2 deletes all volumes, including the Neo4j graph and Qdrant embeddings. Files on the host (sessions/,logs/,reports/) are untouched because they are mounted as bind volumes.The commands above are macOS-specific (Docker Desktop). On Linux:
docker system prune -a --volumes(equivalent, without deleting VM files).
| Item | Size |
|---|---|
Image pentestai:latest
|
7.83 GB (Kali tools + Chromium) |
| Base images (neo4j + qdrant + msf) | ±4.8 GB |
Build cache during --no-cache
|
±8 GB (can be pruned) |
Docker.raw before reset |
29 GB (sparse, maximum size 228 GB) |
Leave at least 10 GB free on the host. Docker Desktop on macOS will hang / return EOF from the API if the host disk is full — see TROUBLESHOOTING.md.
After setup, these are the directories that matter:
pentestai/
├── sessions/ ← Saved sessions (*.json)
│ └── llm_fleet.json ← LLM model configuration
├── logs/ ← Audit logs (*.jsonl)
├── reports/
│ └── output/ ← Generated reports (*.html)
├── wordlists/ ← Wordlists for gobuster/hydra
└── .env ← Configuration (DO NOT commit to git!)
# Default port 8080
python3 __main__.py --api
# Custom port
python3 __main__.py --api --port 9090
# Bind to all interfaces (for network access)
python3 __main__.py --api --host 0.0.0.0curl -X POST http://localhost:8080/api/v1/auth/keys \
-H "Content-Type: application/json" \
-d '{"name": "admin"}'Save the returned key.
BURP_API_URL=http://localhost:8090
BURP_API_KEY=your_burp_key# Start Metasploit RPC
msfrpcd -P yourpassword -S false -f -a 127.0.0.1
# Configuration
MSF_HOST=127.0.0.1
MSF_PORT=55553
MSF_PASSWORD=yourpasswordcurl -fsSL https://ollama.ai/install.sh | sh
ollama pull llama3.1:8b
ollama pull mistral:7b
# Set in .env
OLLAMA_BASE_URL=http://localhost:11434# Verify the env var is available
echo $ANTHROPIC_API_KEY
# Or in the shell
llm add # interactive wizardsudo apt-get install nmap # Debian/Ubuntu
brew install nmap # macOSsudo chmod u+s $(which nmap) # setuid for raw socket# Check status
docker compose ps neo4j
# Check logs
docker compose logs neo4j
# Test connection
curl http://localhost:7474python3 __main__.py --api --port 8081Node.js is required for report docx and orchestrate → ReportSubAgent → Word export.
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version # ≥ 16.x
# Install docx package (from the pentestai/ directory)
cd reports && npm install docxWithout Node.js: report docx and ptes (Markdown) still work. Only Word export fails.
The plugin dir (plugins/) already exists. No additional setup needed. Plugins are installed automatically via marketplace install <id>.
No additional dependencies — only the Python standard library.
No additional dependencies — uses the threading stdlib.
For a more secure deployment:
# Do not expose the API to the internet without auth
# Use a reverse proxy (nginx/caddy) with TLS
# Restrict API access by IP (nginx example)
location /api/ {
allow 10.0.0.0/8;
deny all;
proxy_pass http://localhost:8080;
}
# Rotate API keys periodically
curl -X DELETE http://localhost:8080/api/v1/auth/keys/{old_key_id} \
-H "X-API-Key: $ADMIN_KEY"
# Back up sessions and logs
tar -czf backup-$(date +%Y%m%d).tar.gz sessions/ logs/