Most security teams do not have a scanner problem. They have a spreadsheet problem. A typical DevSecOps pipeline in 2026 runs Trivy against container images, Nessus or OpenVAS against the network, OWASP ZAP against the running web app, and Semgrep against the source code: four tools, four report formats, four places where the same vulnerability might show up under a different name. DefectDojo, the OWASP-flagged open-source vulnerability management platform, exists to collapse all of that into one findings database with one triage workflow. This tutorial walks through a full self-hosted DefectDojo deployment: cloning the repository, building the Docker stack, importing real scan results from Trivy, ZAP, and Nessus, wiring up deduplication, connecting Jira, and automating imports through the REST API in a CI/CD pipeline.
The build below is verified against DefectDojo 3.3.300, the release published to the DefectDojo/django-DefectDojo GitHub repository on September 28, 2026, with DefectDojo 3.4.0 following close behind as a general-availability release in October 2026, per the project’s GitHub release notes. The 3.3.0 line that this guide is built on also retired the classic Bootstrap UI in favor of the newer interface and introduced an alpha preview of API v3, both landing in September 2026, so don’t be surprised if screenshots from older tutorials no longer match what you see in the browser. Every command, environment variable, and container image tag in this guide was pulled directly from the current docker-compose.yml and Dockerfile definitions in that repository, not from memory or an older tutorial. If you have tried DefectDojo before and hit a wall with Docker Compose version errors or a missing admin password, the steps here address both.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What DefectDojo Actually Does and Why Scanner Sprawl Makes It Necessary
DefectDojo organizes security data into a four-level hierarchy: a Product (your application or service), an Engagement (a testing cycle, sprint, or pentest window), a Test (one scanner run, like a Trivy scan against a specific image tag), and a Finding (an individual vulnerability). Every scanner output you import lands as a set of Findings attached to a Test, which means a Trivy scan, a ZAP scan, and a manual pentest report all end up living in the same searchable database instead of three separate PDFs or CSV exports sitting in someone’s downloads folder.
The platform’s real differentiator is parser breadth. DefectDojo’s own documentation advertises “200+ Supported Tools” for import and reimport. A direct look at the dojo/tools directory in the GitHub repository as of late September 2026 shows more than 360 individual parser modules, covering everything from container scanners (Trivy, Grype, Anchore, Aqua) to DAST tools (OWASP ZAP, Acunetix, AppSpider, Arachni), SAST engines, cloud posture scanners (AWS Prowler, AWS Security Hub, ASFF), and API-based feeds like SonarQube and Vulners. Flag that last category if you rely on it: DefectDojo 3.5.0, scheduled for November 2026, is set to remove those API-based parsers in favor of file-based import equivalents, so pipelines pulling findings straight from the SonarQube or Vulners APIs will need to switch to exporting and importing files instead. That parser count is still the practical reason teams adopt DefectDojo over building a custom findings pipeline: someone has already written and maintained the parser for whatever scanner you run. If you’re still working out the broader process, rather than just the tooling, our vulnerability management program guide covers the policy side this platform is meant to support.
DefectDojo is licensed under the BSD 3-Clause license, which permits commercial use, modification, and redistribution without a licensing fee for the open-source edition. A commercial DefectDojo Pro tier exists for teams that want a hosted SaaS deployment, a universal JSON/CSV/XML parser, and downstream connectors to Azure DevOps, ServiceNow, Bitbucket, PagerDuty, and additional ticketing systems beyond the open-source Jira integration. Pro has kept moving fast: version 3.3.300 added 27 UI languages in September 2026, and Pro 3.4.101 shipped the following month, in October 2026, per DefectDojo’s own release notes. That $100-per-month-plus-$0.15-per-finding Pro pricing took effect in September 2026, according to the defectdojo.com pricing page, and sits alongside DefectDojo Sensei, the AI add-on, which as of the same month prices its automated fixes at $15 per fix pull request and $30 per threat model generated. This tutorial covers the self-hosted, free, open-source edition end to end.
Prerequisites: Versions and System Requirements
Before you start, confirm your host machine meets the minimums documented in the project’s own DOCKER.md file. DefectDojo’s Docker Compose path requires Docker 19.03.0 or newer and Docker Compose 1.28.0 or newer. The Dockerfiles also depend on BuildKit, which has shipped enabled by default since Docker 23.0. If you are running an older Docker Engine, you will need to export DOCKER_BUILDKIT=1 manually before building images. Budget at least 4 GB of RAM and 2 CPU cores for a comfortable local evaluation stack. Production deployments with active scanner imports and a growing findings database should plan for considerably more, especially on the PostgreSQL and Celery worker containers.
| Component | Version Pinned in docker-compose.yml (Sept 2026) | Role |
|---|---|---|
| DefectDojo release | 3.3.300 (published Sept 28, 2026) | Core application (Django + Celery) |
| Django framework | 5.2.16 | Web application framework |
| Python base image | 3.14.7-alpine3.23 | Django and Celery container runtime |
| PostgreSQL | 18.6-alpine | Primary findings database |
| Valkey | 9.1.2-alpine | Cache and Celery message broker (Redis-protocol fork) |
| Docker Engine | 19.03.0 minimum | Container runtime |
| Docker Compose | 1.28.0 minimum | Multi-container orchestration |
Note the move to Valkey rather than Redis in the current compose file. Valkey is the Linux Foundation-backed, open-source fork of Redis that several major projects switched to after Redis’s 2024 license change, and DefectDojo’s maintainers now ship it as the default cache and Celery broker. If you are following an older tutorial or Stack Overflow answer that references a redis service name, expect it to be out of date against the current compose file.
Step 1: Clone the Repository and Choose a Deployment Mode
Start by cloning the official repository and checking out the tag you want to run. Pinning to a specific tag rather than tracking master matters for reproducibility. As the Trivy supply-chain incident covered later in this guide illustrates, pinning to an immutable reference instead of a mutable tag is a habit worth building into every tool you pull into a CI/CD pipeline, DefectDojo included.
git clone https://github.com/DefectDojo/django-DefectDojo.git
cd django-DefectDojo
git checkout tags/3.3.300 -b defectdojo-3.3.300
docker --version
docker compose version
DefectDojo ships two compose paths: a release mode that runs prebuilt images pulled from Docker Hub or your local cache, and a development mode that mounts your source tree into the containers for hot-reloading. For a production-style evaluation, release mode is the right starting point. The repository includes a helper script, docker/setEnv.sh, that writes the correct environment file for whichever mode you pick.
docker/setEnv.sh release
Step 2: Configure Environment Variables and Secrets
The compose file reads its configuration from a generated .env.prod (or .env.dev) file rather than hardcoded values. Open it and set, at minimum, a strong DD_SECRET_KEY, a database password, and the hostname you will access the app from. Never leave the default database credentials in place if this instance will be reachable from anything other than localhost: DefectDojo’s findings database is, by definition, a map of every unpatched vulnerability in your environment, which makes it a high-value target if exposed. Worth knowing if you’re coming from an older install: the in-app Credential Manager, previously used to store third-party scan credentials inside DefectDojo itself, was removed in the 2.57.x release line as of June 2026, so any scanner credentials now belong in environment variables or an external secrets manager rather than DefectDojo’s own UI.
# .env.prod (excerpt)
DD_DEBUG=False
DD_DJANGO_ADMIN_ENABLED=True
DD_ALLOWED_HOSTS=defectdojo.internal.example.com,localhost
DD_DATABASE_URL=postgresql://defectdojo:CHANGE_ME_STRONG_PASSWORD@postgres:5432/defectdojo
DD_SECRET_KEY=CHANGE_ME_50_CHAR_RANDOM_STRING
DD_CREDENTIAL_AES_256_KEY=CHANGE_ME_32_BYTE_KEY
Generate real random values for DD_SECRET_KEY and DD_CREDENTIAL_AES_256_KEY rather than typing something memorable. A quick way to produce a safe secret on any Linux or macOS machine:
openssl rand -base64 48
Step 3: Build the Docker Images
Building locally rather than trusting whatever tag is cached ensures the image you run matches the code you actually cloned. This is also the step where BuildKit compatibility issues surface if your Docker Engine is old enough to need the manual flag.
docker compose build
# Or build a single service if you only changed one component:
docker compose build uwsgi
docker compose build nginx
Expect the first build to take several minutes, since the Django image installs the full Python dependency tree on top of the python:3.14.7-alpine3.23 base image, plus the Node-based static asset pipeline for the frontend. Subsequent builds are much faster because Docker caches unchanged layers.
Step 4: Launch DefectDojo With Docker Compose
With images built, bring the stack up. The compose file starts the PostgreSQL database, the Valkey cache/broker, the Django/uWSGI application server, an nginx reverse proxy, one or more Celery worker containers for background jobs like scan imports and report generation, and an initializer container that runs database migrations and creates the first admin account on a fresh install. If you’d rather pull prebuilt images than build from source, Aqua-style publishing conventions apply here too: the maintainers publish tagged defectdojo-django and defectdojo-nginx images to Docker Hub under the defectdojo organization, which is what the compose file falls back to if it doesn’t find a locally built image in your Docker cache.
docker compose up -d
docker compose ps
Give the initializer container a minute or two to finish migrations on first launch, since a database with 369-plus parser definitions and their associated metadata takes longer to initialize than a typical Django app’s first migration run. Watch the logs if you want to confirm it has finished:
docker compose logs -f initializer
Step 5: Retrieve the Admin Password and Complete First Login
DefectDojo generates a random admin password on first boot and writes it once to the initializer container’s log output. It does not use a fixed default like admin/admin. Pull it with:
docker compose logs initializer | grep "Admin password:"
Write that password down immediately. It is only printed on the very first startup, and if you tear down the container without saving it, recovering access means resetting the password through the Django management command instead:
docker compose exec uwsgi ./manage.py changepassword admin
Navigate to http://localhost:8080 (or whatever host you set in DD_ALLOWED_HOSTS) and log in as admin with the retrieved password. The first thing worth doing after login is enabling two-factor authentication on the admin account and creating a second, non-admin user for day-to-day triage work, so the admin credential is used only for configuration changes.
Step 6: Understand the Data Model Before You Import Anything
Products, Engagements, Tests, and Findings
Getting the hierarchy right up front saves you from a messy re-organization later. A Product should map to something a business owner would recognize, such as “Customer Portal API” or “Mobile App Backend,” not “prod-cluster-3.” An Engagement is a bounded window of testing activity inside that product: a sprint, a release candidate, a quarterly pentest, or, for continuous scanning, a rolling engagement that stays open and accumulates Tests. Each Test corresponds to one scanner run: one Trivy scan of one image tag, one ZAP scan of one URL, and the Findings that come out of it are individual vulnerabilities tied back to that Test, that Engagement, and that Product.
Deduplication and Why It Matters More Than People Expect
The reason a hierarchy matters this much is deduplication. If the same CVE shows up in three consecutive Trivy scans of the same image, or in both a Trivy scan and a Grype scan of the same layer, DefectDojo can automatically recognize it as the same underlying Finding rather than creating three duplicate tickets in Jira. Deduplication rules are configurable per scan type in the System Settings, and getting them right early is the single biggest factor in whether your team trusts the dashboard or starts ignoring it because of noise.
Step 7: Create Your First Product and Engagement
From the DefectDojo UI, go to Products → Add Product. Give it a name, a description, and assign a Product Type (Internal, Third Party, or a custom type you define). Under that product, create an Engagement: set a start and end date, and choose whether it is a CI/CD-driven engagement (which stays open indefinitely to absorb automated imports) or a fixed-window engagement for a specific pentest.
You can do the same thing from the command line using the REST API, which is the approach worth adopting immediately if you plan to automate any part of this later:
curl -s -X POST "http://localhost:8080/api/v2/products/" \
-H "Authorization: Token $DD_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Portal API",
"description": "Public-facing REST API for the customer portal",
"prod_type": 1
}'
You can find your personal API token under User icon → API v2 Key in the DefectDojo UI once logged in. The full REST API reference, including every field accepted by the products, engagements, and import-scan endpoints, is published at docs.defectdojo.com, worth bookmarking before you start scripting imports, since a handful of endpoints (particularly reimport versus import) behave differently enough that guessing from the UI alone will cost you time. If you’re scripting against an older install, note that the Questionnaire API endpoints were removed in the 2.56.x release line as of June 2026, so any automation built against them needs to move to the current engagements and findings endpoints instead.
Step 8: Import Trivy and Grype Container Scan Results
Container scanning is usually the first workflow teams wire into DefectDojo, since a Trivy or Grype scan runs in seconds and produces immediate, high-volume findings data. If you haven’t run Trivy before, our guide to scanning container images with Trivy covers installation and flags in more depth than this step alone. Run Trivy against an image and output JSON, then import that JSON file through the UI’s Import Scan Results button under your Engagement, selecting “Trivy Scan” as the scan type.
trivy image --format json --output trivy-results.json myapp:latest
curl -s -X POST "http://localhost:8080/api/v2/import-scan/" \
-H "Authorization: Token $DD_API_TOKEN" \
-F "scan_type=Trivy Scan" \
-F "[email protected]" \
-F "engagement=12" \
-F "active=true" \
-F "verified=false"
Prefer Grype instead of or alongside Trivy? DefectDojo’s parser directory includes dedicated Anchore/Grype parsers, so the same import flow applies. Just change the scan_type parameter to match the scanner you actually ran. Running both against the same image and letting DefectDojo’s deduplication engine merge the overlapping CVEs is a common pattern for teams that don’t fully trust a single scanner’s detection rate.
Step 9: Import OWASP ZAP, Nessus, and Semgrep Results
The real value of DefectDojo shows up once you’re importing from more than one category of scanner into the same Product. A ZAP scan of the running application (see our OWASP ZAP setup guide if you haven’t configured it yet), a Nessus scan of the network hosting it (our Nessus Essentials setup walkthrough covers the free tier), and a Semgrep scan of the source code all describe different attack surfaces of the same system, and triaging them separately means a finding that looks low-priority in isolation (an outdated TLS cipher from Nessus, say) can get missed as part of a chained attack path that a ZAP or Semgrep finding also touches.
# OWASP ZAP (export as JSON from the ZAP UI or zap-cli)
curl -s -X POST "http://localhost:8080/api/v2/import-scan/" \
-H "Authorization: Token $DD_API_TOKEN" \
-F "scan_type=ZAP Scan" \
-F "[email protected]" \
-F "engagement=12"
# Nessus (export as .nessus XML from the scan results page)
curl -s -X POST "http://localhost:8080/api/v2/import-scan/" \
-H "Authorization: Token $DD_API_TOKEN" \
-F "scan_type=Nessus Scan" \
-F "[email protected]" \
-F "engagement=12"
# Semgrep (JSON output from `semgrep --json`)
curl -s -X POST "http://localhost:8080/api/v2/import-scan/" \
-H "Authorization: Token $DD_API_TOKEN" \
-F "scan_type=Semgrep JSON Report" \
-F "[email protected]" \
-F "engagement=12"
Each import creates a distinct Test under the Engagement, so you retain full traceability back to which scanner, and which run of that scanner, produced any given Finding, useful both for audit purposes and for spotting a scanner that has started producing an unusual number of false positives.
Step 10: Configure Deduplication and Reduce Alert Fatigue
Navigate to Configuration → System Settings and look for the deduplication section. DefectDojo supports per-scan-type deduplication algorithms: some scan types dedupe on CVE plus component name, others on a hash of the finding title and description, since not every scanner reports a clean CVE identifier. The default algorithms are sensible for most of the 369-plus supported parsers, but if you’re importing both Trivy and Grype results for the same images, verify that the dedup fields line up on the package name and version rather than only the CVE ID. A mismatch there is the single most common cause of teams seeing “duplicate” findings that DefectDojo failed to merge.
Step 11: Connect Jira for Bidirectional Ticket Sync
Under Configuration → System Settings, enable the Jira integration, then add a Jira instance using either a username/password pair or an API token. Once the instance is linked, attach it to a specific Product or Engagement so findings pushed from that scope land in the right Jira project. The open-source Jira integration is bidirectional through a Jira webhook: closing the linked issue in Jira automatically closes the corresponding Finding back in DefectDojo, which keeps the two systems from drifting apart.
# Example: push a specific finding to Jira via the API
curl -s -X POST "http://localhost:8080/api/v2/findings/42/push_to_jira/" \
-H "Authorization: Token $DD_API_TOKEN"
If you’re connecting to Jira Cloud rather than a self-hosted Jira Server, note that DefectDojo requires a certificate signed by a globally trusted certificate authority for the webhook callback to work: a self-signed cert on your DefectDojo instance will silently break the bidirectional sync even though the initial push succeeds.
Step 12: Automate Imports in CI/CD With the REST API
Manual imports are fine for evaluation. Production usage means every pipeline run pushes its scan results automatically. A GitHub Actions job that scans a container image and imports the results into DefectDojo looks like this:
name: Scan and Import to DefectDojo
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy
run: |
docker run --rm -v $(pwd):/out \
aquasec/trivy:latest image \
--format json --output /out/trivy-results.json \
myapp:${{ github.sha }}
- name: Import into DefectDojo
run: |
curl -s -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/import-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_API_TOKEN }}" \
-F "scan_type=Trivy Scan" \
-F "[email protected]" \
-F "engagement=${{ vars.DEFECTDOJO_ENGAGEMENT_ID }}" \
-F "close_old_findings=true"
Setting close_old_findings=true tells DefectDojo to automatically close previously reported findings that no longer appear in the current scan, the equivalent of a rescan confirming a vulnerability has actually been patched, rather than leaving it open indefinitely because nobody manually closed the ticket.
Step 13: Set SLA Policies, Risk Acceptance, and Reporting
Under Configuration → SLA Configuration, define how many days each severity level (Critical, High, Medium, Low) has before it is considered overdue. DefectDojo surfaces overdue findings on the dashboard and can notify assignees automatically. For findings your team has decided not to fix (a false positive, a vulnerability in code that’s unreachable in your deployment, or an accepted business risk), use the Risk Acceptance workflow rather than just closing the finding, since it preserves the justification and reviewer for audit purposes instead of making the finding disappear with no record of the decision.
Reporting lives under Metrics and the per-Product report generator, which can export a PDF or Word-format summary suitable for a compliance auditor or an executive who does not want to log into the dashboard directly.
The Trivy Supply Chain Attack: Why Aggregation Platforms Need Their Own Hardening
Centralizing scan results in one platform raises a question that’s easy to skip past: what happens if one of the scanners feeding that platform is itself compromised? That is not a hypothetical. On March 19, 2026, Trivy, the same open-source container scanner used in Step 8 above, was hit by a real supply-chain compromise tracked as CVE-2026-33634. According to Microsoft’s Security Response Center and the Cloud Security Alliance, a threat actor cluster identified as TeamPCP used compromised credentials to publish a malicious Trivy v0.69.4 release, then force-pushed 76 of 77 version tags in the aquasecurity/trivy-action GitHub Action to point at credential-stealing malware, and replaced all seven tags in aquasecurity/setup-trivy with malicious commits. The Cloud Security Alliance’s research note describes the campaign as a four-wave attack between March 19 and 24, 2026, that also touched Checkmarx’s KICS/AST GitHub Actions and the LiteLLM project, three tools that are all commonly run inside CI/CD pipelines feeding platforms exactly like DefectDojo.
Kaspersky’s writeup of the incident assigned it a near-maximum CVSS4 severity score of 9.4, and confirmed the attack’s goal was credential theft from CI/CD environments rather than tampering with scan output itself. The safe versions confirmed by the affected repositories’ security advisories were Trivy binary v0.69.2 or v0.69.3, trivy-action v0.35.0, and setup-trivy v0.2.6 after the maintainers recreated the tag safely. The practical lesson for anyone building the pipeline in Step 12 of this tutorial: pin GitHub Actions to an immutable commit SHA rather than a mutable version tag like @v0.35, and verify binary checksums when a script downloads a scanner directly rather than through a package manager. A DefectDojo instance is only as trustworthy as the scanners that feed it: if one of those scanners is compromised in your CI/CD environment, no amount of deduplication logic downstream fixes that.
DefectDojo vs. Dependency-Track vs. ArcherySec vs. Faraday
DefectDojo is not the only open-source option in this space, and it is not always the right one. The table below reflects each project’s stated focus as of September 2026.
| Platform | Primary Focus | License | Best Fit |
|---|---|---|---|
| DefectDojo | Broad findings orchestration across 360+ scanner parsers, dedup, SLA tracking, Jira sync | BSD 3-Clause | Teams running multiple scanner categories (container, DAST, SAST, network) that need one triage surface |
| Dependency-Track | Software supply-chain and SBOM component risk, tied to CycloneDX | Apache 2.0 | Teams focused specifically on third-party dependency and license risk rather than full-spectrum scanning |
| ArcherySec | Open-source vulnerability assessment and management, smaller parser ecosystem | MIT | Smaller teams wanting a lighter-weight alternative with a simpler feature set |
| Faraday | Collaborative pentest workspace, multi-user live collaboration during engagements | GPLv3 (community edition) | Pentest teams that need real-time collaboration during an active engagement rather than post-scan aggregation |
If your organization already generates SBOMs (see our guide to generating an SBOM if that step isn’t in place yet) and mainly worries about dependency risk, Dependency-Track alone may cover the need without the operational overhead of DefectDojo’s broader scope. The two tools are frequently run side by side rather than as substitutes, since DefectDojo can import Dependency-Track’s findings output as just another scan type.
Common Pitfalls When Deploying DefectDojo
1. Losing the admin password on first boot. The admin password prints exactly once, to the initializer container’s log stream. Teams that tear down containers to “start fresh” before saving it end up locked out and have to reset the password through manage.py changepassword instead. Write it into a password manager the moment you see it, not a scratch text file you’ll lose track of by the following week.
2. Treating every Product as one giant Engagement. Dumping every scan from every sprint into a single open-ended Engagement makes SLA tracking and reporting nearly useless, since there’s no clean boundary between testing cycles. Create new Engagements deliberately, especially for point-in-time pentests versus continuous CI/CD scanning, so a report pulled six months from now still tells a coherent story about what was tested and when.
3. Skipping deduplication configuration entirely. Relying on default dedup settings without verifying they match how you actually run your scanners (single image vs. multi-arch builds, for instance) leads to either duplicate tickets flooding Jira or, worse, distinct vulnerabilities getting incorrectly merged and silently hidden behind a single closed ticket.
4. Running the evaluation docker-compose.yml in production unmodified. The repository’s own documentation is explicit that the default compose file is meant for local evaluation, not production, without further hardening: TLS termination, external managed PostgreSQL, resource limits, and secrets management all need to be layered on top before this goes anywhere near the public internet.
5. Trusting scanner output without pinning scanner versions. As the Trivy supply-chain incident above demonstrates, an unpinned scanner pulled fresh in every CI run is an unaudited dependency. Pin scanner container images by digest, not just by tag, in any pipeline that feeds DefectDojo, and treat a sudden spike or drop in finding counts from one scan type as a signal worth investigating rather than dismissing as noise.
6. Forgetting that Jira Cloud requires a trusted TLS certificate. Bidirectional Jira sync depends on a webhook callback to your DefectDojo instance. A self-signed certificate lets the initial push succeed but silently breaks the “closed in Jira closes in DefectDojo” half of the sync, which teams often don’t notice until stale findings pile up and someone asks why the dashboard doesn’t match Jira anymore.
7. Ignoring the difference between “Active” and “Verified” on import. The import API defaults can mark findings active without marking them verified, which some teams mistake for a two-step approval workflow that isn’t actually enforced anywhere downstream. Decide up front whether unverified findings should still trigger Jira tickets and SLA clocks, or whether a human review step needs to happen first. The default behavior will do whichever your import parameters say, silently.
Troubleshooting Guide
| Symptom | Likely Cause | Fix |
|---|---|---|
| “version in docker-compose.yml is unsupported” error | Docker Compose older than 1.28.0 | Upgrade Docker Compose; check with docker compose version |
| Build fails referencing BuildKit syntax | Docker Engine older than 23.0 without BuildKit enabled | Export DOCKER_BUILDKIT=1 before running docker compose build |
| Can’t find the admin password anywhere | Initializer log already scrolled past, or container was removed | Run docker compose exec uwsgi ./manage.py changepassword admin |
| Import API call returns 400 Bad Request | Wrong scan_type string (must match DefectDojo’s exact parser name) | Check the exact scan_type value in DefectDojo’s parser documentation or the Import Scan Results dropdown in the UI |
| Findings from Trivy and Grype aren’t merging as duplicates | Dedup algorithm not matching on package name/version for both scan types | Review deduplication settings under Configuration → System Settings for both scan types |
| Jira sync pushes findings but never closes them back | Self-signed TLS certificate on the DefectDojo instance | Use a certificate from a trusted CA, or a reverse proxy like Caddy/Let’s Encrypt in front of nginx |
| Celery worker container keeps restarting | Valkey/broker connection failure, usually a stale environment variable | Confirm the broker URL points at the valkey service name, not a leftover redis reference from an old config |
| Initializer container hangs on first boot | PostgreSQL container not fully healthy before migrations run | Check docker compose logs postgres for startup errors; increase the health-check start period if running on slower storage |
| REST API returns 401 Unauthorized | Expired or incorrectly copied API token | Regenerate the token under User icon → API v2 Key and confirm the Authorization header uses the format Token <key> |
Advanced Tips: Scaling DefectDojo and the Universal Parser
Once you’re past the evaluation phase, a few adjustments make DefectDojo hold up under real production load. First, move PostgreSQL to a managed instance rather than the containerized default, since a findings database that’s ingesting daily CI/CD scans across dozens of Products grows quickly, and managed backups matter more here than in most internal tools, since this database is effectively your organization’s vulnerability history. Second, scale the Celery worker container independently from the web/uWSGI container, since imports and report generation are the workloads that actually spike under load, not page rendering. Third, if your team runs a scanner without an official DefectDojo parser, check whether DefectDojo Pro’s Universal Parser, which accepts arbitrary JSON, CSV, or XML and lets you map fields yourself, solves the gap before writing a custom parser against the open-source codebase.
Finally, set up the Metrics dashboard’s trend view early rather than only after a compliance audit forces the question. A findings-over-time trend line, broken out by severity and Product, is the artifact most audits and most engineering leadership reviews actually want to see, and it’s far easier to produce when you have months of consistent import history behind it, rather than trying to backfill it after the fact.
Sizing Your Deployment: Evaluation, Team, and Enterprise Scale
Not every DefectDojo deployment needs the same amount of infrastructure, and over-provisioning on day one wastes effort that’s better spent getting the deduplication rules right. For a solo evaluation or a small team importing a handful of scans a week, the default docker-compose.yml running on a single 4 GB/2-vCPU VM is genuinely sufficient, and this is the scenario Steps 1 through 9 of this tutorial assume. Once multiple teams and dozens of Products are importing from CI/CD on every commit, the Celery worker becomes the first bottleneck, since imports, deduplication passes, and report generation all queue through it. Splitting the worker into its own container with dedicated CPU allocation, separate from the uWSGI web process, is usually the first scaling change that actually moves the needle. At enterprise scale, with hundreds of Products and continuous imports from a large CI/CD fleet, moving PostgreSQL off the container entirely to a managed service with point-in-time recovery stops being optional, since a findings database that size represents months or years of vulnerability history that would be expensive to lose. The Kubernetes Helm chart path exists specifically for this tier, letting the web, worker, and initializer components scale independently rather than as a single docker-compose unit.
The Complete Working Project
Put together, the full pipeline built across this tutorial looks like this: a GitHub Actions workflow builds a container image, scans it with Trivy, and imports the JSON output into a DefectDojo Engagement via the REST API with close_old_findings=true. A second scheduled workflow runs OWASP ZAP against a staging deployment weekly and imports those results into the same Product under a different Test. Nessus network scans and Semgrep source-code scans feed in on their own schedules. Deduplication rules merge overlapping CVEs across Trivy and Grype runs. SLA policies flag anything Critical that’s been open more than 7 days. Jira automatically receives a ticket for every new, deduplicated finding above Medium severity, and closes it back out when the linked ticket closes. That’s a single, queryable source of truth replacing four scanners’ worth of disconnected reports, which is the entire point of running DefectDojo in the first place.
Frequently Asked Questions
Is DefectDojo free to use? Yes. The self-hosted, open-source edition covered in this tutorial is licensed under BSD 3-Clause and free to deploy, modify, and run. DefectDojo Pro, the commercial hosted tier, is priced separately at $100 per month plus $0.15 per finding processed as of late September 2026, according to the defectdojo.com pricing page.
How many scanners does DefectDojo actually support? DefectDojo’s official documentation advertises “200+ Supported Tools.” A direct count of the parser modules in the dojo/tools directory of the GitHub repository shows more than 360 individual parsers as of late September 2026, covering container, DAST, SAST, cloud posture, and API-based scanners.
Can I run DefectDojo without Docker? DefectDojo can be installed directly on a host via Kubernetes Helm charts or manual Python/Django deployment, but Docker Compose is the officially documented and most common path for both evaluation and small production deployments, which is why this tutorial focuses on it.
What’s the difference between DefectDojo and a SIEM like Wazuh? A SIEM such as Wazuh correlates live security telemetry (logs, network traffic, endpoint events) to detect active threats. DefectDojo manages the vulnerability lifecycle: it aggregates static scan findings from tools like Trivy, ZAP, and Nessus, tracks whether they’ve been fixed, and manages the remediation workflow. The two are complementary rather than overlapping, and many teams run both.
Does DefectDojo support Kubernetes for production deployment? Yes, the project maintains official Helm charts alongside the Docker Compose path documented in this tutorial, intended for teams that need the scaling and orchestration Kubernetes provides for the Celery worker and web-tier containers.
How does deduplication decide two findings are the same vulnerability? It depends on the scan type. Some parsers deduplicate on a combination of CVE identifier and affected component/version. Others, for scanners that don’t report a clean CVE, use a hash of the finding’s title and description. Both algorithms are configurable per scan type under Configuration → System Settings.
Is the March 2026 Trivy supply-chain attack still a risk if I’m running current versions? No. The malicious release and force-pushed tags were identified and remediated in March 2026, and the safe versions (Trivy v0.69.2/v0.69.3 or later, trivy-action v0.35.0 or later, setup-trivy v0.2.6 or later) have been current for months. The incident is worth understanding as a case study in why CI/CD tooling should be pinned to immutable references, not as an active threat against current installs.
Can DefectDojo import results from a tool that isn’t on the official parser list? The open-source edition requires a written parser for the scan type, though the community-maintained parser list covers the large majority of common tools. DefectDojo Pro’s Universal Parser accepts arbitrary JSON, CSV, or XML with manual field mapping, which covers most gaps without writing custom Python code against the open-source codebase.





