Skip to content

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 ReadWriteMany volume 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/; deployed checkpoint copies under weights/.
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.