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.extrablock that appends custom settings tofossid.confwithout 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 fromwb-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).
- AWS: typically
- 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-envSecret. A simple in-chart template is provided (templates/secret-wb-env.yaml) that createswb-envfrom.Values.secretEnvfor labs. In production, you should managewb-envusing 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
rootlogin; use your RDS master user for bothMYSQL_ROOT_USERandMYSQL_ROOT_PASSWORD. They can be the same asMYSQL_USERif 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, ensureMYSQL_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
envsubston the whole file, any${VAR}inextrawill 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-lockcreates/writable/version.lockif missing. - The main container mounts the same PVC path at
/fossid/version.lockwithsubPath: 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) andshared-rwx.
kubectl,helm.- An image pull secret (e.g.,
quay-creds) in the target namespace. - A
wb-envSecret (either provided via.Values.secretEnvor 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→ checkMYSQL_HOST(DNS), SGs, routing.Access denied for user→ verifyMYSQL_USER/PASSWORD; for admin ops ensureMYSQL_ROOT_USER/PASSWORDpoint to a valid admin (RDS master user).
- PVC Pending
- Ensure
gp3(AWS) ormanaged-csi(Azure) exists for RWO; ensureshared-rwxexists for RWX.
- Ensure
- 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>.
- 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:
7) Security notes
- Avoid putting real secrets into
.Values.secretEnvcommitted to Git. - Prefer External Secrets (AWS Secrets Manager / Azure Key Vault) or Sealed Secrets for
wb-env. - Restrict
fossid-workbenchService exposure withloadBalancerSourceRangesor 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-rwxfor Azure Files (or let the chart create it withstorage.createStorageClasses=trueandstorage.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_urlor you’ll be redirected to a placeholder. - If you switch between in-cluster DB and external DB, make sure
mysql.enabledanddatabase.external.enabledreflect that choice and thewb-envsecret matches. - Keep secrets out of Git wherever possible; use your platform’s secret manager with External Secrets.