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
ReadWriteManyandReadWriteOnceaccessModes - 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 model images from the Apheris image registry and to authenticate with the Apheris-hosted Foldify MSA server.
You can skip this step if you host the Apheris model images in private repositories and do not use the Apheris hosted MSA server.
Request your Apheris Hub API Key from https://www.apheris.com/apherisfold or contact support@apheris.com and set it to your Helm values file:
apherisApiKey: "your-apheris-api-key"
hub:
msa:
enabled: true
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:
# refer to section `2.` for the apherisApiKey
apherisApiKey: "your-apheris-api-key"
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:
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 prediction 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>
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.
MSA Server Configuration🔗
The Hub supports two MSA (Multiple Sequence Alignment) server types with different architectures:
| Type | How it works | Timeout settings |
|---|---|---|
| Foldify (Apheris-hosted) | Streaming — results stream back in real-time | Not configurable via hub.msa.* |
| ColabFold (self-hosted) | Async polling — submit job, poll until complete, download results | Controlled by hub.msa.* |
The hub.msa.* timeout values only affect ColabFold deployments:
hub:
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
Troubleshooting ColabFold:
- "Failed to check job status" errors → Increase
requestTimeout - Want faster progress updates → Decrease
pollInterval(minimum ~3s recommended)
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 |
|---|---|---|---|
| apherisApiKey | string | nil |
Apheris API key for queries to Apheris hosted MSA servers and access to Apheris hosted container images |
| hub.affinity | object | {} |
Affinity rules |
| hub.auth | object | {"audience":"","clientId":"","domain":"","enabled":false,"extraScopes":"","issuer":""} |
Auth0 authentication configuration |
| 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 | {"enabled":false,"pollInterval":"5s","requestTimeout":"5m"} |
MSA server configuration |
| hub.msa.enabled | bool | false |
Enable MSA server configuration |
| hub.msa.pollInterval | string | "5s" |
How frequently the application checks the status of a submitted MSA job on the ColabFold server (e.g., "5s", "10s"). |
| hub.msa.requestTimeout | string | "5m" |
The timeout for each individual HTTP request made to the ColabFold server (e.g., "5m", "10m"). |
| 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 |
|
| labels | object | {} |
Additional labels for every object created by the chart |
| models.imagePullRegistry | string | "quay.io/apheris" |
Registry for image pulls |
| models.imagePullSecrets | list | [] |
Secrets for image pulls |
| models.instances.boltz2 | object | {"deploy":{"enabled":false,"image":"quay.io/apheris/hub-apps:0.37.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"} |
boltz2 requires a GPU with 40GB of memory, not enabled by default |
| models.instances.boltz2.deploy.enabled | bool | false |
enable boltz2 with enabled: true |
| models.instances.mock | object | {"deploy":{"enabled":true,"image":"quay.io/apheris/hub-apps:0.37.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"} |
lightweight mock model that does not require a GPU, enabled by default |
| models.instances.openfold3 | object | {"deploy":{"enabled":false,"image":"quay.io/apheris/hub-apps:0.37.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"} |
openfold3 requires a GPU with 40GB of memory, not enabled by default |
| models.instances.openfold3.deploy.enabled | bool | false |
enable openfold3 with enabled: true |
| 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 |