Skip to content

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)
  • A PostgreSQL database
  • A Storage Provisioner that supports ReadWriteMany and ReadWriteOnce accessModes
  • 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 and Boltz-2)

1. Create a Namespace for the Apheris Hub🔗

kubectl create namespace apheris-hub

2. Request the Apheris Hub API Key🔗

You need the Apheris Hub API Key to pull the model images from the Quay.io registry and to authenticate with the Apheris-hosted Foldify MSA server. Request your Apheris Hub API Key from https://www.apheris.com/apherisfold or contact support@apheris.com.

The key is a base64-encoded username:password secret. Decode it to inspect the username and token before you deploy:

echo -n "your-apheris-api-key" | base64 --decode
# => your-apheris-username:your-apheris-password

Set the decoded values in your Helm values so the chart can build the image pull secret:

models:
  imagePullUsername: "your-apheris-username"
  imagePullPassword: "your-apheris-password"

hub:
  msa:
    enabled: true
    apiKey: "your-apheris-api-key"

hub.msa.apiKey expects the original base64 string and is required whenever you enable the Foldify MSA integration (the chart defaults to enabled: false). If you host the Apheris images in a private registry, continue to supply the original Apheris key for hub.msa.apiKey so the Hub can reach msa.foldify.apheris.net with valid credentials. Keep the pull secret confidential and do not commit it to version control.

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).

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:
  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>

models:
  imagePullUsername: <provided by Apheris>
  imagePullPassword: <provided by Apheris>

  persistence:

    # 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 inference results.
    #
    # `50Gi` is the minimum size (and default)
    # we recommend `500Gi` if you can make that happen.
    #
    size: 50Gi

  # The `mock` model is the only default model that the chart
  # deploys with the default values.
  #
  # You can enable deployment of other default models by setting
  # `deploy.enabled=true`, so for instance
  # `models.instances.boltz2.deploy.enabled=true` or
  # `models.instances.openfold3.deploy.enabled=true`.
  #
  instances:
    boltz2:
      deploy:
        enabled: <true|false>
    mock:
      deploy:
        enabled: <true|false>
    openfold3:
      deploy:
        enabled: <true|false>

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

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.auth object {"audience":"","clientId":"","domain":"","enabled":false} Auth0 authentication configuration
hub.benchmarks.enabled bool false
hub.enabled bool true Enable Hub deployment (set to false for models-only release)
hub.env list []
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.imagePullSecrets list [] Image pull secrets for private registries
hub.ingress object {"annotations":{},"className":"","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 "" Ingress/Gateway class name
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 "" Gateway namespace (if different from release namespace)
hub.ingress.hostname string "" Hostname for ingress
hub.ingress.ingressPath string "/" Ingress path
hub.ingress.tls object {"enabled":false,"secretName":""} TLS configuration
hub.ingress.tls.enabled bool false Enable TLS
hub.ingress.tls.secretName string "" TLS certificate secret name
hub.ingress.type string "ingress" Networking type (gateway, ingress)
hub.msa object {"apiKey":"","enabled":false} MSA server configuration
hub.msa.apiKey string "" MSA API key (base64-encoded username:password) for authenticating with MSA servers
hub.msa.enabled bool false Enable MSA server configuration
hub.nodeSelector object {} Node selector
hub.persistence object {"accessMode":"ReadWriteOnce","annotations":{},"enabled":true,"existingVolumeName":null,"size":"5Gi","storageClass":""} Persistence configuration
hub.persistence.accessMode string "ReadWriteOnce" Access mode for state PVC
hub.persistence.annotations object {} Annotations for state PVC
hub.persistence.enabled bool true Enable state persistence
hub.persistence.existingVolumeName string nil Existing PersistentVolume to bind to. If null, a new one will be dynamically created.
hub.persistence.size string "5Gi" Size of state PVC
hub.persistence.storageClass string "" Storage class for state PVC
hub.podAnnotations object {} Pod annotations
hub.podLabels object {} Pod labels
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 replicas for the Hub deployment
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 string nil NodePort (only used when type is NodePort)
hub.service.port int 8080 Service port
hub.service.type string "ClusterIP" Service type
hub.terminationGracePeriodSeconds int 30 Termination grace period in seconds
hub.tolerations list [] Tolerations
hub.websockets.enabled bool true
models.imagePullPassword string "" Password for image pulls
models.imagePullRegistry string "quay.io/apheris" Registry for image pulls
models.imagePullUsername string "" Username for image pulls
models.instances object {"boltz2":{"deploy":{"enabled":false,"image":"quay.io/apheris/hub-apps:0.28.0-boltz2-by-file","podSecurityContext":{"fsGroup":65534,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}},"port":8000,"resources":{"limits":{"cpu":"8","memory":"64Gi","nvidia.com/gpu":1},"requests":{"cpu":"8","memory":"64Gi","nvidia.com/gpu":1}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534},"shmSize":"16Gi","tolerations":[{"effect":"NoSchedule","key":"nvidia.com/gpu","operator":"Equal","value":"true"}]},"id":"boltz2","model":"boltz2"},"mock":{"deploy":{"enabled":true,"image":"quay.io/apheris/hub-apps:0.28.0-mock-by-file","podSecurityContext":{"fsGroup":65534,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}},"port":8000,"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534}},"id":"mock","model":"mock"},"openfold3":{"deploy":{"enabled":false,"image":"quay.io/apheris/hub-apps:0.28.0-openfold3-by-file","podSecurityContext":{"fsGroup":65534,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}},"port":8000,"resources":{"limits":{"cpu":"8","memory":"64Gi","nvidia.com/gpu":1},"requests":{"cpu":"8","memory":"64Gi","nvidia.com/gpu":1}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534},"shmSize":"16Gi","tolerations":[{"effect":"NoSchedule","key":"nvidia.com/gpu","operator":"Equal","value":"true"}]},"id":"openfold3","model":"openfold3"}} Model instances can be external (url only) or deployed (deploy.enabled=true with port, image) Cannot specify both deploy.enabled=true and url
models.persistence object {"accessMode":"ReadWriteMany","annotations":{},"enabled":true,"existingVolumeName":null,"size":"50Gi","storageClass":""} Artifacts persistence (input/output)
models.persistence.accessMode string "ReadWriteMany" Access mode for artifacts PVC
models.persistence.annotations object {} Annotations for artifacts PVC
models.persistence.enabled bool true Enable artifacts persistence via PVC
models.persistence.existingVolumeName string nil Existing PersistentVolume to bind to. If null, a new one will be dynamically created.
models.persistence.size string "50Gi" Size of artifacts PVC
models.persistence.storageClass string "" Storage class for artifacts PVC