Archived ApherisFold documentation
This page is part of the archived ApherisFold and Hub documentation. For Apheris Foundry, see the Foundry overview.
Apheris Hub Kubernetes Deployment🔗
This guide covers deploying and configuring Apheris Hub on Kubernetes using Helm.
Prerequisites🔗
- A Kubernetes cluster in a recent version (>= 1.30)
- Helm CLI (>= v3.17)
- A PostgreSQL database
- A
ReadWriteManyvolume for the shared storage: pre-provisioned, or from a storage class that provisions one - An Ingress or Gateway Controller that enables network access to the Kubernetes cluster
- NVIDIA GPU support for GPU workloads on the Kubernetes cluster (required to run OpenFold3, Boltz-2, and Protenix)
1. Create a Namespace for the Apheris Hub🔗
kubectl create namespace apheris-hub
2. Create an image pull secret for the Apheris image registry🔗
The Hub needs registry credentials to pull the Apheris model and Foundry task
images and to discover Foundry module images. The chart reads them from one or
more Kubernetes image pull secrets that you create and reference in
hub.imagePullSecrets. At least one pull secret is required. The registry
resolver reads every listed secret, and the chart attaches them all to two
service accounts: the Hub account, which launcher pods run under, and the task
account, which Nextflow task pods run under. Each pod inherits the pull secrets
from its account, including different credentials for the same registry. The
task account has no role and mounts no API token, so a task container can't
reach the Kubernetes API.
When hub.serviceAccount.create=false, configure the existing service account
with every secret in hub.imagePullSecrets; the chart does not modify an
externally managed account. The chart still creates the task account.
Request your Apheris image registry credentials from https://www.apheris.com/applications/apherisfold or contact support@apheris.com.
Create a docker-registry secret from the username and password. Set
--docker-server to the bare registry host quay.io:
kubectl create secret docker-registry apheris-registry \
--namespace=apheris-hub \
--docker-server=quay.io \
--docker-username="your-registry-username" \
--docker-password="your-registry-password"
If you host the Apheris images in your own registry, create the pull secret for that registry instead.
Reference the secret in your Helm values file:
hub:
imagePullSecrets:
- apheris-registry
3. Add a secret with a PostgreSQL DSN🔗
You can add a DSN for an existing PostgreSQL database with:
kubectl create secret generic hub-db-dsn --from-literal=dsn=<existing_dsn> \
--namespace=apheris-hub
We recommend using the managed database offering of your cloud provider in its PostgreSQL flavor, for instance Amazon RDS for PostgreSQL (AWS), Google Cloud SQL for PostgreSQL (GCP) or Azure Database for PostgreSQL (Microsoft Azure).
Hub creates and uses the foundry PostgreSQL schema on startup, with its own
migration history. An existing Fold schema in the same database remains
untouched. Foundry starts with fresh application records; user records are
created independently when users sign in through the configured identity
provider. Existing Fold users and history are not copied.
The database role must be allowed to create the foundry schema, or an
administrator must create it beforehand and grant the role USAGE and CREATE
on that schema. The pg_trgm extension must be installed and accessible, or the
role must be allowed to install it.
4. Create apheris-hub-values.yaml with values for the helm release🔗
Find the complete values reference at Helm Chart Values Reference.
The following are lightly annotated values with placeholders:
hub:
# refer to section `2.` for the image pull secret
imagePullSecrets:
- apheris-registry
postgresDsnSecretName: hub-db-dsn
ingress:
className: <ingress_class_name>
hostname: <ingress_hostname>
# TLS Termination at the ingress controller level.
tls:
enabled: <true|false>
# Name of a secret that contains the certificate material in the
# format documented in https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
secretName: <tls_secret_name>
sharedStorage:
# The provisioner for this `storageClass` needs to
# support `ReadWriteMany` `accessMode`.
#
storageClass: <storage_class_that_supports_read_write_many>
# This is the space available for
# persisting prediction results.
#
# `50Gi` is the minimum size (and default)
# we recommend `500Gi` if you can make that happen.
#
size: 50Gi
Shared storage🔗
The chart provisions a shared PVC used for prediction inputs, outputs, and Foundry run work directories. The chart mounts this volume into the Hub coordinator and into the Nextflow launcher and task pods, so they can exchange run inputs, outputs, and work files.
The PVC is mounted at hub.workDir (/apheris/foundry by default):
jobs/<run_id>/ # new jobs, each with its own input/, output/, work/, etc.
weights/<weight_id>/<checkpoint> # deployed checkpoint file or directory
<run_id>/ # pre-upgrade runs, left at their original paths
The chart and API no longer create shared top-level input/ or output/
directories. Existing directories are not removed during an upgrade. Legacy
input/output root environment variables no longer cause startup filesystem
creation.
Deployment durably copies checkpoint bytes before publishing the weight record.
Copies survive deletion of the producing run. Because deployed weights were not
released before this storage change, existing rows are marked deleted and must
be redeployed. Deleting a weight hides it from new selections and retains its
bytes by default. physical_delete=true also removes the managed bytes when no
non-terminal run references the weight.
By default, hub.sharedStorage.annotations includes
helm.sh/resource-policy: keep. Helm uninstall retains the shared PVC (and the
chart-managed local PV, if used). This is not a backup: deleting the namespace,
PVC, or external storage can still destroy data. Operators must explicitly
delete retained resources when decommissioning the deployment. Upgrade an
existing release before uninstalling it to apply the retention annotation.
Use a storageClass whose provisioner supports the ReadWriteMany accessMode.
Volume ownership requirement🔗
The Hub coordinator and Foundry task pods run as the user and group in hub.podSecurityContext.runAsUser and runAsGroup, 65534 by default. Chart-managed directories on the shared PVC must be owned by that user before Foundry can create run work directories.
When using a dynamically provisioned PVC backed by an EFS Access Point whose OwnerUid and OwnerGid are those IDs, the CSI driver creates the root directory with the correct ownership and no further action is needed.
When binding to an existing PV (via hub.sharedStorage.existingVolumeName) or a statically provisioned EFS filesystem, root may own the PVC root. Set the following flag and the chart runs a one-time Helm hook Job on the next install or upgrade to correct ownership:
hub:
sharedStorage:
initVolumePermissions: true
The Job runs as root with only CHOWN/FOWNER capabilities added, creates the jobs/ and weights/ directories, hands them and the shared PVC root to runAsUser:runAsGroup, then deletes itself on success. Once ownership is correct you can remove the flag — it is safe to leave enabled but unnecessary after the first run.
Admission policy requirement
This hook requires runAsUser: 0 with CHOWN and FOWNER. If your cluster blocks that, fix the volume ownership with an administrative workflow that is permitted in your environment, then rerun the Helm upgrade.
This is a one-time step. Once the ownership is correct, the Hub creates and manages Foundry run directories on its own.
Authentication and Identity Providers🔗
Set the hub.auth.* values to match your identity provider and frontend configuration. The Authentication Setup guide explains the requirements for Auth0, Microsoft Entra, and Dex and shows how those settings map back to Helm values.
Custom CA Certificates🔗
If your identity provider or external services use TLS certificates signed by a custom Certificate Authority, configure the Hub to trust those certificates. The file will be mounted to /etc/ssl/certs/custom-ca.crt in the Hub container and automatically trusted alongside system CAs.
Create the ConfigMap🔗
Create or update a ConfigMap with your CA certificate (safe to re-run):
kubectl create configmap custom-ca-certs \
--from-file=ca.crt=/path/to/your-ca.crt \
-n apheris-hub --dry-run=client -o yaml | kubectl apply -f -
Mount the certificate🔗
Add to your values file to mount the custom CA into the Hub container:
hub:
extraVolumes:
- name: custom-ca
configMap:
name: custom-ca-certs
extraVolumeMounts:
- name: custom-ca
mountPath: /etc/ssl/certs/custom-ca.crt
subPath: ca.crt
readOnly: true
Apply the Helm upgrade🔗
Re-run your helm upgrade command so the pod picks up the new mount.
Verify the ConfigMap🔗
Check the CA data stored in the ConfigMap:
# Read the CA data from the ConfigMap into a local file
kubectl get configmap custom-ca-certs -n apheris-hub -o jsonpath='{.data.ca\.crt}' > /tmp/custom-ca.crt
# Inspect the certificate content
openssl x509 -in /tmp/custom-ca.crt -noout -subject -issuer
This confirms the CA content that will be mounted into the Hub pod.
Verify the live mount (optional)🔗
If your cluster allows ephemeral debug containers, you can examine the mounted file without changing the pod:
# Print container name(s) in the Hub pod (needed for --target)
kubectl get pod -n apheris-hub <hub-pod-name> -o jsonpath='{.spec.containers[*].name}'
# Start a debug container and read the mounted CA from the target container's root
kubectl debug -n apheris-hub -it pod/<hub-pod-name> --image=alpine:3.19 --target=<container-name> -- \
sh -c "cat /proc/1/root/etc/ssl/certs/custom-ca.crt" > /tmp/custom-ca.crt
# Inspect the certificate content copied from the pod
openssl x509 -in /tmp/custom-ca.crt -noout -subject -issuer
If debug containers are blocked by policy, rely on the ConfigMap check above and look for certificate-related errors in logs:
# Print container name(s) in the Hub pod (needed for -c)
kubectl get pod -n apheris-hub <hub-pod-name> -o jsonpath='{.spec.containers[*].name}'
# Print Hub container logs and filter for TLS/certificate errors
kubectl logs -n apheris-hub deployment/<hub-deployment-name> -c <container-name> | \
grep -i "certificate\|tls\|x509"
MSA Server Configuration🔗
MSA servers are deployment-managed and global. Administrators define them in Helm values, and users can only select one of the configured servers (or opt out and upload .a3m files manually).
Supported MSA server types:
| Provider | Type identifier | Notes |
|---|---|---|
| ColabFold | colabfold |
Supports self-hosted deployments and public servers |
| NVIDIA NIM ColabFold | nvidia-colabfold |
Requires a deployed NVIDIA NIM MSA Search service |
The msa.* timeout values only affect ColabFold and NVIDIA NIM ColabFold deployments:
msa:
enabled: true
# How often to check if the job is done (PENDING → RUNNING → COMPLETE)
pollInterval: "10s" # Lower = faster feedback, more API calls
# Per-request HTTP timeout for submit/status/download calls
requestTimeout: "10m" # Increase for slow networks or large downloads
When msa.enabled=true, you must configure msa.servers with at least one server.
Use defaultActive: true on exactly one server to define the deployment-level fallback server:
msa:
enabled: true
servers:
- name: "Public ColabFold"
type: colabfold
url: "https://api.colabfold.com"
defaultActive: true
config: {}
- name: "NVIDIA ColabFold"
type: nvidia-colabfold
url: "https://api.nim.example.com"
defaultActive: false
config:
numberOfSequences: "500"
eValue: "0.0001"
databases:
- "Uniref30_2302"
headers:
- name: "X-Api-Key"
valueFrom:
secretKeyRef:
name: "msa-auth"
key: "api-key"
- name: "X-Client-Id"
valueFrom:
configMapKeyRef:
name: "msa-shared-config"
key: "client-id"
- name: "X-Source"
value: "hub"
defaultActive is not a per-user preference. It is used by default for new users, and as fallback when a stored active selection cannot be resolved (for example after server removal or URL-identity change during deployment sync).
If a user explicitly disabled MSA usage, fallback is not applied for that user.
MSA Server Headers🔗
Use msa.servers[].headers to send provider-specific headers (for example API keys, client IDs, or metadata) with every request to that server.
When possible, source sensitive values from Kubernetes Secrets:
msa:
enabled: true
servers:
- name: "NVIDIA ColabFold"
type: nvidia-colabfold
url: "https://api.nim.example.com"
config:
numberOfSequences: "500"
headers:
- name: "X-Api-Key"
valueFrom:
secretKeyRef:
name: "msa-auth"
key: "api-key"
- name: "X-Client-Id"
valueFrom:
configMapKeyRef:
name: "msa-shared-config"
key: "client-id"
- name: "X-Source"
value: "hub"
Troubleshooting ColabFold:
- "Failed to check job status" errors → Increase
requestTimeout - Want faster progress updates → Decrease
pollInterval(minimum ~3s recommended)
MSA Server Egress🔗
With hub.networkPolicy.enabled=true, Foundry task pods get DNS egress only. The task pods that query an MSA server need egress to msa.servers[].url as well, so the chart renders an extra NetworkPolicy (<release>-foundry-task-msa) for the processes listed in hub.networkPolicy.msa.processNames.
A NetworkPolicy peer is an IP range, never a hostname, so the chart doesn't derive the egress from the server URLs: list each server's address range and port in hub.networkPolicy.msa.endpoints. The port is the one the URL names, otherwise 443 for https and 80 for http. Narrow the range where your network allows, or use 0.0.0.0/0 for a server whose addresses you can't pin. With MSA and the NetworkPolicies on, the chart fails to render while the list is empty:
hub:
networkPolicy:
enabled: true
msa:
endpoints:
# https://api.colabfold.com, by name: any address on its port
- cidr: 0.0.0.0/0
port: 443
# http://10.4.5.6:9000, by literal address
- cidr: 10.4.5.6/32
port: 9000
processNames holds nextflow.io/processName label values, which are Nextflow's sanitized process selectors: the scope chain with : replaced by _, so the MSA:FETCH_MSA_IMPL process is labelled MSA_FETCH_MSA_IMPL. The defaults cover the bundled workflows. A package release that renames the process stops the match and the MSA search then fails to connect, so read the label off a running task pod when adding an entry:
kubectl get pods -n <namespace> -L nextflow.io/processName
Foundry prediction templates🔗
The bundled predict workflow accepts optional CIF templates. The README that
the workflow package ships documents how to use them, along with the query
manifest and the result downloads, and changes with each package release. Hub
serves the README of every enabled workflow at
GET /api/v1/foundry/workflows/<id>/docs, for example
/api/v1/foundry/workflows/predict/docs, as JSON with the Markdown in
content.
Foundry workflow packages and compute classes🔗
Every Foundry workflow is a package: main.nf, modules.yaml, the contract
snapshot of each pinned module image, the compiled task wrappers, and its
README.md and SKILL.md. The Hub image bundles the Apheris packages under
workflow.root; customers deliver their own on workflow.mountVolume, which
the coordinator mounts read-only at workflow.mountRoot. Hub
validates both the same way when it starts, and a package that fails, for
example without modules.yaml or with a task that no compute class can serve,
is unavailable while the others keep working. Images, task requirements and
model metadata come from the packages, so this chart holds no task catalog and
Hub reads no registry to list workflows or models.
Keep the packages on a volume of their own, never on the shared PVC: task pods
mount that read-write, so a task could rewrite a package that a later run
executes. The chart rejects a workflow.mountRoot inside hub.workDir.
Earlier chart versions read mounted packages from /apheris/foundry/workflows
on the shared PVC; move them to their own volume and name it:
workflow:
mountVolume:
persistentVolumeClaim:
claimName: hub-workflows
The chart owns the compute classes under workflow.resourcePolicy. A task
names its class; the class caps the task's requests, can raise CPU and memory
requests with overrides floors, and places the task's pods:
workflow:
resourcePolicy:
version: 1
classes:
cpu:
limits: {cpus: 32, memory_mib: 131072, gpus: 0}
tolerations:
- {key: apheris.net/computation, operator: Exists, effect: NoSchedule}
gpu:
limits: {cpus: 8, memory_mib: 65536, gpus: 1}
max_forks: 1
gpu_resource: nvidia.com/gpu
shm_mib: 16384
node_selector: {nvidia.com/gpu.product: NVIDIA-L4}
tolerations:
- {key: nvidia.com/gpu, operator: Exists, effect: NoSchedule}
max_forks bounds each GPU task process within one run. The chart sets one;
a set value must be a positive integer, and omitting it keeps each workflow's
own default. Increase it only when the deployment has capacity for additional
whole-GPU pods.
It does not cap aggregate use across runs. Kubernetes schedules pods from their
resource requests; namespace quotas and node-pool limits govern shared capacity.
Foundry workflow exposure🔗
Configure workflow.workflows.<id> to control which discovered workflows users can access. The ID is the workflow package's directory name; adding an entry does not install a missing package or make an invalid package available.
| Setting | Behavior |
|---|---|
| No entry for a discovered workflow | Enabled, with the full Hub submission UI. |
enabled: true, uiMode: full |
Enabled, with the full Hub submission UI. This is also the default when either field is omitted. |
enabled: true, uiMode: cli-only |
Listed by the API and shown as a CLI only workflow card. Its details page shows the workflow graph and command snippets, but no input form, metadata fields, or Hub submit controls. CLI/API submission remains available. |
enabled: false |
Not exposed in the workflow catalog or available for new submissions through the Hub, including its CLI/API. This is not just a hidden UI card. |
For example:
workflow:
workflows:
predict:
enabled: true
uiMode: full
affinity-predict:
enabled: true
uiMode: cli-only
preprocess-structures:
enabled: false
Refer to workflow.workflows in the chart defaults (see Helm Chart Values Reference) for the workflows configured as CLI only. Other valid discovered workflows default to full UI exposure unless overridden. Supported uiMode values are full and cli-only; unsupported values fail configuration validation. These settings apply to bundled and mounted workflow packages alike.
Apply changes with a Helm upgrade. This controls workflow exposure, not per-user authorization. See the application guide for the user-facing navigation.
5. Install the helm release🔗
helm install apheris-hub oci://quay.io/apheris/hub-chart \
--namespace=apheris-hub \
--values=apheris-hub-values.yaml \
--wait \
--timeout=15m
The chart checks your values against its JSON schema and rejects any key the
schema doesn't declare, so a misspelled or removed key fails the install
rather than doing nothing. The error names the key and where it sits, for
example at '/hub': additional properties 'foundryEnabled' not allowed.
Plan for upgrade downtime
Upgrades recreate the Hub coordinator and temporarily make the Hub unavailable while its persistent volume is detached and reattached. Run upgrades during a maintenance window: wait for active jobs to finish and do not start new jobs until the upgrade completes, because active jobs may be interrupted.
6. Access the Apheris Hub installation🔗
You can now access your Apheris Hub installation via the configured ingress.
For most setups, the external hostname will be the value you configured under
hub.ingress.hostname.
Please do not hesitate to contact Apheris via e-mail in case you encounter any problems.
Helm Chart Values Reference🔗
| Key | Type | Default | Description |
|---|---|---|---|
| hub.affinity | object | {} |
Affinity rules |
| hub.artifactObserver.pollInterval | string | "10s" |
How often Hub checks active structural-finetune runs' epoch/result JSON files and emits user-scoped socket notifications. Dataset/checkpoint trees are not scanned. Shared PVC writes from task pods cannot reliably use filesystem notifications. Set 0s to disable. |
| hub.auth | object | {"allowedOrganizations":[],"audience":"","browserUrl":"","clientId":"","domain":"","enabled":false,"extraScopes":"","issuer":"","organizationCheckEnabled":false,"providerType":""} |
Authentication configuration (OIDC/Auth0/ForgeRock) |
| hub.auth.allowedOrganizations | list | [] |
Exact organization IDs admitted when organizationCheckEnabled is true. Requires auth enabled and a nonempty list; each ID must be nonempty without surrounding whitespace. |
| hub.auth.organizationCheckEnabled | bool | false |
Require the signed https://sso.apheris.com/org_id claim independently of providerType. Enable only after migration preflight and claim verification. |
| hub.auth.providerType | string | "" |
Provider type (supported values: "auth0", "forgerock", or empty string for generic OIDC) |
| hub.env | list | [] |
Additional environment variables |
| hub.extraVolumeMounts | list | [] |
Extra volume mounts (e.g., for custom CA certificates) |
| hub.extraVolumes | list | [] |
Extra volumes (e.g., for custom CA certificates) |
| hub.gatekeeper | object | {"enabled":false} |
Gatekeeper mutation that hardens pods to satisfy Pod Security Admission "restricted". Nextflow's k8s executor only sets a pod-level securityContext, so its task pods miss the container-level fields PSA restricted requires. This Assign injects them (idempotently, only where unset) across the release namespace so enforce=restricted can be turned on without breaking runs. Requires OPA Gatekeeper CRDs in the cluster. On Kubernetes 1.36 or later, hub.mutatingAdmissionPolicy hardens task pods without Gatekeeper. |
| hub.gatekeeper.enabled | bool | false |
Enable the Gatekeeper Assign mutation. Requires Gatekeeper installed (Assign/AssignMetadata CRDs present). Leave false if the cluster has no Gatekeeper or hardens task pods by other means. |
| hub.image.digest | string | nil |
Image digest (sha256). |
| hub.image.pullPolicy | string | "IfNotPresent" |
Image pull policy |
| hub.image.repository | string | "quay.io/apheris/hub" |
Container image repository |
| hub.image.tag | string | nil |
Overrides the image tag whose default is the chart appVersion |
| hub.image.tagSuffix | string | "" |
Appended to the image tag, whether tag or the chart appVersion. The oci-reconciler publishes the image of version 1.2.3 as 1.2.3-source-hub, so -source-hub with a repository it publishes to, such as quay.io/apheris/dev-catalog, keeps the image at the chart's version. |
| hub.imagePullSecrets | list | [] |
Image pull secrets (type kubernetes.io/dockerconfigjson) for private registries such as quay.io/apheris. At least one is required. The chart attaches all secrets to the service accounts of the launcher and task pods; module image resolution reads every secret's .dockerconfigjson in order. |
| hub.ingress | object | {"annotations":{},"className":"","defaultGatewayClassName":"default","enabled":true,"existingGatewayName":"","gatewayNamespace":"","hostname":"","ingressPath":"/","tls":{"enabled":false,"secretName":""},"type":"ingress"} |
Ingress configuration (common for both Gateway API and Ingress resources) |
| hub.ingress.annotations | object | {} |
Additional annotations |
| hub.ingress.className | string | "" |
IngressClass of the Ingress, or GatewayClass of the generated Gateway. Empty leaves the Ingress to the cluster's default IngressClass and gives the Gateway defaultGatewayClassName. |
| hub.ingress.defaultGatewayClassName | string | "default" |
GatewayClass of the generated Gateway when className is empty |
| hub.ingress.enabled | bool | true |
Enable ingress (Gateway API or Ingress resource) |
| hub.ingress.existingGatewayName | string | "" |
Existing gateway name (if not set, a new gateway will be created) |
| hub.ingress.gatewayNamespace | string | "" |
Namespace of the existing gateway, if different from the release namespace. Applies only with existingGatewayName: the chart creates its own gateway in the release namespace. |
| hub.ingress.hostname | string | "" |
Hostname for the Ingress or Gateway. Required for an Ingress with TLS. |
| hub.ingress.ingressPath | string | "/" |
Path prefix that the Ingress or HTTPRoute routes to the coordinator |
| hub.ingress.tls | object | {"enabled":false,"secretName":""} |
TLS configuration |
| hub.ingress.tls.enabled | bool | false |
Enable TLS |
| hub.ingress.tls.secretName | string | "" |
Secret with the TLS certificate. Required for the Gateway the chart creates; an Ingress without one uses the controller's default certificate. |
| hub.ingress.type | string | "ingress" |
Networking type (gateway, ingress) |
| hub.launcherImage | string | "" |
Launcher image. Defaults to the Hub image when empty. |
| hub.launcherPullPolicy | string | "IfNotPresent" |
Image pull policy for the launcher Jobs |
| hub.moduleRegistry | object | {"concurrency":256,"enabled":true,"resolveTimeout":"30s"} |
Resolve each package-pinned image digest in repositories accessible to this deployment. Versions and contracts remain owned by the package. |
| hub.moduleRegistry.concurrency | int | 256 |
Maximum in-flight requests per registry credential while resolving image choices. |
| hub.moduleRegistry.enabled | bool | true |
Resolve all image choices before publishing the startup catalog. Disable to use the package's original image references without registry discovery. |
| hub.moduleRegistry.resolveTimeout | string | "30s" |
Timeout per registry discovery/read. Each workflow gets a separate binding budget of this duration times the number of available registry credentials. Restart Hub to reload bindings. Set <= 0 to disable these timeouts. |
| hub.mutatingAdmissionPolicy | object | {"enabled":false} |
The built-in alternative to hub.gatekeeper: a Kubernetes MutatingAdmissionPolicy that hardens Foundry task pods to pass the Pod Security Admission "restricted" profile. On pods labelled nextflow.io/app: nextflow in the release namespace, it sets runAsNonRoot, a RuntimeDefault seccomp profile, allowPrivilegeEscalation=false and capabilities.drop=[ALL] wherever the pod leaves them unset. Requires Kubernetes 1.36 or later. |
| hub.mutatingAdmissionPolicy.enabled | bool | false |
Enable the MutatingAdmissionPolicy and its binding, both cluster scoped. Enable this or hub.gatekeeper; either one is enough. |
| hub.networkPolicy | object | {"apiServer":{"cidrs":[]},"dns":{"peers":[{"namespaceSelector":{"matchLabels":{"kubernetes.io/metadata.name":"kube-system"}}}],"ports":[53]},"enabled":false,"msa":{"endpoints":[],"processNames":["MSA_FETCH_MSA_IMPL","FETCH_AFFINITY_MSAS_FETCH_MSA_FETCH_MSA_IMPL"]}} |
NetworkPolicy for foundry launcher and task pods. |
| hub.networkPolicy.apiServer | object | {"cidrs":[]} |
Egress from the Foundry launcher pods to the Kubernetes API server, which the Nextflow k8s executor calls to create task pods. |
| hub.networkPolicy.apiServer.cidrs | list | [] |
Address ranges of the Kubernetes API server, reachable on ports 443 and 6443. Most CNIs match the API server's endpoint addresses after Service translation, so list those: kubectl get endpointslices -n default -l kubernetes.io/service-name=kubernetes. An empty list allows any address (0.0.0.0/0) on those ports. |
| hub.networkPolicy.dns | object | {"peers":[{"namespaceSelector":{"matchLabels":{"kubernetes.io/metadata.name":"kube-system"}}}],"ports":[53]} |
Egress from the Foundry launcher and task pods to the cluster DNS. |
| hub.networkPolicy.dns.peers | list | [{"namespaceSelector":{"matchLabels":{"kubernetes.io/metadata.name":"kube-system"}}}] |
NetworkPolicy peers that serve DNS. The default selects every pod in kube-system. NodeLocal DNSCache needs an ipBlock for its link-local address, such as 169.254.20.10/32, and OpenShift a namespaceSelector for openshift-dns. An empty list allows any address on the DNS ports. |
| hub.networkPolicy.dns.ports | list | [53] |
Ports the DNS pods listen on. The policy allows each over UDP and TCP. A NetworkPolicy matches the pod's port, not the Service's, so OpenShift, whose DNS pods listen on 5353, needs that port. |
| hub.networkPolicy.enabled | bool | false |
Enable the NetworkPolicy resources for foundry pipeline pods. Requires a CNI that enforces NetworkPolicy (Calico, Cilium, etc.). kind clusters need --config with kindnet replaced by Calico, or use the kindest/node image built with Calico. |
| hub.networkPolicy.msa | object | {"endpoints":[],"processNames":["MSA_FETCH_MSA_IMPL","FETCH_AFFINITY_MSAS_FETCH_MSA_FETCH_MSA_IMPL"]} |
Egress to the MSA servers for the task pods that query them. Only rendered when msa.enabled is true and processNames is not empty. |
| hub.networkPolicy.msa.endpoints | list | [] |
Destinations those pods may reach over TCP, one cidr and port per entry. Required when msa.enabled is true and processNames is not empty. A NetworkPolicy peer is an IP range, never a hostname, so list the address range each msa.servers[].url resolves to, or 0.0.0.0/0 for a server you can't pin, on the port the URL names, else 443 for https and 80 for http. |
| hub.networkPolicy.msa.processNames | list | ["MSA_FETCH_MSA_IMPL","FETCH_AFFINITY_MSAS_FETCH_MSA_FETCH_MSA_IMPL"] |
nextflow.io/processName label values of the task pods allowed to reach msa.servers[].url. The label is Nextflow's sanitized process selector: the scope chain with : replaced by _, so the MSA:FETCH_MSA_IMPL process is labelled MSA_FETCH_MSA_IMPL. The defaults cover the bundled workflows: predict, benchmark, and structural-finetune run the MSA search as MSA, the affinity workflows inside FETCH_AFFINITY_MSAS. A package release that renames the process stops the match; an empty list leaves the MSA egress policy out. |
| hub.nodeSelector | object | {} |
Node selector |
| hub.podAnnotations | object | {} |
Pod annotations |
| hub.podLabels | object | {} |
Pod labels. They override the chart's labels and labels, except the selector labels app.kubernetes.io/name and app.kubernetes.io/instance, which keep the pods attached to their Deployment. |
| hub.podSecurityContext | object | {"fsGroup":65534,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}} |
Pod security context |
| hub.postgresDsnSecretName | string | nil |
Name of a kubernetes secret containing a postgres DSN, needs a key dsn |
| hub.replicaCount | int | 1 |
Number of coordinator replicas: 1, or 0 to stop Hub. Hub runs as a single instance: it keeps realtime sessions in memory, and every replica would watch every active run. |
| hub.resources | object | {"limits":{"memory":"4Gi"},"requests":{"cpu":"500m","memory":"1Gi"}} |
Resource requests and limits of the coordinator container. Besides the API, the coordinator starts Nextflow, a JVM, to render workflow graphs, so leave the memory limit room for both. |
| hub.securityContext | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534} |
Container security context |
| hub.service | object | {"annotations":{},"nodePort":null,"port":8080,"type":"ClusterIP"} |
Service configuration |
| hub.service.annotations | object | {} |
Service annotations |
| hub.service.nodePort | int | nil |
Node port when type is NodePort. If null, Kubernetes picks a free one. |
| hub.service.port | int | 8080 |
Service port |
| hub.service.type | string | "ClusterIP" |
Service type |
| hub.serviceAccount | object | {"annotations":{},"create":true,"name":""} |
Service account configuration for the coordinator and the launcher pods. Nextflow task pods run under a separate account, which the chart always creates without a role or an API token. |
| hub.serviceAccount.annotations | object | {} |
Annotations to add to the service account |
| hub.serviceAccount.create | bool | true |
Specifies whether a service account should be created. An externally managed account must carry all hub.imagePullSecrets for launcher image pulls. |
| hub.serviceAccount.name | string | "" |
The name of the service account to use. Required when create is false; if not set and create is true, the chart names it after the release. |
| hub.sharedStorage | object | {"accessMode":"ReadWriteMany","annotations":{"helm.sh/resource-policy":"keep"},"existingVolumeName":null,"initVolumePermissions":false,"size":"50Gi","storageClass":""} |
Shared persistence for Foundry run inputs, outputs, work directories, and deployed model weights: the ReadWriteMany artifacts/work volume mounted into the coordinator and the Foundry launcher and task pods. |
| hub.sharedStorage.accessMode | string | "ReadWriteMany" |
Access mode for the shared PVC |
| hub.sharedStorage.annotations | object | {"helm.sh/resource-policy":"keep"} |
Annotations for the shared PVC. Keep the claim on Helm uninstall so runs and deployed checkpoints survive; deletion is an explicit operator action. |
| hub.sharedStorage.existingVolumeName | string | nil |
Existing PersistentVolume to bind to. If null, the PVC takes a volume of storageClass. |
| hub.sharedStorage.initVolumePermissions | bool | false |
Run a dedicated Job to create the chart-managed directories on the shared PVC and hand them to hub.podSecurityContext.runAsUser and runAsGroup, which it then requires. Enable this when binding to an existing or statically provisioned volume whose root or weights directories belong to root. Requires the cluster to permit runAsUser: 0 with CHOWN/FOWNER capabilities. On install the Job is a normal release resource so the PVC can be created first; a non-privileged coordinator init container waits for writable directories. On upgrade the Job runs as a pre-upgrade hook before the new coordinator starts. |
| hub.sharedStorage.size | string | "50Gi" |
Size of the shared PVC |
| hub.sharedStorage.storageClass | string | "" |
Storage class for the shared PVC. Empty renders storageClassName: "", which binds only a pre-provisioned volume without a class; name a class to have one provisioned dynamically. |
| hub.terminationGracePeriodSeconds | int | 30 |
Termination grace period in seconds |
| hub.tolerations | list | [] |
Tolerations |
| hub.webhookBaseURL | string | "" |
Base URL at which the Foundry launcher pods reach Hub to push Nextflow's weblog events in real time. Leave empty to use the coordinator Service, http://<service>.<namespace>.svc:<port>, which resolves whatever the cluster domain. Set it only when the launchers have to reach Hub at another address, such as through a proxy. |
| hub.workDir | string | "/apheris/foundry" |
Shared PVC mount root for coordinator, launcher, and task pods. New runs live under jobs/ |
| labels | object | {} |
Labels to add to every object the chart creates. They can't replace the chart's own labels, such as app.kubernetes.io/name. |
| msa | object | {"enabled":false,"pollInterval":"5s","requestTimeout":"5m","servers":[]} |
MSA server configuration |
| msa.enabled | bool | false |
Enable MSA server configuration |
| msa.pollInterval | string | "5s" |
How frequently the application checks the status of a submitted MSA job on the ColabFold server (e.g., "5s", "10s"). |
| msa.requestTimeout | string | "5m" |
The timeout for each individual HTTP request made to the ColabFold server (e.g., "5m", "10m"). |
| msa.servers | list | [] |
Globally configured MSA servers. At least one server is required when MSA is enabled. defaultActive: true marks the deployment-level fallback server used for new users and when a stored active selection no longer resolves (for example after server removal or URL-identity change). At most one server can be marked defaultActive: true. |
| workflow | object | {"mountRoot":"/apheris/workflows","mountVolume":{},"resourcePolicy":{"classes":{"cpu":{"limits":{"cpus":32,"gpus":0,"memory_mib":131072}},"gpu":{"gpu_resource":"nvidia.com/gpu","limits":{"cpus":8,"gpus":1,"memory_mib":65536},"max_forks":1,"shm_mib":16384,"tolerations":[{"effect":"NoSchedule","key":"nvidia.com/gpu","operator":"Exists"},{"effect":"NoSchedule","key":"apheris.com/hub-gpu-model","operator":"Exists"}]}},"version":1},"root":"/workflows","uploadMaxConcurrent":2,"uploadMaxEntries":10000,"uploadMinFreeBytes":1073741824,"workflows":{"admet-finetune":{"enabled":true,"uiMode":"cli-only"},"admet-predict":{"enabled":true,"uiMode":"cli-only"},"affinity-finetune":{"enabled":true,"uiMode":"cli-only"},"affinity-predict":{"enabled":true,"uiMode":"cli-only"},"preprocess-structures":{"enabled":true,"uiMode":"cli-only"},"rfd4-proteina-design":{"enabled":true,"uiMode":"cli-only"},"rfd4-proteina-finetune":{"enabled":true,"uiMode":"cli-only"}}} |
Nextflow workflow / pipeline configuration. Controls workflow discovery, exposure, and the compute classes that every workflow package is prepared for. Hub runs the k8s profile; each package owns its tasks, images, docs and skills. Consumed by the Foundry workflow API. |
| workflow.mountRoot | string | "/apheris/workflows" |
Where the coordinator mounts mountVolume, read-only. Hub discovers each direct child with main.nf as a workflow; like a bundled one, it must hold a valid modules.yaml, its contract snapshots and compiled task wrappers, or it's unavailable. It must lie outside hub.workDir, which task pods can write to. |
| workflow.mountVolume | object | {} |
Volume with the externally delivered workflow packages, as a pod volume source without its name, such as persistentVolumeClaim: {claimName: hub-workflows}. Empty mounts no packages, and Hub discovers only the bundled ones. Keep it apart from the shared PVC: task pods mount that read-write, so packages on it aren't safe from the tasks. |
| workflow.resourcePolicy | object | {"classes":{"cpu":{"limits":{"cpus":32,"gpus":0,"memory_mib":131072}},"gpu":{"gpu_resource":"nvidia.com/gpu","limits":{"cpus":8,"gpus":1,"memory_mib":65536},"max_forks":1,"shm_mib":16384,"tolerations":[{"effect":"NoSchedule","key":"nvidia.com/gpu","operator":"Exists"},{"effect":"NoSchedule","key":"apheris.com/hub-gpu-model","operator":"Exists"}]}},"version":1} |
Compute classes that every workflow package is prepared for at discovery. A task names its class; the class caps its requests, can raise them with overrides.cpus/memory_mib floors, and places its pods. A package whose tasks a class can't serve is unavailable. Task requirements belong to the packages, not to this chart. |
| workflow.root | string | "/workflows" |
Root directory containing bundled pipeline subdirectories. |
| workflow.uploadMaxConcurrent | int | 2 |
Maximum simultaneous dataset uploads per Hub process; 0 disables the concurrency limit. |
| workflow.uploadMaxEntries | int | 10000 |
Maximum entries in an uploaded dataset ZIP; 0 disables the entry-count limit. The 1 GiB byte limit still applies. |
| workflow.uploadMinFreeBytes | int | 1073741824 |
Free bytes retained on temporary and run filesystems after reserving 2 GiB per active upload. |
| workflow.workflows | object | {"admet-finetune":{"enabled":true,"uiMode":"cli-only"},"admet-predict":{"enabled":true,"uiMode":"cli-only"},"affinity-finetune":{"enabled":true,"uiMode":"cli-only"},"affinity-predict":{"enabled":true,"uiMode":"cli-only"},"preprocess-structures":{"enabled":true,"uiMode":"cli-only"},"rfd4-proteina-design":{"enabled":true,"uiMode":"cli-only"},"rfd4-proteina-finetune":{"enabled":true,"uiMode":"cli-only"}} |
Per-workflow API and UI exposure. Workflows missing from this map are enabled and support the full Hub submission UI. Set enabled=false to hide a discovered workflow entirely. Set uiMode=cli-only to show the workflow card and command snippets without rendering Hub input forms or submit controls. |
Module-registry discovery credentials must use concrete registry-host keys, such
as quay.io, without repository paths or wildcards. The registry determines
which repositories each credential can access, including across organisations;
Hub does not add prefix filtering or substitute anonymous access for other
repository paths. Path-scoped Kubernetes pull-secret entries are not reinterpreted
as registry-wide discovery credentials: supply a host-keyed discovery credential.
Kubernetes pod image-pull-secret handling is unchanged. Each digest candidate
receives its own read timeout, so an unavailable copy does not hide an already
verified copy in the same registry. The caller's startup deadline still takes precedence.