Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsTraining
StudyProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Training
Study
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

1794+ Articles
149+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. Projects
  3. OWASP DefectDojo: Self-Hosted Vulnerability Management Platform
OWASP DefectDojo: Self-Hosted Vulnerability Management Platform
PROJECTIntermediate

OWASP DefectDojo: Self-Hosted Vulnerability Management Platform

Aggregate, deduplicate, and track security findings from 100+ scanners in a unified dashboard. Build a production-grade vulnerability management pipeline...

Dylan H.

Projects

July 1, 2026
10 min read
3-5 hours

Tools & Technologies

DockerDocker ComposeDefectDojoTrivySemgrepcurl / HTTPie

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?

FeatureValue
100+ scanner parsersTrivy, Nessus, Burp Suite, Semgrep, Bandit, OWASP ZAP, Grype, and more
Smart deduplicationHashes findings across tests so re-runs don't multiply noise
Product / engagement modelOrganises findings by application, sprint, and test type
SLA policiesConfigurable remediation deadlines by severity (Critical: 7d, High: 30d)
REST API & webhooksNative CI/CD integration — push findings from any pipeline
RBACFine-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-DefectDojo

Copy the environment template and edit credentials:

cp docker/environments/postgres-redis.env.example \
   docker/environments/postgres-redis.env

Open 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:587

Step 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 -d

Watch 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-stopped on 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 --version

4b — Scan a Container Image

# Scan and output DefectDojo-compatible JSON
trivy image --format json --output trivy-results.json \
  nginx:latest

4c — 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/media

Reverse 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:

  1. Navigate to Settings → Users → Add User
  2. Assign the API User role to the product(s) it should write to
  3. 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.

#vulnerability-management#DevSecOps#Docker#open-source#AppSec#SIEM

Related Articles

Building a SOAR Platform with Shuffle in Your Homelab

Deploy Shuffle, the open-source SOAR platform, to automate security workflows and orchestrate your homelab tools — from Wazuh alerts to enrichment lookups...

11 min read

Building a Wazuh XDR + SIEM Homelab

Deploy a full Wazuh stack in Docker to gain host-based intrusion detection, file integrity monitoring, vulnerability scanning, and active response across your…

14 min read

OpenCTI: Building a Cyber Threat Intelligence Platform in Your Homelab

Deploy OpenCTI to aggregate, correlate, and visualize threat intelligence from MISP, NVD, AlienVault OTX, and Shodan — all in a self-hosted Docker stack.

12 min read
Back to all Projects