Overview
Security teams using multiple scanners — SAST, DAST, container image scanning, dependency auditing — quickly end up drowning in disconnected CSV exports and PDF reports. Findings sit in silos, duplicates multiply across tools, and nothing tracks remediation SLAs.
OWASP DefectDojo solves this. It is a mature, open-source vulnerability management platform that ingests findings from over 100 security tools, deduplicates them, maps them to your product hierarchy, and drives a full remediation workflow complete with risk scoring, SLA tracking, and CI/CD integration via its REST API.
This guide walks through deploying DefectDojo with Docker Compose, wiring up Trivy and Semgrep as live scanners, and integrating it into a GitHub Actions pipeline so every pull request feeds scan results directly into your vulnerability backlog.
Why DefectDojo?
| Feature | Value |
|---|---|
| 100+ scanner parsers | Trivy, Nessus, Burp Suite, Semgrep, Bandit, OWASP ZAP, Grype, and more |
| Smart deduplication | Hashes findings across tests so re-runs don't multiply noise |
| Product / engagement model | Organises findings by application, sprint, and test type |
| SLA policies | Configurable remediation deadlines by severity (Critical: 7d, High: 30d) |
| REST API & webhooks | Native CI/CD integration — push findings from any pipeline |
| RBAC | Fine-grained roles: superuser, product owner, API user |
Architecture
┌─────────────────────────────────────────────────────────┐
│ DefectDojo Stack │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Nginx │──▶│ Django │──▶│ PostgreSQL 16 │ │
│ │ :80/:443 │ │ :8080 │ │ (defectdojo) │ │
│ └──────────┘ └────┬─────┘ └──────────────────┘ │
│ │ │
│ ┌──────▼──────┐ ┌──────────────────┐ │
│ │ Celery │──▶│ Redis 7 │ │
│ │ Workers │ │ (message broker) │ │
│ └─────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲ ▲
│ │
┌────────┴───────┐ ┌────────┴───────┐
│ CI/CD Pipline │ │ Scanner Import │
│ (GitHub Acts) │ │ (Trivy, DAST) │
└────────────────┘ └────────────────┘
DefectDojo uses a Django application server, Celery workers for async import and notification tasks, Redis as the broker, and PostgreSQL for persistent storage. Nginx fronts everything.
Product Hierarchy
DefectDojo organises data in a three-tier model:
Product Type (e.g. "Web Applications")
└── Product (e.g. "My API Service")
└── Engagement (e.g. "Sprint 42 Scan" or "CI/CD")
└── Test (e.g. "Trivy image scan 2026-07-01")
└── Finding (CVE-2024-XXXX — Critical)
This hierarchy means you can compare findings across releases, track fix rates per product, and report risk posture by product type.
Prerequisites
- Linux host with Docker Engine 24+ and Docker Compose v2
- 4 GB RAM minimum (8 GB recommended for comfortable operation)
- 20 GB disk (findings database grows over time)
- Ports 80 and 443 available, or a reverse proxy (Traefik/Nginx) in front
Step 1 — Clone and Configure
git clone https://github.com/DefectDojo/django-DefectDojo.git
cd django-DefectDojoCopy the environment template and edit credentials:
cp docker/environments/postgres-redis.env.example \
docker/environments/postgres-redis.envOpen docker/environments/postgres-redis.env and set strong values:
# Database
DD_DATABASE_URL=postgresql://defectdojo:CHANGEME_DB_PASS@postgres:5432/defectdojo
# Secret key — generate with: python3 -c "import secrets; print(secrets.token_hex(50))"
DD_SECRET_KEY=CHANGEME_SECRET_KEY_50_CHARS
# Admin credentials (first-run only)
DD_ADMIN_USER=admin
DD_ADMIN_MAIL=admin@example.com
DD_ADMIN_FIRST_NAME=Admin
DD_ADMIN_LAST_NAME=User
DD_ADMIN_PASSWORD=CHANGEME_ADMIN_PASS
# Optional: SMTP for email notifications
DD_EMAIL_URL=smtp://user:pass@smtp.example.com:587Step 2 — Launch the Stack
DefectDojo ships a production-ready compose file. The initializer service handles migrations and the first admin user:
docker compose \
--env-file docker/environments/postgres-redis.env \
-f docker-compose.yml \
-f docker-compose.override.yml \
up -dWatch the initializer complete (takes 2–4 minutes on first run):
docker compose logs -f initializer
# Look for: "Admin user created successfully"Once done, the UI is available at http://localhost. Log in with the credentials from your env file.
Production tip: Mount a named volume for PostgreSQL data and set
restart: unless-stoppedon all services. For TLS, place DefectDojo behind Traefik or Nginx with a Let's Encrypt certificate.
Step 3 — Create Your First Product
DefectDojo organises everything by product. Create one via the API (you can also use the UI):
# Get an API token
TOKEN=$(curl -s -X POST http://localhost/api/v2/api-token-auth/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"CHANGEME_ADMIN_PASS"}' \
| jq -r '.token')
echo "Token: $TOKEN"
# Create a Product Type
curl -s -X POST http://localhost/api/v2/product_types/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Container Images","critical_product":false,"key_product":false}' \
| jq .
# Create a Product
curl -s -X POST http://localhost/api/v2/products/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My API Service",
"description": "Main backend API",
"prod_type": 1
}' | jq .Step 4 — Run Trivy and Import Results
4a — Install Trivy
# On the host or in a pipeline runner
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b /usr/local/bin v0.54.0
trivy --version4b — Scan a Container Image
# Scan and output DefectDojo-compatible JSON
trivy image --format json --output trivy-results.json \
nginx:latest4c — Create an Engagement and Import
Every scan lives inside an engagement — a time-boxed testing event. Create one, then import:
# Create a CI/CD engagement (continuous type = always open)
ENGAGEMENT_ID=$(curl -s -X POST http://localhost/api/v2/engagements/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Container Scan - CI/CD",
"product": 1,
"target_start": "2026-07-01",
"target_end": "2026-12-31",
"engagement_type": "CI/CD",
"status": "In Progress",
"deduplication_on_engagement": false
}' | jq -r '.id')
echo "Engagement ID: $ENGAGEMENT_ID"
# Import the Trivy JSON report
curl -s -X POST http://localhost/api/v2/import-scan/ \
-H "Authorization: Token $TOKEN" \
-F "scan_type=Trivy Scan" \
-F "engagement=$ENGAGEMENT_ID" \
-F "file=@trivy-results.json" \
-F "minimum_severity=Low" \
-F "active=true" \
-F "verified=false" \
| jq '{test_id: .test_id, findings_count: .findings}'Navigate to Products → My API Service → Engagements → Container Scan in the UI to see your findings, grouped by severity.
Step 5 — Add a SAST Scanner (Semgrep)
# Install Semgrep
pip install semgrep
# Run against your codebase
semgrep --config=auto --json --output semgrep-results.json ./src/
# Import into the same engagement
curl -s -X POST http://localhost/api/v2/import-scan/ \
-H "Authorization: Token $TOKEN" \
-F "scan_type=Semgrep JSON Report" \
-F "engagement=$ENGAGEMENT_ID" \
-F "file=@semgrep-results.json" \
-F "minimum_severity=Low" \
-F "active=true" \
| jq '{test_id: .test_id, findings_count: .findings}'DefectDojo will deduplicate any finding that matches by hash across the Trivy and Semgrep tests, so you won't see the same vulnerability counted twice.
Step 6 — Configure SLA Policies
SLA policies enforce remediation timelines. Navigate to Configuration → SLA Configuration in the UI, or use the API:
curl -s -X POST http://localhost/api/v2/sla_configuration/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Standard SLA",
"description": "Default remediation SLAs by severity",
"critical": 7,
"high": 30,
"medium": 90,
"low": 180
}' | jq .Assign this SLA to your product. DefectDojo will flag findings that breach their deadline in red on the dashboard.
Step 7 — GitHub Actions CI Integration
Add a workflow that scans on every PR and pushes findings to DefectDojo automatically:
# .github/workflows/security-scan.yml
name: Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
format: json
output: trivy-results.json
severity: CRITICAL,HIGH,MEDIUM
- name: Upload to DefectDojo
env:
DD_URL: ${{ secrets.DEFECTDOJO_URL }}
DD_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }}
DD_ENGAGEMENT: ${{ secrets.DEFECTDOJO_ENGAGEMENT_ID }}
run: |
curl -s -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "scan_type=Trivy Scan" \
-F "engagement=$DD_ENGAGEMENT" \
-F "file=@trivy-results.json" \
-F "minimum_severity=Low" \
-F "active=true" \
-F "verified=false" \
-F "commit_hash=${{ github.sha }}" \
-F "branch_tag=${{ github.ref_name }}"
semgrep-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: auto
generateSarif: "0"
env:
SEMGREP_RULES: auto
- name: Export JSON
run: semgrep --config=auto --json --output semgrep-results.json .
- name: Upload to DefectDojo
env:
DD_URL: ${{ secrets.DEFECTDOJO_URL }}
DD_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }}
DD_ENGAGEMENT: ${{ secrets.DEFECTDOJO_ENGAGEMENT_ID }}
run: |
curl -s -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "scan_type=Semgrep JSON Report" \
-F "engagement=$DD_ENGAGEMENT" \
-F "file=@semgrep-results.json" \
-F "minimum_severity=Low" \
-F "active=true"Add DEFECTDOJO_URL, DEFECTDOJO_TOKEN, and DEFECTDOJO_ENGAGEMENT_ID to your repository secrets.
Step 8 — Enable Smart Deduplication
DefectDojo's deduplication engine prevents findings from multiplying across scans. Configure it per product under Product → Edit → Deduplication. The recommended algorithm is unique_id_from_tool_or_hash_code, which uses the tool's native CVE/rule ID if present, falling back to a content hash.
Check deduplication status from the API:
# List findings and check for duplicates
curl -s "http://localhost/api/v2/findings/?duplicate=true&limit=20" \
-H "Authorization: Token $TOKEN" \
| jq '[.results[] | {id, title, severity, duplicate_finding}]'Testing the Setup
Verify the Stack is Healthy
# All containers should be running
docker compose ps --format "table {{.Name}}\t{{.Status}}"
# Django health check
curl -s http://localhost/api/v2/system_settings/ \
-H "Authorization: Token $TOKEN" | jq '.system_settings_url'Verify Findings Appear
# Count findings by severity
curl -s "http://localhost/api/v2/findings/?limit=1" \
-H "Authorization: Token $TOKEN" | jq '.count'
# List Critical findings
curl -s "http://localhost/api/v2/findings/?severity=Critical&active=true" \
-H "Authorization: Token $TOKEN" \
| jq '[.results[] | {id, title, cve, component_name}]'Test the Deduplication
Import the same Trivy report twice and confirm the second import produces 0 new findings (all deduplicated):
# Import again
RESPONSE=$(curl -s -X POST http://localhost/api/v2/import-scan/ \
-H "Authorization: Token $TOKEN" \
-F "scan_type=Trivy Scan" \
-F "engagement=$ENGAGEMENT_ID" \
-F "file=@trivy-results.json" \
-F "minimum_severity=Low" \
-F "active=true")
echo "New findings: $(echo $RESPONSE | jq '.findings')"
echo "Closed findings: $(echo $RESPONSE | jq '.closed_findings')"A healthy deduplication result shows findings: 0 (all matched existing records) and closed_findings: 0.
Deployment Considerations
Persistent Storage
Add named volumes to your compose file to survive container restarts:
volumes:
defectdojo-media:
postgres-data:
services:
postgres:
volumes:
- postgres-data:/var/lib/postgresql/data
django:
volumes:
- defectdojo-media:/app/mediaReverse Proxy with TLS
If running behind Traefik, add labels to the nginx service:
services:
nginx:
labels:
- "traefik.enable=true"
- "traefik.http.routers.defectdojo.rule=Host(`dojo.example.com`)"
- "traefik.http.routers.defectdojo.entrypoints=websecure"
- "traefik.http.routers.defectdojo.tls.certresolver=letsencrypt"
- "traefik.http.services.defectdojo.loadbalancer.server.port=80"User Management
Create scoped API users for each CI/CD system rather than sharing the admin token:
- Navigate to Settings → Users → Add User
- Assign the
API Userrole to the product(s) it should write to - Generate a personal API token under that user's profile
Extensions and Next Steps
Add More Scanners
DefectDojo has parsers for all of the following — add them to your pipelines with the same import-scan API call:
- DAST: OWASP ZAP, Burp Suite, Nuclei
- Container: Grype, Snyk Container, AWS ECR findings
- IaC: Checkov, tfsec, Terrascan
- Dependency SCA: OWASP Dependency-Check, Grype SBOM, npm audit
- Cloud: AWS Inspector, Microsoft Defender for Cloud export
SBOM Integration
Generate a Software Bill of Materials with Syft, then import it for SCA:
syft nginx:latest -o cyclonedx-json > sbom.json
curl -s -X POST http://localhost/api/v2/import-scan/ \
-H "Authorization: Token $TOKEN" \
-F "scan_type=CycloneDX Scan" \
-F "engagement=$ENGAGEMENT_ID" \
-F "file=@sbom.json" \
-F "minimum_severity=Low" \
-F "active=true"Notifications and Jira Integration
DefectDojo can push finding notifications to Slack, MS Teams, and create Jira tickets automatically when new Critical/High findings are imported. Configure under Configuration → System Settings → Notifications and Configuration → JIRA.
Metrics with Grafana
DefectDojo exposes finding metrics via its API. Pair it with Grafana using a Prometheus push-gateway or build a custom data source to visualise your vulnerability trend over time — open findings by severity, mean time to remediation (MTTR), and SLA compliance rate.
Summary
You now have a self-hosted vulnerability management platform that:
- Ingests findings from Trivy, Semgrep, and 100+ other tools
- Deduplicates intelligently so your backlog stays clean
- Enforces SLAs with configurable timelines per severity
- Integrates with CI/CD via REST API, tagging findings to commits and branches
- Scales with Celery workers for async processing of large scan results
DefectDojo bridges the gap between running security tools and actually managing the vulnerabilities they find — turning disconnected scan outputs into an auditable remediation workflow.