Skip to content

This is the Experimental version (Latest). It is under active development and may change. For the most reliable documentation, use the version selector in the top-right to switch to Stable, or click here to go to the Stable version's homepage.

DefectDojo Installation

Last updated:

DefectDojo is a Centralised Vulnerability Management


  • Security Context Configration with Official DefectDojo Helm Chart is not applicatable
    • As DefectDojo Container Images are not yet implement run as non-root user and requires Run as Root/Run with Privilege Exceptions.

DefectDojo will be installed on 🟢 Management Kubernetes Cluster

  • 📥Ingress Service provided as Kubernetes Ingress Class (IngressClass)
  • 🛡️TLS Certificate for DefectDojo provided as Kubernetes Secret
    • DefectDojo will be exposed as HTTPS with Kubernetes Ingress.

DefectDojo depends on multiple dependencies to be up and running but only the following will be provisioned and/or managed by you.

  • 📦PostgreSQL SQL Database ; Deploy external/dedicated instance of Database.
    • In case will be installed on Kubernetes Cluster.
      • Will require 💿Persistence Storage as Kubernetes Storage Class (StorageClass)

Once finished everything, these are systems that up and running.

No.ToolDescriptionKubernetes NamespaceKubernetes Cluster
1.PostgreSQL for DefectDojoDatabase System for DefectDojodevsecops-system🟢 Management
2.DefectDojoCentralised Vulnerability Managementdevsecops-system🟢 Management

Ensure you have defined and loaded your Global Shell Variables as described in Shell Variables.

  1. Connect to 🟢 Management Kubernetes Cluster ; i.e w/ Kubeconfig File

    Ensure you have defined and loaded your Global Shell Variables as described in Shell Variables.

    Terminal window
    source $BASE_WORKING_DIR/shell-values/kubernetes/management_cluster.vars.sh
  2. Export Required Shell Variables

    Ensure DEFECTDOJO_DOMAIN, DEFECTDOJO_ADMIN_USERNAME, DEFECTDOJO_ADMIN_PASSWORD, and DEFECTDOJO_ADMIN_EMAIL are defined as per the Shell Variables guide.

    Ensure K8S_INTERNAL_DOMAIN, K8S_INGRESSCLASS_NAME, K8S_STORAGECLASS_NAME, K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME are defined as per the Shell Variables guide.

    Ensure KEYCLOAK_DOMAIN, KEYCLOAK_REALM are defined as per the Shell Variables guide.

  3. Create Kubernetes Secret for 🛡️ TLS Certificate for DefectDojo in Namespace devsecops-system.

    Kubernetes Ingress for DefectDojo will associate TLS Certificate with Kubernetes Secret named $K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME.

    Create one using from .crt and .key file.

    Terminal window
    kubectl create secret tls $K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME \
    --cert=/path/to/cert/file --key=/path/to/key/file \
    --namespace devsecops-system

    💡 Should return secret/... created message.

  1. Set PostgreSQL Information

    • Database Name: defectdojo (Defined in manifest)
    • Database User: defectdojo (Defined in manifest)
    • Database Password: ${DEFECTDOJO_POSTGRES_USER_PASSWORD} (Previously exported)
    • Postgres Superuser Password: ${DEFECTDOJO_POSTGRES_SUPERUSER_PASSWORD} (Previously exported)
  2. Provision PostgreSQL for DefectDojo

    The following manifest defines the entire PostgreSQL system, including credentials and the CNPG cluster.

    Terminal window
    cat <<EOF > $BASE_WORKING_DIR/kubernetes-manifests/defectdojo-postgres.yaml
    ---
    # DefectDojo Postgres Superuser Credentials
    apiVersion: v1
    kind: Secret
    type: kubernetes.io/basic-auth
    metadata:
    name: defectdojo-postgres-superuser
    namespace: devsecops-system
    stringData:
    username: postgres
    password: "${DEFECTDOJO_POSTGRES_SUPERUSER_PASSWORD}"
    ---
    # DefectDojo Application User Credentials
    apiVersion: v1
    kind: Secret
    type: kubernetes.io/basic-auth
    metadata:
    name: defectdojo-postgres-user
    namespace: devsecops-system
    stringData:
    username: defectdojo
    password: "${DEFECTDOJO_POSTGRES_USER_PASSWORD}"
    ---
    # S3 Credentials for Postgres Backups (to SeaweedFS HA)
    apiVersion: v1
    kind: Secret
    type: Opaque
    metadata:
    name: defectdojo-postgres-s3-secret
    namespace: devsecops-system
    stringData:
    S3_ACCESS_KEY: "postgres-backup"
    S3_SECRET_KEY: "${SEAWEEDFS_HA_S3_POSTGRES_BACKUP_PASSWORD}"
    ---
    apiVersion: barmancloud.cnpg.io/v1
    kind: ObjectStore
    metadata:
    name: defectdojo-postgres-backup
    namespace: devsecops-system
    spec:
    # This resource defines the backup destination for the Barman Cloud Plugin
    configuration:
    destinationPath: s3://postgres-backups/
    endpointURL: "http://${SEAWEEDFS_HA_API_INTERNAL_DOMAIN}"
    s3Credentials:
    accessKeyId:
    name: defectdojo-postgres-s3-secret
    key: S3_ACCESS_KEY
    secretAccessKey:
    name: defectdojo-postgres-s3-secret
    key: S3_SECRET_KEY
    wal:
    compression: gzip
    data:
    compression: gzip
    # Retention policy for backups and WALs handled by the plugin
    retentionPolicy: "30d"
    ---
    apiVersion: postgresql.cnpg.io/v1
    kind: Cluster
    metadata:
    name: defectdojo-postgres
    namespace: devsecops-system
    labels:
    app.kubernetes.io/name: defectdojo-postgres
    app.kubernetes.io/part-of: defectdojo
    spec:
    instances: 1
    # Image configuration (Postgres 15)
    imageName: ghcr.io/cloudnative-pg/postgresql:15
    # Enable the Barman Cloud Plugin for backups
    plugins:
    - name: barman-cloud.cloudnative-pg.io
    isWALArchiver: true
    parameters:
    serverName: "defectdojo-postgres"
    barmanObjectName: "defectdojo-postgres-backup"
    # Storage Configuration
    storage:
    size: 5Gi
    storageClass: "${K8S_STORAGECLASS_NAME}"
    walStorage:
    size: 2Gi
    storageClass: "${K8S_STORAGECLASS_NAME}"
    # Bootstrap Configuration
    bootstrap:
    initdb:
    database: defectdojo
    owner: defectdojo
    secret:
    name: defectdojo-postgres-user
    # Superuser credentials (root/postgres)
    superuserSecret:
    name: defectdojo-postgres-superuser
    # Backup Configuration (Barman Cloud Plugin will automatically detect matching ObjectStore)
    backup:
    {}
    # Resources
    resources:
    requests:
    memory: "256Mi"
    cpu: "200m"
    limits:
    memory: "1Gi"
    cpu: "1000m"
    EOF
    Terminal window
    kubectl apply -f $BASE_WORKING_DIR/kubernetes-manifests/defectdojo-postgres.yaml
  3. Check for PostgreSQL for DefectDojo Readiness - MUST be Ready and Running.

    Get Pod Status - PostgreSQL for DefectDojo

    Terminal window
    kubectl get pods -n devsecops-system

    💡 PostgreSQL for DefectDojo Pods MUST be Running

    NAME READY STATUS RESTARTS AGE
    ... (deducted)
    defectdojo-postgres-1 2/2 Running 0 XdXh
  1. DefectDojo Entrypoint Domain

    • Domain: ${DEFECTDOJO_DOMAIN} (Previously exported)
  2. DefectDojo Initial Admin Account

    • Admin Username: ${DEFECTDOJO_ADMIN_USERNAME} (Previously exported)
    • Admin Password: ${DEFECTDOJO_ADMIN_PASSWORD} (Previously exported)
    • Admin Email: ${DEFECTDOJO_ADMIN_EMAIL} (Previously exported)
  3. Configure OIDC with Keycloak

    Ensure you have followed the Procuring Keycloak Credentials guide to obtain the client-id, client-secret, and signing-public-key for DefectDojo.

    Terminal window
    cat <<EOF > $BASE_WORKING_DIR/kubernetes-manifests/defectdojo-oidc.yaml
    apiVersion: v1
    kind: Secret
    type: Opaque
    metadata:
    name: defectdojo-oidc-configurations
    stringData:
    client-id: ${DEFECTDOJO_OIDC_CLIENT_ID}
    client-secret: ${DEFECTDOJO_OIDC_CLIENT_SECRET}
    authorization-endpoint: https://${KEYCLOAK_DOMAIN}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/auth
    access-token-endpoint: https://${KEYCLOAK_DOMAIN}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token
    signing-public-key: ${KEYCLOAK_SIGNING_PUBKEY}
    EOF

    Apply the OIDC configuration:

    Terminal window
    kubectl apply --namespace devsecops-system \
    -f $BASE_WORKING_DIR/kubernetes-manifests/defectdojo-oidc.yaml
  4. Create Helm Values Configurations

    Terminal window
    cat <<EOF > $BASE_WORKING_DIR/helm-values/defectdojo-full-values.yaml
    ---
    ## Application: Exposed Hostname (Ingress)
    host: "${DEFECTDOJO_DOMAIN}"
    site_url: "https://${DEFECTDOJO_DOMAIN}"
    ## Application: Configure RabbitMQ
    ## NOTE: Community Deprecate RabbitMQ on 2024-Q1 Updates, move to Redis; Retain for Older Helm Chart Version DefectDojo Deployment, Can be removed later
    ## See On: https://github.com/DefectDojo/django-DefectDojo/discussions/9690
    createRabbitMqSecret: true
    rabbitmq:
    ulimitNofiles: "" ## OPSTELLA_CUSTOMIZE: Eliminate ulimit Problem
    clusterDomain: "${K8S_INTERNAL_DOMAIN}"
    ## Application: Configure Redis
    createRedisSecret: true
    redis:
    image:
    repository: bitnamilegacy/redis
    clusterDomain: "${K8S_INTERNAL_DOMAIN}"
    master:
    persistence:
    storageClass: "${K8S_STORAGECLASS_NAME}"
    ## Kubernetes/Application: Authentication
    createSecret: true
    admin:
    user: admin
    password: "${DEFECTDOJO_ADMIN_PASSWORD}"
    mail: "${DEFECTDOJO_ADMIN_EMAIL}"
    firstName: Defectdojo
    lastName: Administrator
    ## Application: Application Initializer ; For first time ONLY
    initializer:
    ## should be false after initial installation was performed
    run: true
    securityContext:
    enabled: true
    djangoSecurityContext:
    # django dockerfile sets USER=1001
    runAsUser: 1001
    runAsNonRoot: true
    seccompProfile:
    type: RuntimeDefault
    privileged: false
    allowPrivilegeEscalation: false
    capabilities:
    drop: ["ALL"]
    nginxSecurityContext:
    # nginx dockerfile sets USER=1001
    runAsUser: 1001
    runAsNonRoot: true
    seccompProfile:
    type: RuntimeDefault
    privileged: false
    allowPrivilegeEscalation: false
    capabilities:
    drop: ["ALL"]
    django:
    ## Kubernetes: Expose through Kubernetes Ingress
    ingress:
    enabled: true
    ingressClassName: "${K8S_INGRESSCLASS_NAME}"
    annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/proxy-body-size: "0"
    ## OPSTELLA_CUSTOMIZE: Fix Heavy DefectDojo Finding Uploads Failed Due to Timeout
    nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"
    activateTLS: true
    secretName: "${K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME}"
    ## Kubernetes/Application: Database Configuration
    database: postgresql
    createPostgresqlSecret: false
    postgresql:
    enabled: false
    postgresServer: defectdojo-postgres-rw.devsecops-system.svc
    auth:
    database: defectdojo
    username: defectdojo
    existingSecret: defectdojo-postgres-user
    secretKeys:
    adminPasswordKey: username
    userPasswordKey: password
    ## OPSTELLA_CUSTOMIZE/Application: Extra Configurations:
    ## - OIDC with Keycloak
    extraConfigs:
    DD_SECURE_SSL_REDIRECT: 'True'
    DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_ENABLED: 'True'
    DD_SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT: "Sign in with Opstella"
    ## OPSTELLA_CUSTOMIZE/Kubernetes/Application: Extra Environment Variables:
    ## - OIDC with Keycloak: Related Values for Setup
    extraEnv:
    - name: DD_SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY
    valueFrom:
    secretKeyRef:
    name: defectdojo-oidc-configurations
    key: signing-public-key
    - name: DD_SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL
    valueFrom:
    secretKeyRef:
    name: defectdojo-oidc-configurations
    key: authorization-endpoint
    - name: DD_SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL
    valueFrom:
    secretKeyRef:
    name: defectdojo-oidc-configurations
    key: access-token-endpoint
    - name: DD_SOCIAL_AUTH_KEYCLOAK_KEY
    valueFrom:
    secretKeyRef:
    name: defectdojo-oidc-configurations
    key: client-id
    - name: DD_SOCIAL_AUTH_KEYCLOAK_SECRET
    valueFrom:
    secretKeyRef:
    name: defectdojo-oidc-configurations
    key: client-secret
    EOF
  1. Add DefectDojo Helm Repository

    Terminal window
    helm repo add defectdojo https://raw.githubusercontent.com/DefectDojo/django-DefectDojo/helm-charts
    helm repo update
  2. Install DefectDojo

    • Install a Helm Release with specific Helm Chart Version --version 1.6.179 (App Version: 2.44.3)

      Terminal window
      helm upgrade --install defectdojo defectdojo/defectdojo --version 1.6.179 \
      --namespace devsecops-system \
      --set initializer.run=true \
      -f $BASE_WORKING_DIR/helm-values/defectdojo-full-values.yaml
  1. Get Pod Status - DefectDojo

    Terminal window
    kubectl get pods -n devsecops-system

    DefectDojo Pods should be Running

    NAME READY STATUS RESTARTS AGE
    ... (deducted)
    defectdojo-celery-beat-XXXXX-YYYY 1/1 Running 0 XdXh
    defectdojo-celery-worker-XXXXX-YYYY 1/1 Running 0 XdXh
    defectdojo-django-XXXXX-YYYY 2/2 Running 0 XdXh
    defectdojo-redis-master-0 1/1 Running 0 XdXh
  2. Visit https://${DEFECTDOJO_DOMAIN}

    • It should be accessible.
    • TLS Certificate should be valid and coresponding to your TLS Certificate Installed

  3. Login with Users

Will be used for Integrating with Opstella

  1. Login with Initial Admin Account

    • Username: defectdojo
    • Password: $DEFECTDOJO_ADMIN_PASSWORD
  2. Go to Top Right User Menu > API v2 Key

  3. Save the one starts with Token abc123...

  4. Save to Shell variables for future use $BASE_WORKING_DIR/shell-values/tools/defectdojo.vars.sh with DEFECTDOJO_TOKEN variable name

defectdojo-celery-beat stuck in CrashLoopBackOff and never recovers

Section titled “defectdojo-celery-beat stuck in CrashLoopBackOff and never recovers”

Symptom — defectdojo-celery-beat is 0/1 CrashLoopBackOff with a very high RESTARTS count, while every other DefectDojo Pod is Running. The Helm Release still reports deployed.

Terminal window
kubectl logs -n devsecops-system -l defectdojo.org/component=celery,defectdojo.org/subcomponent=beat
ERROR: Pidfile (/var/run/defectdojo/celery-beat.pid) already exists.
Seems we're already running? (pid: 1)

Cause — The Helm Chart mounts an emptyDir Volume at /run/defectdojo (/var/run is a symlink to /run), and Celery Beat writes its pidfile there. An emptyDir lives for the lifetime of the Pod, not the Container. If Celery Beat is ever killed uncleanly (OOM, Node pressure, SIGKILL, an API Server outage), the pidfile is left behind, and every Container restart inside the same Pod reads the stale pidfile, assumes another Beat is already running, and exits with code 73. The restart loop can never clear itself.

Fix — Delete the Pod, so its emptyDir is discarded with it. Restarting the Container, or kubectl rollout restart, is not enough.

Terminal window
kubectl delete pod -n devsecops-system -l defectdojo.org/component=celery,defectdojo.org/subcomponent=beat

💡 The new Pod should reach 1/1 Running, with beat: Starting... in its Logs.

Finished?

Use the below navigation to proceed