Vulnerability Management
    Docker Compose

    Deploy OWASP DefectDojo on a VPS

    Self-host DefectDojo on a RamNode VPS — Django, PostgreSQL, Redis, and Celery workers behind nginx with TLS for unified vulnerability management.

    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

    shell
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y git curl

    Install Docker Engine and the Compose plugin from the official Docker repository:

    shell
    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker $USER

    Log out and back in so your group membership takes effect, then confirm:

    shell
    docker --version
    docker compose version

    DefectDojo 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

    shell
    git clone https://github.com/DefectDojo/django-DefectDojo
    cd django-DefectDojo
    ./docker/docker-compose-check.sh

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

    shell
    openssl rand -base64 32

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

    shell
        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

    shell
    docker compose build
    docker compose up -d

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

    shell
    docker compose logs -f initializer

    When it finishes, retrieve the generated admin password:

    shell
    docker compose logs initializer | grep "Admin password:"

    Save that password immediately and change it after your first login.

    Confirm the stack is healthy:

    shell
    docker compose ps

    Step 5: Reverse proxy and TLS with Caddy

    Caddy is the simplest way to get automatic HTTPS in front of DefectDojo. Install it:

    shell
    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 caddy

    Replace /etc/caddy/Caddyfile with:

    shell
    dojo.example.com {
        reverse_proxy 127.0.0.1:8080
    }

    Then reload:

    shell
    sudo systemctl restart caddy

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

    shell
          DD_SITE_URL: "https://dojo.example.com"
          DD_ALLOWED_HOSTS: "dojo.example.com"

    Recreate the affected containers:

    shell
    docker compose up -d

    Step 6: Firewall

    shell
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable

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

    1. Create a Product.
    2. Create an Engagement inside that Product.
    3. 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_ENABLED in production.

    Backups

    Two things must be backed up: the database and the media volume.

    shell
    # 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

    shell
    cd django-DefectDojo
    git pull
    docker compose build
    docker compose up -d

    Set 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 ps shows it healthy and that the port mapping is 127.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.