Documentation

Workbench Kubernetes Deployment Guide

FOSSID Workbench on Kubernetes (AWS EKS / Azure AKS)

This README explains what gets created, how high availability and scaling work, and how to deploy the full stack.
It also documents two recent additions:

  • External DB support (e.g., AWS RDS MySQL).
  • Config extensibility via a new fossidConf.extra block that appends custom settings to fossid.conf without editing the chart.

1) What resources are created and how they behave

Kubernetes resources (from the Helm chart)

Kind Name Purpose HA / Scaling Notes
Deployment fossid-workbench-leader Runs the “leader” pod (app role driven by FOSSID_IS_LEADER_NODE=true). 1 replica by design. PDB enforces 0 voluntary disruption.
Deployment fossid-workbench-followers Runs the “follower” pods (serve user traffic and run scans). replicas: N (default 2) + HPA up to 10 based on CPU 85%. PDB requires at least 2 available during voluntary disruptions.
Service fossid-workbench Type LoadBalancer for user traffic. Selector targets followers only (app=fossid-workbench, role=follower). Users never hit leader directly. Use loadBalancerSourceRanges to IP-restrict.
HPA fossid-workbench-followers Autoscale follower replicas on CPU. Default minReplicas = followers.replicas, maxReplicas = 10, target=85%.
PDB fossid-workbench-* Disruption controls for followers and leader. Followers: keep minAvailable=2. Leader: maxUnavailable=0.
ConfigMap fossid-conf Generates fossid.conf (mailer, base URL, DB connection). Set fossidConf.webapp_base_url. Use fossidConf.extra to append custom blocks.
StatefulSet (opt) fossid-mysql MariaDB for Workbench (in-cluster DB). 1 replica with an RWO disk. Only created when mysql.enabled=true.
Service (opt) fossid-mysql Headless service backing the MySQL StatefulSet. Internal only. Created when mysql.enabled=true.
Deployment + Service fossid-mailer (optional) Optional SMTP test sink (Mailhog). For non-prod/testing only.
PVCs wb-uploads, wb-backup, wb-logs, wb-config Shared RWX storage the pods mount. Bind to a class named shared-rwx (EFS on AWS, Azure Files on Azure).
Secret wb-env Application env (DB creds, tokens, etc.). Created from .Values.secretEnv by default; prefer External/Sealed Secrets in prod.

If you use an external DB (e.g., RDS), set mysql.enabled=false. The chart won’t create the MySQL StatefulSet/Service and will connect to the external DB using the env vars from wb-env.

Storage classes expected by the chart

  • MySQL (RWO): mysql.storageClassRWO
    • AWS: typically gp3 (EBS CSI).
    • Azure: set to a Disk class such as managed-csi (Azure Disk CSI).
  • Shared RWX: a class named shared-rwx.
    • AWS: EFS CSI.
    • Azure: Azure Files CSI.

2) External Database (e.g., AWS RDS MySQL)

You can run Workbench against an external MySQL-compatible database (MySQL 8.x recommended).

Values to toggle

# Use external DB (e.g., AWS RDS)
mysql:
  enabled: false

database:
  external:
    enabled: true
    host: "wb-mysql.xxxxxx.eu-central-1.rds.amazonaws.com"
    port: 3306
    dbName: "workbenchdb"
    user: "fossiduser"
    # one of:
    password: ""             # (dev/testing; see secretEnv below)
    existingSecretName: ""   # (prod)
    existingSecretKey: "password"

The chart reads DB connection info from the wb-env Secret. A simple in-chart template is provided (templates/secret-wb-env.yaml) that creates wb-env from .Values.secretEnv for labs. In production, you should manage wb-env using External Secrets or Sealed Secrets and avoid putting secrets into Helm values.

Required environment variables (mapped in wb-env)

Key Purpose Example / Notes
MYSQL_HOST DB hostname e.g., RDS endpoint xxx.rds.amazonaws.com
MYSQL_USER Application DB user e.g., fossiduser
MYSQL_PASSWORD Password for MYSQL_USER  
FOSSID_MYSQL_DATABASE Database name e.g., workbenchdb
MYSQL_ROOT_USER Admin user used by entrypoint tasks In RDS, use the RDS master user (not literally root).
MYSQL_ROOT_PASSWORD Password for MYSQL_ROOT_USER Must match the admin user above.

Why ROOT user vars? The application’s startup scripts perform administrative tasks (e.g., migrate/seed). In RDS, there is no root login; use your RDS master user for both MYSQL_ROOT_USER and MYSQL_ROOT_PASSWORD. They can be the same as MYSQL_USER if that user has sufficient privileges.

Example secretEnv for RDS

secretEnv:
  MYSQL_HOST: "wb-mysql.xxxxxx.eu-central-1.rds.amazonaws.com"
  MYSQL_USER: "fossiduser"
  MYSQL_PASSWORD: "fossidpass"

  # Admin creds used by startup scripts (use your RDS master user/password)
  MYSQL_ROOT_USER: "fossiduser"
  MYSQL_ROOT_PASSWORD: "fossidpass"

  FOSSID_MYSQL_DATABASE: "workbenchdb"

RDS setup checklist (Terraform hints)

  • Subnet group in private subnets.
  • Security Group that allows 3306 from EKS node SG.
  • MySQL 8.x parameter group; optionally set sql_mode=NO_ENGINE_SUBSTITUTION.
  • If you enforce TLS (require_secure_transport=ON), ensure the app/client is configured for TLS; otherwise keep it off (default is off).

Connectivity tests (from within the cluster)

# DNS resolution (from a Workbench pod)
kubectl -n workbench exec deploy/fossid-workbench-leader -- getent hosts $MYSQL_HOST

# SQL test using a throwaway pod
PW=$(kubectl -n workbench get secret wb-env -o jsonpath='{.data.MYSQL_PASSWORD}' | base64 -d)
kubectl -n workbench run dbtest --rm -it --image=alpine:3.19 --restart=Never -- \
  sh -lc 'apk add --no-cache mysql-client >/dev/null; mysql -h '"$MYSQL_HOST"' -u '"$MYSQL_USER"' -p"'"$PW"'" -e "SELECT 1" '"$FOSSID_MYSQL_DATABASE"

Common errors:

  • ERROR 2005 (HY000): Unknown server host → DNS/SG/VPC routing.
  • ERROR 1045 (28000): Access denied → wrong user/password or insufficient privileges; in RDS, ensure MYSQL_ROOT_* are set to a valid admin user.

3) fossid.conf configuration and extra extension point

The chart renders /fossid/etc/fossid.conf from a ConfigMap template, then runs envsubst in an init container so ${VAR} references resolve from wb-env.

Base template (snippet):

apiVersion: v1
kind: ConfigMap
metadata:
  name: fossid-conf
data:
  fossid.conf: |
    [WebApp]
    webapp_enable_email_sending=
    webapp_mailer_transport=
    webapp_mailer_host=
    webapp_mailer_port=
    webapp_mailer_sender_address=
    webapp_base_url=''
    webapp_db_server=${MYSQL_HOST}
    webapp_db_database=${FOSSID_MYSQL_DATABASE}
    webapp_db_username=${MYSQL_USER}
    webapp_db_password=${MYSQL_PASSWORD}
    webapp_db_port=3306

Use fossidConf.extra to append any custom blocks

fossidConf:
  webapp_base_url: "https://workbench.example.com/index.php"
  webapp_enable_email_sending: 1
  webapp_mailer_transport: smtp
  webapp_mailer_host: fossid-mailer
  webapp_mailer_port: 1025
  webapp_mailer_sender_address: example@fossid.com
  webapp_db_port: 3306

  # Appended verbatim after the base config (and processed by envsubst)
  extra: |
    [OAuth]
    oauth_enabled=1
    oauth_provider=azuread
    oauth_client_id=${OAUTH_CLIENT_ID}
    oauth_client_secret=${OAUTH_CLIENT_SECRET}
    oauth_tenant_id=${OAUTH_TENANT_ID}

Add the matching secrets to secretEnv (or an external Secret):

secretEnv:
  OAUTH_CLIENT_ID: "xxx"
  OAUTH_CLIENT_SECRET: "yyy"
  OAUTH_TENANT_ID: "zzz"

Note: Because we run envsubst on the whole file, any ${VAR} in extra will be expanded. If you want to restrict expansion, change the init command to only substitute a whitelist of vars, e.g.:

envsubst '${MYSQL_HOST} ${MYSQL_USER} ${MYSQL_PASSWORD} ${FOSSID_MYSQL_DATABASE}' \
  < /readonly/fossid.conf > /writable/fossid.conf

(Optional) Force rollout on config change

If you want pods to roll automatically when fossid.conf changes, add a checksum annotation to both Deployments under .spec.template.metadata.annotations:

checksum/fossid-conf: 

4) Version lock file

The chart ensures an empty /fossid/version.lock exists by creating it in an init container and mounting it via subPath:

  • Init container ensure-version-lock creates /writable/version.lock if missing.
  • The main container mounts the same PVC path at /fossid/version.lock with subPath: version.lock.

This allows the application’s startup logic to detect the current app version vs. the lock and perform upgrades.


5) How to deploy

A. Prerequisites

  • A Kubernetes cluster (EKS/AKS) with:
    • EBS/EFS CSI on AWS or Disk/Files CSI on Azure.
    • StorageClasses: mysql.storageClassRWO (for in-cluster DB) and shared-rwx.
  • kubectl, helm.
  • An image pull secret (e.g., quay-creds) in the target namespace.
  • A wb-env Secret (either provided via .Values.secretEnv or externally).

B. Install / upgrade

Edit values as needed (base URL, image pull secret, DB settings), then:

helm upgrade --install workbench ./workbench-chart -n workbench --create-namespace \
  --set imagePullSecrets[0].name=quay-creds \
  --set fossidConf.webapp_base_url="https://workbench.example.com/index.php"

Verify:

kubectl -n workbench get pods,svc,pvc

If exposing the Service directly, optionally restrict access:

service:
  loadBalancerSourceRanges:
    - "203.0.113.10/32"

6) Troubleshooting quick hits

  • Pods Running but not Ready; logs show DB errors
    • Unknown server host → check MYSQL_HOST (DNS), SGs, routing.
    • Access denied for user → verify MYSQL_USER/PASSWORD; for admin ops ensure MYSQL_ROOT_USER/PASSWORD point to a valid admin (RDS master user).
  • PVC Pending
    • Ensure gp3 (AWS) or managed-csi (Azure) exists for RWO; ensure shared-rwx exists for RWX.
  • Config changes not applied
    • Because config is rendered to a PVC by init containers, pods don’t auto-restart on ConfigMap updates. Use the checksum annotation (see above) or manually restart: kubectl rollout restart deploy/<name>.

7) Security notes

  • Avoid putting real secrets into .Values.secretEnv committed to Git.
  • Prefer External Secrets (AWS Secrets Manager / Azure Key Vault) or Sealed Secrets for wb-env.
  • Restrict fossid-workbench Service exposure with loadBalancerSourceRanges or put it behind Ingress/WAF.
  • Limit EKS/AKS API exposure to your admin CIDRs.

8) Azure mapping summary

  • AKS + Azure Disk CSI (RWO) + Azure Files CSI (RWX).
  • StorageClasses:
    • mysql.storageClassRWO: managed-csi (example).
    • shared-rwx for Azure Files (or let the chart create it with storage.createStorageClasses=true and storage.azure.skuName).
  • Manage secrets via External Secrets backed by Key Vault where possible.

9) Example values snippets

A. External DB (AWS RDS) + OAuth extra

cloud: aws

mysql:
  enabled: false

database:
  external:
    enabled: true
    host: "wb-mysql.xxxxxx.eu-central-1.rds.amazonaws.com"
    port: 3306
    dbName: "workbenchdb"
    user: "fossiduser"

secretEnv:
  MYSQL_HOST: "wb-mysql.xxxxxx.eu-central-1.rds.amazonaws.com"
  MYSQL_USER: "fossiduser"
  MYSQL_PASSWORD: "fossidpass"
  MYSQL_ROOT_USER: "fossiduser"
  MYSQL_ROOT_PASSWORD: "fossidpass"
  FOSSID_MYSQL_DATABASE: "workbenchdb"

fossidConf:
  webapp_base_url: "https://workbench.example.com/index.php"
  extra: |
    [OAuth]
    oauth_enabled=1
    oauth_provider=azuread
    oauth_client_id=${OAUTH_CLIENT_ID}
    oauth_client_secret=${OAUTH_CLIENT_SECRET}
    oauth_tenant_id=${OAUTH_TENANT_ID}

B. In-cluster MySQL (MariaDB) + defaults

cloud: aws

mysql:
  enabled: true
  storageClassRWO: gp3
  size: 5Gi

secretEnv:
  MYSQL_HOST: "fossid-mysql"
  MYSQL_USER: "fossiduser"
  MYSQL_PASSWORD: "fossidpass"
  MYSQL_ROOT_PASSWORD: "rootpass"
  FOSSID_MYSQL_DATABASE: "workbenchdb"

fossidConf:
  webapp_base_url: "https://workbench.example.com/index.php"

10) Appendix – managing wb-env externally

External Secrets (AWS Secrets Manager) example:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: wb-env
  namespace: workbench
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: ClusterSecretStore
    name: aws-secretsmanager
  target:
    name: wb-env
    creationPolicy: Owner
  data:
    - secretKey: MYSQL_HOST
      remoteRef: { key: /workbench/db, property: host }
    - secretKey: MYSQL_USER
      remoteRef: { key: /workbench/db, property: user }
    - secretKey: MYSQL_PASSWORD
      remoteRef: { key: /workbench/db, property: password }
    - secretKey: MYSQL_ROOT_USER
      remoteRef: { key: /workbench/db, property: adminUser }
    - secretKey: MYSQL_ROOT_PASSWORD
      remoteRef: { key: /workbench/db, property: adminPassword }
    - secretKey: FOSSID_MYSQL_DATABASE
      remoteRef: { key: /workbench/db, property: name }

Final reminders

  • Always set a real fossidConf.webapp_base_url or you’ll be redirected to a placeholder.
  • If you switch between in-cluster DB and external DB, make sure mysql.enabled and database.external.enabled reflect that choice and the wb-env secret matches.
  • Keep secrets out of Git wherever possible; use your platform’s secret manager with External Secrets.