Following the acquisition, Onfido is now known as Entrust.Read more
Onfido LogoOnfido Logo

Developers

Biometric Passkey: Deployment

Introduction

This guide covers deployment and operations for the self-hosted Biometric Passkey backend. The production deployment path is Kubernetes with the packaged Helm chart from the backend release bundle. Docker Compose and direct Docker CLI examples are included only as simple startup options for evaluation or local integration checks.

Mobile SDK distribution is separate from backend deployment: Android is distributed through Maven Central, and iOS is distributed through the public GitHub Swift package repository. See Biometric Passkey: SDK integration. For supported release lines and cross-component upgrade planning, see Biometric Passkey: Version policy.

Deployment topology

The Biometric Passkey backend is delivered as a Helm chart plus container image archives that you run in your environment. In the default Helm deployment you run two long-lived application processes plus a migration job, all backed by a single PostgreSQL database:

ComponentImageRole
Core APIbiometric-passkey-coreServes the flow API consumed by your backend.
Management runtimebiometric-passkey-managementServes the browser-based management dashboard, browser-safe runtime configuration, and the Management API used by support tooling.
Migration jobbiometric-passkey-migrateJob that applies database migrations on install or upgrade.

All three share a single PostgreSQL database. Outbound network access to Entrust Identity Verification is required.

Biometric Passkey deployment topology

Deployment topology: the Core API, management runtime, and migration job run in your environment and share PostgreSQL. The management runtime serves the dashboard, /runtime-config.json, and /api/management/v1/** from the same management runtime. The application containers listen on internal HTTP ports; expose them through TLS-terminating ingress only.

The Helm chart creates Deployments, Services, a ServiceAccount, and an optional migration job. It does not install PostgreSQL, create Ingress or Gateway resources, create image registries, or create Secrets containing secret material. Long-lived service images (biometric-passkey-core, biometric-passkey-management) declare a Docker HEALTHCHECK; the biometric-passkey-migrate image is a job container and does not declare a health check.

Runtime prerequisites

RequirementNotes
Kubernetes and HelmProduction deployments use your Kubernetes cluster and Helm 3.
Container runtimeAny OCI runtime; production images are distroless and run as non-root with no shell.
PostgreSQLPostgreSQL 16, 17, or 18. The migration job creates the required database objects.
Outbound networkHTTPS to Entrust Identity Verification.
Inbound networkHTTPS from your backend to the Core API; HTTPS from trusted operator browsers and approved automation clients to the management runtime; HTTPS from Entrust Identity Verification to the Core API webhook endpoint.
TLS terminationTerminate TLS at your ingress or service mesh. Application containers listen over HTTP on the configured internal port.
Secret storeA secret manager for keys, tokens, and webhook secrets. Do not load secrets from disk.

Backend release bundle

Download production Biometric Passkey backend releases from TrustedCare. Each backend release package includes the Helm chart archive, three gzipped distroless image archives, and release metadata:

  • biometric-passkey-helm-chart-backend-vX.Y.Z.tgz
  • biometric-passkey-core-distroless-backend-vX.Y.Z.tar.gz
  • biometric-passkey-management-distroless-backend-vX.Y.Z.tar.gz
  • biometric-passkey-migrate-distroless-backend-vX.Y.Z.tar.gz
  • checksums.txt
  • release-manifest.json
  • release-notes.md
  • release-sbom.cdx.json

The release-sbom.cdx.json file is a CycloneDX SBOM for the backend release bundle. Ingest it into your dependency inventory, vulnerability-management, or procurement-review process according to your security policy.

The backend release package delivered through TrustedCare contains the artifact filenames, image digests, chart version, and backend version for your deployment. Use the chart archive and image archives from the same backend release bundle. Backend images are delivered as loadable archives, not as pullable registry references. Mobile SDKs use separate public package channels: Maven Central for Android and the public GitHub Swift package repository for iOS.

Helm chart deployment

Verify release checksums before loading images or installing the chart:

bash
1sha256sum -c checksums.txt

Inspect the manifest and keep it with your deployment record:

bash
1jq . release-manifest.json

Load each image archive, tag it for your registry, and push it:

bash
1load_and_push() {
2 archive="$1"
3 target="$2"
4
5 load_output="$(gzip -dc "$archive" | docker load)"
6 printf '%s\n' "$load_output"
7
8 loaded_ref="$(printf '%s\n' "$load_output" | sed -n 's/^Loaded image: //p; s/^Loaded image ID: //p' | tail -n 1)"
9 test -n "$loaded_ref"
10
11 docker tag "$loaded_ref" "$target"
12 docker push "$target"
13}
14
15load_and_push biometric-passkey-core-distroless-backend-vX.Y.Z.tar.gz \
16 registry.example.com/biometric-passkey-core:X.Y.Z
17
18load_and_push biometric-passkey-management-distroless-backend-vX.Y.Z.tar.gz \
19 registry.example.com/biometric-passkey-management:X.Y.Z
20
21load_and_push biometric-passkey-migrate-distroless-backend-vX.Y.Z.tar.gz \
22 registry.example.com/biometric-passkey-migrate:X.Y.Z

Always pin to an immutable version tag in production. Do not use a floating tag.

Create the namespace and Secrets outside the chart:

bash
1kubectl create namespace biometric-passkey
2
3kubectl -n biometric-passkey create secret generic biometric-passkey-database \
4 --from-literal=password='<postgres-password>'
5
6kubectl -n biometric-passkey create secret generic biometric-passkey-secrets \
7 --from-literal=data-encryption-key='<unpadded-base64url-encoded-32-byte-key>'
8
9kubectl -n biometric-passkey create secret generic biometric-passkey-onfido \
10 --from-literal=api-token='<onfido-api-token>' \
11 --from-literal=webhook-secret='<onfido-webhook-secret>'

Do not put secret values directly in values.yaml. The data encryption key protects sensitive database values and must be an unpadded base64url-encoded 32-byte AES-256-GCM key. The key ID stored with protected database values is configured with Helm value dataEncryption.keyId and defaults to default. Keep the key and key ID stable after initial deployment; this release does not support in-place rotation of this key.

After supported Secret updates, restart the workloads that consume the Secret so Kubernetes refreshes the environment values:

bash
1kubectl -n biometric-passkey rollout restart deploy/biometric-passkey-core
2
3# If management.enabled=true:
4kubectl -n biometric-passkey rollout restart deploy/biometric-passkey-management

Create biometric-passkey-values.yaml:

yaml
1images:
2 core:
3 repository: registry.example.com/biometric-passkey-core
4 tag: X.Y.Z
5 management:
6 repository: registry.example.com/biometric-passkey-management
7 tag: X.Y.Z
8 migrate:
9 repository: registry.example.com/biometric-passkey-migrate
10 tag: X.Y.Z
11
12database:
13 host: postgres.example.com
14 port: 5432
15 name: biometric_passkey
16 user: biometric_passkey
17 sslmode: require
18
19secrets:
20 database:
21 name: biometric-passkey-database
22 passwordKey: password
23 dataEncryption:
24 name: biometric-passkey-secrets
25 key: data-encryption-key
26 onfido:
27 name: biometric-passkey-onfido
28 apiTokenKey: api-token
29 webhookSecretKey: webhook-secret
30
31dataEncryption:
32 keyId: default
33
34idv:
35 mode: onfido
36 registrationWorkflowId: biometric-passkey-registration
37 stepUpWorkflowId: biometric-passkey-step-up
38 recoveryVerifyWorkflowId: biometric-passkey-recovery-verify
39 onfido:
40 region: eu
41 webhookMaxAge: 15m
42
43core:
44 replicas: 2
45
46resources:
47 core:
48 requests:
49 cpu: 250m
50 memory: 256Mi
51 limits:
52 cpu: "1"
53 memory: 512Mi
54
55migrations:
56 enabled: true
57 useHelmHooks: true

Render the chart before installing:

bash
1helm template biometric-passkey ./biometric-passkey-helm-chart-backend-vX.Y.Z.tgz \
2 -n biometric-passkey \
3 -f biometric-passkey-values.yaml

Install the chart:

bash
1helm install biometric-passkey ./biometric-passkey-helm-chart-backend-vX.Y.Z.tgz \
2 -n biometric-passkey \
3 -f biometric-passkey-values.yaml \
4 --wait \
5 --timeout 10m

Check the rollout and health:

bash
1kubectl -n biometric-passkey get pods,jobs,svc
2kubectl -n biometric-passkey rollout status deploy/biometric-passkey-core
3kubectl -n biometric-passkey port-forward svc/biometric-passkey-core 3000:80 >/tmp/biometric-passkey-core-port-forward.log 2>&1 &
4core_pf_pid=$!
5trap 'kill "$core_pf_pid" 2>/dev/null || true' EXIT
6sleep 2
7curl -fsS http://127.0.0.1:3000/api/health

If management is enabled, also check the management runtime:

bash
1kubectl -n biometric-passkey rollout status deploy/biometric-passkey-management
2kubectl -n biometric-passkey port-forward svc/biometric-passkey-management 3001:80 >/tmp/biometric-passkey-management-port-forward.log 2>&1 &
3management_pf_pid=$!
4trap 'kill "$management_pf_pid" 2>/dev/null || true' EXIT
5sleep 2
6curl -fsS http://127.0.0.1:3001/api/health
7curl -fsS http://127.0.0.1:3001/runtime-config.json

The chart creates Kubernetes Services only. Configure your Ingress, Gateway API, service mesh, or load balancer resources separately. Route inbound IDV callbacks to POST /api/public/v1/providers/onfido/webhooks on the Core service.

Enable management runtime

The management runtime is disabled by default. It serves the browser management dashboard, the generated /runtime-config.json response, and /api/management/v1/** routes from the same management runtime and Kubernetes Service. Enable it only after you choose an HTTPS admin origin, register the public OIDC client, and configure the JWT issuer, audience, JWKS URL, and UI client settings.

Register these fixed dashboard redirect paths in your OIDC provider for the admin origin you expose through ingress:

SettingValue
Redirect URIhttps://<your-management-origin>/auth/callback
Post-logout redirect URIhttps://<your-management-origin>/login

The example below requests the full dashboard scope set used by operators who need user search, user deletion, passkey lifecycle actions, flow review, and audit review. The packaged Helm values default to a smaller bootstrap set: openid, profile, email, management.users.read, and management.users.delete. Expand or reduce management.ui.scopes to match each operator role.

yaml
1management:
2 enabled: true
3 replicas: 1
4 auth:
5 jwksUrl: https://issuer.example.com/.well-known/jwks.json
6 issuer: https://issuer.example.com/
7 audience: biometric-passkey-management-api
8 scopeClaim: scope
9 scopePrefix: ""
10 clockSkew: 30s
11 ui:
12 clientId: biometric-passkey-management-ui
13 scopes:
14 - openid
15 - profile
16 - email
17 - management.users.read
18 - management.users.delete
19 - management.credentials.read
20 - management.credentials.suspend
21 - management.credentials.activate
22 - management.credentials.revoke
23 - management.flows.read
24 - management.audit.read

The management.ui.scopes list is what the dashboard requests at sign-in. Grant the same scopes to the operator in your OIDC provider. Keep this list in canonical management.* form; if your OIDC provider issues namespaced scopes, set management.auth.scopePrefix and let the runtime prefix dashboard requests and strip the prefix during API authorization.

The management backend generates /runtime-config.json from these values:

Runtime config fieldHelm value
oidc.authoritymanagement.auth.issuer
oidc.clientIdmanagement.ui.clientId
oidc.audiencemanagement.auth.audience
oidc.scopesmanagement.ui.scopes, with management.auth.scopePrefix applied to management.* entries when set
oidc.scopeClaimmanagement.auth.scopeClaim
oidc.scopePrefixmanagement.auth.scopePrefix
apiBaseUrlEmpty for same-origin Management API calls in the supported topology

The generated runtime config response is a browser-readable configuration. Do not put client secrets, refresh-token material, private keys, or operator-specific values in management runtime configuration.

The management runtime does not run audit partition maintenance. The chart always renders BIOMETRIC_PASSKEY_AUDIT_PARTITION_MAINTENANCE_ENABLED=false for management pods.

Customize chart attributes

Keep chart configuration in biometric-passkey-values.yaml. Use values files instead of repeated --set flags for production.

Common values you own are:

AreaValues
Image registry referencesimages.core, images.management, images.migrate, imagePullSecrets
External PostgreSQLdatabase, secrets.database
Secret referencessecrets.database, secrets.dataEncryption, secrets.onfido
Identity verificationidv.mode, idv.*WorkflowId, idv.onfido
Core workloadcore, resources.core, probes.core
Management workloadmanagement, resources.management, probes.management
Migration jobmigrations, resources.migrate
Servicesservice
Runtime hardeningserviceAccount, podSecurityContext, containerSecurityContext, writableTmp
Audit partitionsauditPartition

Keep the Helm release name, nameOverride, and fullnameOverride stable after installation unless the intent is to recreate Kubernetes resources under new names.

Use tags or digests for release images:

yaml
1images:
2 core:
3 repository: registry.example.com/biometric-passkey-core
4 tag: X.Y.Z
5 management:
6 repository: registry.example.com/biometric-passkey-management
7 tag: X.Y.Z
8 migrate:
9 repository: registry.example.com/biometric-passkey-migrate
10 digest: sha256:<digest>

Use imagePullSecrets when the registry requires Kubernetes image-pull credentials:

yaml
1imagePullSecrets:
2 - name: registry-credentials

Configure your PostgreSQL connection through database and secrets.database:

yaml
1database:
2 host: postgres.example.com
3 port: 5432
4 name: biometric_passkey
5 user: biometric_passkey
6 sslmode: require
7 pool:
8 maxOpenConns: 8
9 maxIdleConns: 4
10 connMaxLifetime: 5m
11 connMaxIdleTime: 1m

Set replica counts and resources per workload:

yaml
1core:
2 replicas: 3
3
4management:
5 replicas: 2
6
7resources:
8 core:
9 requests:
10 cpu: 500m
11 memory: 512Mi
12 limits:
13 cpu: "2"
14 memory: 1Gi
15 management:
16 requests:
17 cpu: 250m
18 memory: 256Mi
19 limits:
20 cpu: "1"
21 memory: 512Mi

The chart uses /api/health for liveness and readiness probes:

yaml
1probes:
2 core:
3 readiness:
4 initialDelaySeconds: 5
5 periodSeconds: 10
6 timeoutSeconds: 5
7 failureThreshold: 3
8 management:
9 readiness:
10 initialDelaySeconds: 5
11 periodSeconds: 10
12 timeoutSeconds: 5
13 failureThreshold: 3

Configure labels, annotations, node selectors, tolerations, affinity, or topology spread constraints per workload:

yaml
1core:
2 podLabels:
3 workload: biometric-passkey-core
4 nodeSelector:
5 kubernetes.io/os: linux
6 tolerations: []
7 affinity: {}
8 topologySpreadConstraints: []

The same keys are available under management and migrations.

Service defaults are ClusterIP Services:

yaml
1service:
2 type: ClusterIP
3 core:
4 port: 80
5 annotations: {}
6 management:
7 port: 80
8 annotations: {}

Use extraEnv for deployment-specific runtime environment variables, such as proxy settings:

yaml
1core:
2 extraEnv:
3 - name: HTTPS_PROXY
4 value: http://proxy.example.com:8080
5 - name: NO_PROXY
6 value: .svc,.cluster.local,127.0.0.1,localhost

Use extraEnvFrom to import an existing ConfigMap or Secret:

yaml
1core:
2 extraEnvFrom:
3 - configMapRef:
4 name: biometric-passkey-extra-config

The same keys are available under management and migrations.

The chart runs containers as non-root, applies the RuntimeDefault seccomp profile, disables service account token automount by default, disallows privilege escalation, drops Linux capabilities, and keeps the root filesystem read-only. A small writable /tmp emptyDir is mounted by default:

yaml
1writableTmp:
2 enabled: true
3 sizeLimit: 64Mi

Migrations in Helm

By default, migrations run as a Helm hook before install and upgrade:

yaml
1migrations:
2 enabled: true
3 useHelmHooks: true

Pre-install hooks run before regular chart resources are created. By default, hook migrations use the namespace default ServiceAccount with token automount disabled on the Pod. To use a dedicated ServiceAccount for hook migrations, pre-create it and set:

yaml
1migrations:
2 serviceAccountName: biometric-passkey-migrations

If database migrations are managed by a separate release process, disable the Helm hook:

yaml
1migrations:
2 enabled: false

When migrations are disabled in Helm, apply database migrations before upgrading the Core and Management workloads.

Configuration

The backend is configured by environment variables. Group them by category and load secrets from your secret manager. The table below describes runtime behavior. The packaged Helm chart renders these environment variables from chart values, and some chart values intentionally use production-oriented defaults that differ from the raw runtime default.

Database

VariableRequiredDefaultNotes
PORTNo3000HTTP listen port for the Core API or management runtime process. The Helm chart sets this from core.containerPort or management.containerPort.
DB_HOSTYesNonePostgreSQL host. May include a port; DB_PORT overrides the parsed port when set.
DB_PORTNo5432PostgreSQL port.
DB_USERYesNoneDatabase user. The packaged Helm chart uses this same user for the migration job, Core API, and management runtime.
DB_PASSWORDNoEmptyPostgreSQL password. Set it unless your PostgreSQL authentication policy permits passwordless connections. Secret.
DB_NAMEYesNonePostgreSQL database name.
DB_SSLMODENodisablePostgreSQL TLS mode. The Helm chart values default this to require; use a production-appropriate TLS mode for remote databases.
DB_MAX_OPEN_CONNSNo4Maximum open connections. Tune for your PostgreSQL instance.
DB_MAX_IDLE_CONNSNo4Maximum idle connections. Must not exceed DB_MAX_OPEN_CONNS when DB_MAX_OPEN_CONNS is greater than zero.
DB_CONN_MAX_LIFETIMENo5mMaximum connection lifetime. Uses Go duration syntax.
DB_CONN_MAX_IDLE_TIMENo1mMaximum idle time for a connection. Uses Go duration syntax.

Identity verification (IDV)

VariableRequiredDefaultNotes
BIOMETRIC_PASSKEY_IDV_PROVIDER_MODENoonfidoSupported values are onfido and synthetic. Use onfido for production. synthetic is intended only for non-production environments.
BIOMETRIC_PASSKEY_IDV_REGISTRATION_WORKFLOW_IDYes for Core APINoneWorkflow identifier for enrollment IDV. The Helm chart values include an example value; replace it with the workflow ID for your deployment.
BIOMETRIC_PASSKEY_IDV_STEP_UP_WORKFLOW_IDYes for Core APINoneWorkflow identifier for step-up biometric verification. The Helm chart values include an example value; replace it with the workflow ID for your deployment.
BIOMETRIC_PASSKEY_IDV_RECOVERY_VERIFY_WORKFLOW_IDYes for Core APINoneWorkflow identifier for recovery on a new device. The Helm chart values include an example value; replace it with the workflow ID for your deployment.
BIOMETRIC_PASSKEY_IDV_ONFIDO_API_TOKENRequired when BIOMETRIC_PASSKEY_IDV_PROVIDER_MODE=onfidoNoneAPI token for the IDV provider. Secret.
BIOMETRIC_PASSKEY_IDV_ONFIDO_WEBHOOK_SECRETRequired when BIOMETRIC_PASSKEY_IDV_PROVIDER_MODE=onfidoNoneWebhook signing secret for inbound IDV result callbacks. Secret.
BIOMETRIC_PASSKEY_IDV_ONFIDO_REGIONNoeuRegion selector for the IDV provider. Supported values are eu, us, and ca.
BIOMETRIC_PASSKEY_IDV_ONFIDO_BASE_URLNoEmptyRegional base URL override. When unset, the backend derives the base URL from the configured region.
BIOMETRIC_PASSKEY_IDV_ONFIDO_WEBHOOK_MAX_AGENo15mMaximum age accepted for webhook signatures. Uses Go duration syntax; use a tight window.

Data encryption

VariableRequiredDefaultNotes
BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEYYes for Core APINoneSymmetric AES-256-GCM key, unpadded base64url-encoded from exactly 32 raw bytes. Secret. Used to protect sensitive database values. Keep stable after initial deployment.
BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY_IDNodefaultKey identifier stored with protected database values. Use 64 characters or fewer from A-Z, a-z, 0-9, ., _, :, or -.

Keep BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY_ID stable for data encrypted with the current key. It is not secret material, but it should not be changed independently of a supported key rotation process.

Management runtime authentication

VariableRequiredDefaultNotes
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_ISSUERYes for management runtimeNoneExpected JWT issuer for Management API tokens and the issuer emitted to the dashboard in /runtime-config.json.
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_AUDIENCEYes for management runtimeNoneExpected JWT audience for Management API tokens and the audience requested by the dashboard when present.
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_JWKS_URLYes for management runtimeNoneJWKS URL for verifying Management API tokens.
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_SCOPE_CLAIMNoscopeJWT claim name carrying scope values.
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_SCOPE_PREFIXNoEmptyOptional prefix applied to dashboard scope requests and stripped from accepted management scopes during API authorization.
BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_CLOCK_SKEWNo30sAllowed clock skew for JWT validation. Uses Go duration syntax; use a small value.
BIOMETRIC_PASSKEY_MANAGEMENT_UI_CLIENT_IDYes for management runtimeNonePublic client identifier emitted to the dashboard in /runtime-config.json.
BIOMETRIC_PASSKEY_MANAGEMENT_UI_SCOPESNoRuntime: openid profile email management.audit.read management.credentials.activate management.credentials.read management.credentials.revoke management.credentials.suspend management.flows.read management.users.delete management.users.read; Helm values: openid profile email management.users.read management.users.deleteSpace-delimited scopes requested by the dashboard. Keep values in canonical management.* form. The runtime default applies only when the environment variable is unset; Helm installs render the management.ui.scopes value.

Audit partition maintenance

VariableRequiredDefaultNotes
BIOMETRIC_PASSKEY_AUDIT_PARTITION_MAINTENANCE_ENABLEDNoCore API: true; management runtime: false when unsetEnables scheduled audit partition maintenance. The management runtime rejects startup when this value is true; the Helm chart always renders false for management pods.
BIOMETRIC_PASSKEY_AUDIT_PARTITION_RETENTION_MONTHSNo0Retention window in months. 0 disables retention-based partition dropping.

Do not enable audit partition maintenance in the management container. If you use a shared env file, override BIOMETRIC_PASSKEY_AUDIT_PARTITION_MAINTENANCE_ENABLED=false for the management runtime or use service-specific env files.

Logging

VariableRequiredDefaultNotes
BIOMETRIC_PASSKEY_LOG_LEVELNoinfoLog level. Supported values are debug, info, warn, and error; use info or warn in production.

Treat all keys, tokens, and webhook secrets as secrets. Load them from your secret manager, not from disk or environment files committed to source control.

Database setup and migrations

The schema is owned by the migration job (biometric-passkey-migrate). With the packaged Helm chart, migrations.enabled=true and migrations.useHelmHooks=true run the migration job as a pre-install,pre-upgrade hook before runtime pods are installed or upgraded. Migration job runs are idempotent.

Recommended database posture:

  • With Helm-managed migrations, grant the configured database user the permissions required to run migrations and serve application traffic. The chart uses the same database credential for the migration job, Core API, and management runtime.
  • Right-size the connection pool to your PostgreSQL instance.
  • Audit events are written to monthly partitions. Core API partition maintenance creates the current and future partition window automatically. Retention-based dropping runs only when BIOMETRIC_PASSKEY_AUDIT_PARTITION_RETENTION_MONTHS is greater than zero and archived partitions have archive-manifest records.

If your database team needs separate schema-owner and runtime roles, apply migrations outside Helm with the schema-owner role and set:

yaml
1migrations:
2 enabled: false

Always apply migrations with the new release version before promoting traffic to the new Core API and management runtime instances.

Simple startup options

Use Docker Compose or direct Docker CLI only for evaluation or local integration checks. Production deployments should use the packaged Helm chart on Kubernetes.

Docker Compose

yaml
1version: "3.9"
2
3services:
4 migrate:
5 image: registry.example.com/biometric-passkey-migrate:X.Y.Z
6 environment:
7 DB_HOST: db
8 DB_PORT: "5432"
9 DB_USER: postgres
10 DB_PASSWORD: postgres
11 DB_NAME: biometric_passkey
12 DB_SSLMODE: disable
13 restart: "no"
14 depends_on:
15 db:
16 condition: service_healthy
17
18 core:
19 image: registry.example.com/biometric-passkey-core:X.Y.Z
20 environment:
21 DB_HOST: db
22 DB_PORT: "5432"
23 DB_USER: postgres
24 DB_PASSWORD: postgres
25 DB_NAME: biometric_passkey
26 DB_SSLMODE: disable
27 PORT: "3000"
28 BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA
29 BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY_ID: default
30 BIOMETRIC_PASSKEY_IDV_PROVIDER_MODE: synthetic
31 BIOMETRIC_PASSKEY_IDV_REGISTRATION_WORKFLOW_ID: biometric-passkey-registration
32 BIOMETRIC_PASSKEY_IDV_STEP_UP_WORKFLOW_ID: biometric-passkey-step-up
33 BIOMETRIC_PASSKEY_IDV_RECOVERY_VERIFY_WORKFLOW_ID: biometric-passkey-recovery-verify
34 BIOMETRIC_PASSKEY_AUDIT_PARTITION_MAINTENANCE_ENABLED: "true"
35 depends_on:
36 migrate:
37 condition: service_completed_successfully
38 ports:
39 - "8080:3000"
40 restart: unless-stopped
41 read_only: true
42 cap_drop: ["ALL"]
43
44 db:
45 image: postgres:16
46 environment:
47 POSTGRES_PASSWORD: postgres
48 POSTGRES_DB: biometric_passkey
49 healthcheck:
50 test: ["CMD-SHELL", "pg_isready -U postgres -d biometric_passkey"]
51 interval: 5s
52 timeout: 5s
53 retries: 10

The example uses synthetic IDV mode and an example data encryption key for local startup. Do not reuse that key or synthetic IDV mode for production.

The simple startup examples run the Core API only. To run the management container outside Helm, provide the same database settings plus BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_ISSUER, BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_AUDIENCE, BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_JWKS_URL, and BIOMETRIC_PASSKEY_MANAGEMENT_UI_CLIENT_ID. The issuer and JWKS URL must use HTTPS. For direct local development only, loopback HTTP issuer and JWKS URLs are accepted.

Docker CLI

The same simple startup can be run without Compose after loading or retagging the release images:

bash
1docker network create biometric-passkey-demo
2
3docker run -d --name biometric-passkey-db \
4 --network biometric-passkey-demo \
5 -e POSTGRES_PASSWORD=postgres \
6 -e POSTGRES_DB=biometric_passkey \
7 postgres:16
8
9until docker exec biometric-passkey-db pg_isready -U postgres -d biometric_passkey; do sleep 1; done
10
11docker run --rm \
12 --network biometric-passkey-demo \
13 -e DB_HOST=biometric-passkey-db \
14 -e DB_PORT=5432 \
15 -e DB_USER=postgres \
16 -e DB_PASSWORD=postgres \
17 -e DB_NAME=biometric_passkey \
18 -e DB_SSLMODE=disable \
19 registry.example.com/biometric-passkey-migrate:X.Y.Z
20
21docker run --rm --name biometric-passkey-core \
22 --network biometric-passkey-demo \
23 -p 8080:3000 \
24 --read-only \
25 --cap-drop=ALL \
26 -e DB_HOST=biometric-passkey-db \
27 -e DB_PORT=5432 \
28 -e DB_USER=postgres \
29 -e DB_PASSWORD=postgres \
30 -e DB_NAME=biometric_passkey \
31 -e DB_SSLMODE=disable \
32 -e PORT=3000 \
33 -e BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY=AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA \
34 -e BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY_ID=default \
35 -e BIOMETRIC_PASSKEY_IDV_PROVIDER_MODE=synthetic \
36 -e BIOMETRIC_PASSKEY_IDV_REGISTRATION_WORKFLOW_ID=biometric-passkey-registration \
37 -e BIOMETRIC_PASSKEY_IDV_STEP_UP_WORKFLOW_ID=biometric-passkey-step-up \
38 -e BIOMETRIC_PASSKEY_IDV_RECOVERY_VERIFY_WORKFLOW_ID=biometric-passkey-recovery-verify \
39 -e BIOMETRIC_PASSKEY_AUDIT_PARTITION_MAINTENANCE_ENABLED=true \
40 registry.example.com/biometric-passkey-core:X.Y.Z

Check the running Core API:

bash
1curl -fsS http://127.0.0.1:8080/api/health

Distroless images support running with --read-only --cap-drop=ALL and as a non-root user. Front any non-local stack with TLS-terminating ingress; do not expose application ports directly to the public internet.

Networking and ingress

  • Place the Core API behind your existing API ingress. It must be reachable by your backend and by inbound webhook traffic from Entrust Identity Verification.
  • Place the management runtime behind a separately restricted admin ingress reachable only from trusted operator networks and approved automation clients.
  • Route the entire admin origin to the management Service, not only /api/management/v1/**. The same origin serves the dashboard shell, static assets, OIDC callback routes, /runtime-config.json, health and version probes, and Management API routes.
  • Do not route Entrust Identity Verification webhook traffic to the management runtime. Webhook traffic belongs on the Core API route.
  • Restrict egress to the Entrust Identity Verification endpoints required for your region.
  • Apply rate limits at your ingress: token abuse and excessive identity verification attempts should not reach the application.
  • Pin TLS at a minimum of TLS 1.2 (preferably TLS 1.3).

If your ingress uses path-based routing, route these management paths to the management Service:

PathPurpose
/ and /assets/*Dashboard HTML and static assets.
/auth/callback and /loginOIDC sign-in and sign-out redirects.
/runtime-config.jsonBrowser-safe runtime configuration generated by the management backend.
/api/management/v1/*Management API routes.
/api/health and /api/versionHealth and build-info probes.

The management runtime is not currently supported under a subpath such as /admin; use a dedicated admin origin or root-level routing for the listed paths.

Observability and monitoring

  • Ship structured JSON application logs from stdout to your aggregator. Each request carries a correlation identifier; preserve it through your edge and application logs to enable end-to-end tracing.
  • Audit events are stored in PostgreSQL. Export or replicate those events to your security analytics pipeline according to your audit architecture.
  • Configure liveness probes against GET /api/health for both long-lived runtimes. The current health endpoint confirms that the HTTP process is serving; it is not a database or downstream readiness check.
  • Alert on:
    • Sustained 5xx rate on the Core API.
    • Sustained 5xx rate on the management runtime.
    • Webhook delivery failures from Entrust Identity Verification.
    • Migration job failures.
    • PostgreSQL connection pool saturation.
    • Audit partition maintenance failures.

Capacity and scaling

  • Core API and management runtime instances are stateless and scale horizontally behind a load balancer.
  • The dominant resources are PostgreSQL connections and outbound HTTPS to Entrust Identity Verification.
  • Right-size the connection pool to your PostgreSQL instance.
  • Run multiple replicas of each enabled long-lived service when your availability requirements require high availability.

Backup and restore

Specific RTO and RPO targets are agreed per deployment; consult your Entrust contact for committed values. The procedure below is the recommended baseline:

  • Back up the PostgreSQL database daily at a minimum. Use point-in-time recovery (continuous WAL archiving) for production deployments.
  • Back up the Kubernetes Secrets or external secret-manager entries needed to decrypt restored database values, including the Secret that provides BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY and its configured key ID.
  • Test restore at least quarterly into an isolated environment.
  • Audit events are part of the database and are included in PostgreSQL backups.
  • User-scoped EBT custody records are part of the database. Restoring the database restores those stored biometric-reference records; protect and restore backups with the same deployment encryption context and Entrust Identity Verification tenant configuration.

Key rotation

Key rotation cadence is agreed per deployment; consult your Entrust contact for committed values. Include the items below in your rotation plan:

  • BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY — symmetric encryption key for backend database-stored ciphertexts.
  • BIOMETRIC_PASSKEY_IDV_ONFIDO_API_TOKEN — IDV provider API token.
  • BIOMETRIC_PASSKEY_IDV_ONFIDO_WEBHOOK_SECRET — IDV provider webhook signing secret.
  • PostgreSQL password for the configured database user. If you run migrations outside Helm with separate roles, include both the schema-owner and runtime-role passwords in your rotation plan.
  • Management runtime JWKS / OIDC client configuration referenced by BIOMETRIC_PASSKEY_MANAGEMENT_AUTH_*.

Rotate supported credentials on a defined schedule. Coordinate rotation through your secret manager, ingress, and deployment automation.

Do not rotate BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY in this release. Protected database values include BIOMETRIC_PASSKEY_DATA_ENCRYPTION_KEY_ID, but this release still supports only one configured data encryption key and does not provide key rotation yet.

Upgrades

Use helm upgrade --install for repeatable installs and upgrades. Before upgrading, review the release notes and Biometric Passkey: Version policy. The compatibility matrix on that page is the source of truth for supported backend, database schema, Android SDK, and iOS SDK combinations.

Before you upgrade:

  • Review the release notes for migration and configuration changes.
  • Back up the PostgreSQL database according to your operational policy.
  • Load and push the new Core, Management, and migration images to your registry.
  • Update biometric-passkey-values.yaml with the new image tags or digests.
  • Keep the Helm release name stable.
  • Do not change nameOverride or fullnameOverride for an existing release unless you intentionally plan to recreate resources.
  • Keep the data encryption key unchanged.

Run the upgrade:

bash
1helm upgrade --install biometric-passkey ./biometric-passkey-helm-chart-backend-vX.Y.Z.tgz \
2 -n biometric-passkey \
3 -f biometric-passkey-values.yaml \
4 --wait \
5 --timeout 10m

For production, consider --atomic if your rollback policy allows Helm to roll back Kubernetes resource changes automatically after a failed upgrade:

bash
1helm upgrade --install biometric-passkey ./biometric-passkey-helm-chart-backend-vX.Y.Z.tgz \
2 -n biometric-passkey \
3 -f biometric-passkey-values.yaml \
4 --wait \
5 --timeout 10m \
6 --atomic

Helm rollback does not roll back database migrations. Handle database backup, restore, and downgrade through your own operational process.

Check the upgrade status:

bash
1helm status biometric-passkey -n biometric-passkey
2kubectl -n biometric-passkey rollout status deploy/biometric-passkey-core
3kubectl -n biometric-passkey get pods,jobs,svc

If management is enabled:

bash
1kubectl -n biometric-passkey rollout status deploy/biometric-passkey-management

Rollback

List the release history:

bash
1helm history biometric-passkey -n biometric-passkey

Roll back Kubernetes resources to a previous revision:

bash
1helm rollback biometric-passkey <revision> -n biometric-passkey --wait --timeout 10m

Rollback changes Kubernetes resources only. It does not reverse database migrations; restore the database separately if required.

Uninstall

bash
1helm uninstall biometric-passkey -n biometric-passkey

The chart does not delete your PostgreSQL databases or externally created Secrets.

Troubleshooting

Show rendered manifests:

bash
1helm template biometric-passkey ./biometric-passkey-helm-chart-backend-vX.Y.Z.tgz \
2 -n biometric-passkey \
3 -f biometric-passkey-values.yaml

Inspect pods and events:

bash
1kubectl -n biometric-passkey get pods
2kubectl -n biometric-passkey describe pod <pod-name>
3kubectl -n biometric-passkey get events --sort-by=.lastTimestamp

Inspect logs:

bash
1kubectl -n biometric-passkey logs deploy/biometric-passkey-core
2kubectl -n biometric-passkey logs deploy/biometric-passkey-management

Inspect migration hook jobs:

bash
1kubectl -n biometric-passkey get jobs
2kubectl -n biometric-passkey logs job/<migration-job-name>

Start with these checks:

  • Verify image tags or digests exist in your registry.
  • Verify image pull credentials are configured when required.
  • Verify database host, username, password Secret, database name, and SSL mode.
  • Verify the data encryption key Secret is present and unchanged.
  • Verify identity verification provider credentials are present when idv.mode=onfido.
  • Verify your Ingress or Gateway routes webhooks to the Core service.
  • If management is enabled, verify management.auth.issuer, management.auth.audience, management.auth.jwksUrl, and management.ui.clientId are non-empty.
  • If the dashboard loads but sign-in fails, verify the OIDC redirect URI is registered as https://<your-management-origin>/auth/callback.
  • If the dashboard cannot start authentication, verify the admin ingress routes /runtime-config.json to the management Service and that the response contains oidc.authority and oidc.clientId.
  • If a signed-in operator sees access denied, verify the operator's token contains the required management.* scope and that any scope prefix is configured only in management.auth.scopePrefix.

Disaster recovery

Your disaster recovery plan should cover:

  • Database backup, restore, and point-in-time recovery (see Backup and restore).
  • Re-deployment of the application from versioned images.
  • Rotation of supported credentials, including the IDV provider webhook secret (see Key rotation).
  • A documented failure-handling path for each release component, agreed with your Entrust contact.