OWASP DefectDojo is a flagship open source vulnerability management and application security posture management (ASPM) platform. It ingests output from more than 200 security scanners, deduplicates findings, scores risk, and tracks remediation in one place. If you run SAST, DAST, SCA, container, or infrastructure scans across multiple tools, DefectDojo turns that noise into a single source of truth.
This guide walks through a production oriented Docker Compose deployment on a RamNode VPS running Ubuntu 24.04 LTS.
Why a RamNode VPS
DefectDojo is built on Django with PostgreSQL, Redis, Celery workers, and an nginx front end. That stack is comfortable on a mid sized KVM instance and gives you full control over where your vulnerability data lives, which matters when those findings describe exactly how to attack your own infrastructure.
Prerequisites
- A RamNode KVM VPS with at least 4 GB RAM and 2 vCPUs. The initializer and Celery workers are memory hungry during imports, so 6 to 8 GB is more comfortable for active use.
- At least 20 GB of disk for the OS, images, and the media volume. Allow more if you store threat models and large scan files.
- Ubuntu 24.04 LTS with a non root sudo user.
- A domain or subdomain (for example
dojo.example.com) with an A record pointing at your VPS IP. - Ports 80 and 443 open for the reverse proxy. The application itself only needs to listen locally.
Step 1: Prepare the system
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curlInstall Docker Engine and the Compose plugin from the official Docker repository:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USERLog out and back in so your group membership takes effect, then confirm:
docker --version
docker compose versionDefectDojo requires Docker 19.03.0 or newer and Compose 1.28.0 or newer, and the build uses BuildKit, which is enabled by default on current Docker releases.
Step 2: Clone the repository
git clone https://github.com/DefectDojo/django-DefectDojo
cd django-DefectDojo
./docker/docker-compose-check.shThe check script confirms your Docker and Compose versions are compatible before you build anything.
Step 3: Harden the compose file before building
The shipped docker-compose.yml is fully functional but is meant for evaluation, not production, until you customize it. Two changes matter most.
First, replace the default AES 256 encryption key. This key encrypts API credentials that DefectDojo stores to talk to external tools, so a default value is a real risk. Generate a strong key:
openssl rand -base64 32Open docker-compose.yml and find the DD_CREDENTIAL_AES_256_KEY value (the default looks like &91a*agLqesc*0DJ+2*bAbsUZfR*4nLw). Replace it with your generated key.
Second, bind the application to localhost only so it is reachable solely through your reverse proxy. Find the nginx service ports mapping and change it from 8080:8080 to:
ports:
- "127.0.0.1:8080:8080"For real workloads the documentation recommends a dedicated PostgreSQL server rather than the bundled container, which significantly improves performance. For a single node start the bundled database is fine, but plan to externalize it as usage grows.
Step 4: Build and start
docker compose build
docker compose up -dThe first run includes an initializer container that sets up the database and creates the admin user. It can take up to three minutes. Watch its progress:
docker compose logs -f initializerWhen it finishes, retrieve the generated admin password:
docker compose logs initializer | grep "Admin password:"Save that password immediately and change it after your first login.
Confirm the stack is healthy:
docker compose psStep 5: Reverse proxy and TLS with Caddy
Caddy is the simplest way to get automatic HTTPS in front of DefectDojo. Install it:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddyReplace /etc/caddy/Caddyfile with:
dojo.example.com {
reverse_proxy 127.0.0.1:8080
}Then reload:
sudo systemctl restart caddyCaddy will obtain and renew a Let's Encrypt certificate automatically. Because DefectDojo now sits behind TLS, set the site URL inside the app environment so generated links use HTTPS. Add the following to the uwsgi service environment in docker-compose.yml:
DD_SITE_URL: "https://dojo.example.com"
DD_ALLOWED_HOSTS: "dojo.example.com"Recreate the affected containers:
docker compose up -dStep 6: Firewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableThe PostgreSQL, Redis, and uWSGI ports stay internal to the Docker network and are never exposed.
Step 7: First login and an initial import
Browse to https://dojo.example.com, log in as admin, and change the password under your user profile. Then try a real import to confirm everything works end to end:
- Create a Product.
- Create an Engagement inside that Product.
- Add a Test and upload a scan file (a Trivy, Semgrep, or ZAP report works well).
DefectDojo parses the file, deduplicates against existing findings, and assigns risk scores. A typical result is several hundred raw findings collapsing to a much smaller set of unique vulnerabilities.
Production notes
- Email. DefectDojo can send notifications. RamNode VPS plans are not intended to run a mail server, so point DefectDojo at an external transactional email provider using the
DD_EMAIL_*environment variables rather than installing an MTA locally. Notifications to Slack or Microsoft Teams avoid email entirely and are often the better fit. - Celery tuning. Under heavy import load, adjust the worker autoscale and concurrency variables (
DD_CELERY_WORKER_AUTOSCALE_MAX,DD_CELERY_WORKER_CONCURRENCY) to match your vCPU count. - Disable debug toolbar. Never set
DD_DJANGO_DEBUG_TOOLBAR_ENABLEDin production.
Backups
Two things must be backed up: the database and the media volume.
# Database dump
docker compose exec postgres pg_dump -U defectdojo defectdojo > dojo-db-$(date +%F).sql
# Media volume (uploaded scan files, threat models)
docker run --rm -v django-defectdojo_media:/data -v $(pwd):/backup alpine \
tar czf /backup/dojo-media-$(date +%F).tar.gz -C /data .Automate both with a cron job and ship the output off the VPS.
Upgrading
cd django-DefectDojo
git pull
docker compose build
docker compose up -dSet DD_INITIALIZE=false in the environment if you want to guarantee the database and admin password are left untouched on restart after the initial setup.
Troubleshooting
- Initializer never prints a password. It is still running. Imports and migrations can take a few minutes on a smaller VPS; keep tailing the logs.
- 502 from Caddy. The nginx container is not up yet or is bound to the wrong address. Confirm
docker compose psshows it healthy and that the port mapping is127.0.0.1:8080:8080. - Out of memory during import. Large scans can exhaust a 4 GB instance. Resize the VPS or lower Celery concurrency.
Wrap up
You now have a TLS protected DefectDojo instance aggregating scanner output on your own RamNode VPS, with the database and media volume under regular backup. From here, wire your CI pipelines to push results through the REST API so findings land in DefectDojo automatically on every build.
