Helm charts
The dalp umbrella chart: what its subcharts default to, what a release gates on, and where the Gateway API CRDs come from.
The dalp Helm chart is an umbrella chart: one release that composes the subcharts described in Topologies into a DALP InstanceA DALP Instance is one self-contained copy of the platform, deployed inside a boundary you choose and connected to ledger networks you approve.Read the concept, each subchart pinned to the same chart version, targeting Kubernetes 1.21 and later. This page covers installation and release behavior. Use Topologies to choose components and Values and secrets to prepare the deployment settings.
Install
Apply the Gateway API CRD bundle once per cluster, before the first install. dapp (the Console), dapi (the Platform API), and blockscout each create HTTPRoute resources that need the CRDs already in place, so a release started without this step fails to create them:
helm template envoy-gateway-crds oci://docker.io/envoyproxy/gateway-crds-helm \
--version 1.9.1 \
--set crds.gatewayAPI.enabled=true \
--set crds.gatewayAPI.channel=standard \
--set crds.envoyGateway.enabled=true \
| kubectl apply --server-side --force-conflicts -f ---server-side is required for this apply. Gateway API CRDs ship separately below covers why this is a separate step and how often it needs repeating.
From the SettleMint OCI registry, pinning the chart version you want to deploy:
helm install dalp oci://pkg.distribution.settlemint.com/settlemint/dalp \
--version 3.1.5 \
--namespace dalp --create-namespace \
--values my-values.yamlSupply your own my-values.yaml (or repeated --set key=value flags) on top of the chart's defaults. Values and secrets covers what to set. Deploying to OpenShift needs the additional overlay in OpenShift.
The CRD prerequisite and the migration hook are the two states drawn here that can fail the release outright; a helm upgrade that runs out its wait is a third, covered in What goes wrong below. Everything else here is workload-specific timing.
Migrations are a release gate; index rebuilds are not
A schema migration runs as a Helm hook on the dapp subchart, and whether it gates the release depends on dapp.migrator.hookType. The default, pre, runs it as a pre-install and pre-upgrade hook before any workload rolls out, so a failed migration fails the release before a pod ever serves traffic. Set it to post when PostgreSQL is deployed inside the same Helm release, so the database exists before the migration runs against it; set it to none to run the job as a plain, unhooked Job with no release gate at all. The rest of this section describes the pre default.
The chart retries the job up to dapp.migrator.backoffLimit times (3, by default) inside a dapp.migrator.timeout window (600 seconds, by default). That window bounds every retry combined, not each attempt on its own. dapp.migrator.hookDeletePolicy decides whether Helm removes the job automatically; the chart's own default, before-hook-creation,hook-succeeded, only deletes a job that succeeded, so a failed job survives that upgrade. It does not survive the next one: before-hook-creation reclaims it the moment you retry helm upgrade, well inside its dapp.migrator.ttlSecondsAfterFinished window (24 hours, by default). Read the failed job's logs before you retry, not after.
An indexer index rebuild is a different kind of operation and does not block a release. It runs behind an already-healthy deployment, re-derives projections from already-ingested chain data, and a rebuild in progress does not fail or delay helm upgrade. Do not wire an index rebuild into a release gate the way you would a migration. A failed pre-upgrade migration blocks the new rollout. A projection rebuild has a separate completion signal; affected reads can remain stale or unavailable until it completes.
Subcharts
| Subchart | Condition |
|---|---|
common | always installed |
dapp (the Console) | dapp.enabled, on by default |
dapi (the Platform API) | dapi.enabled, on by default |
docs (this documentation site) | docs.enabled, on by default |
pgbouncer | global.pgbouncer.enabled, on by default |
postgres-exporter | postgres-exporter.enabled, on by default |
blockscout | blockscout.enabled, off by default |
investorPortal (Investor portal) | investorPortal.enabled, off by default |
pgbouncer gates both the connection-pooling workload and the application URL routing at once, so leaving it enabled changes how dapp and dapi reach PostgreSQL, not only whether an extra pod runs. investorPortal deploys the Investor portal, the standalone investor-facing web app for sign-in, funding, primary-issuance offers, and secondary-market trading, off by default for the same reason.
| Your deployment | Enable this |
|---|---|
| Standard EVM asset issuance, no explorer, no investor-facing portal | Keep the defaults (dapp, dapi, docs, pgbouncer, postgres-exporter). Enable Blockscout or the Investor portal only when the deployment needs it. |
| Needs an on-chain block explorer | blockscout.enabled: true. Blockscout ships as its own subchart with its own dedicated PostgreSQL database. |
| Serves investor-facing primary or secondary market access | investorPortal.enabled: true. The Investor portal is a standalone web app on the published SDK, deployed as its own workload. |
Every subchart pins the same chart version as the umbrella chart, so dalp version 3.1.5 always installs dapp, dapi, docs, and the rest at 3.1.5 too. Mixing subchart versions inside one release is not supported.
Gateway API CRDs ship separately
dapp, dapi, and blockscout each create an HTTPRoute resource, but neither dalp nor the separate support chart bundles the Gateway API custom resource definitions themselves. The bundle exceeds the size Helm allows inside a release's stored secret, and Helm cannot upgrade CRDs that ship inside a subchart's own crds/ directory anyway, so a chart that bundled them could install once and never refresh them again.
The bundle ships instead as support's gateway-helm dependency, behind envoy-gateway.enabled, off by default. Enable it for local and dev clusters where the bundled CRDs are acceptable; for production, install gateway-crds-helm and gateway-helm as separate, out-of-band Helm releases instead, the way Install above does.
The CRD bundle's version follows that dependency's pin (1.9.1), not the dalp chart version, so a dalp upgrade alone never tells you the CRDs also need a refresh.
Kubernetes validates a resource's kind against installed CRDs before it stores it. A release started without the bundle applied fails to create its HTTPRoute resources, and that surfaces as a helm install or helm upgrade error at install time, not a silent failure.
What goes wrong
A helm upgrade times out before the last workload reports ready
Size --timeout around your deployment's real worst case rather than accepting Helm's own default wait (5 minutes).
progressDeadlineSeconds is raised to 1500 seconds (25 minutes) on both dapi and dapp, well above the Kubernetes
default of 600. The platform's own release pipelines currently size --timeout at 30 to 40 minutes to clear this
comfortably; start there and adjust for your cluster. A single surge pod's path to ready can still spend up to its
boot budget plus relaunch headroom before /readyz flips, so a tighter number needs its own margin. On the Kubernetes
default of 600 seconds, dapp would trip before the PgBouncer pooler it waits on, which needs up to 900. That reads
as a Console failure when the real fault is the pooler, and dapp's 1500 seconds is set to outlast it on purpose.
Combined with maxSurge: 1 and maxUnavailable: 0 forcing api-role pods through the rollout one at a time, a tight
timeout can fire before the last workload reports ready. A tight scheduler pool adds its own stall on top of this: see
overprovisioning.enabled in Values that matter. A timeout abort does not roll back completed
steps by itself; check helm history and the workload status before retrying.
The readiness check keeps failing and the database looks fine
Kubernetes' readiness and startup probesPlatform Health lets a platform admin see in one glance whether the platform is healthy right now, instead of checking a dozen dashboards.Read the concept differ by workload in what they check and
how long they wait, so confirm which one is failing with kubectl describe pod before assuming Postgres is the
problem. dapi's /readyz probes Postgres through a dedicated health session every 15 seconds. The probe has a
10-second timeout and allows three failures. An unavailable database removes the pod from service after roughly 45
seconds, or up to about 55 seconds including reporting lag. dapi's startup probe answers on /healthz instead, with
its own 180-second boot budget. dapp's startup and readiness probes both answer on /readyz; its liveness probe
answers on /healthz. The startup probe budgets 300 seconds (5 minutes) for slow first-boot SSR and SDK warmup, and
only once that clears does the readiness probe start applying its own 3-second, process-only budget. A pod stuck in
that 300-second startup window looks identical to a hung readiness probe in kubectl get pods output, so check which
probe is actually failing before assuming either.
An indexer or runner rollout looks stuck when the api workload rolled fine
dapi renders three separately scaled workloads: an api role, a runner role, and an indexer role, each its own
Deployment with its own HorizontalPodAutoscaler. The api role rolls maxSurge: 1 and maxUnavailable: 0, the same
one-at-a-time pattern as dapp. The runner and indexer roles both roll the opposite way, maxUnavailable: 1 and
maxSurge: 0, each with a PodDisruptionBudget that keeps at least one pod of that role up throughout. The indexer
also holds a replacement back for a 90-second minReadySeconds before it counts as ready. Expect the runner
and indexer to take an old pod down before a new one is ready; that is deliberate capacity behavior on those two
roles, not a wedge, and it looks different from the api role's rollout on purpose.
Redis restarts and pods keep serving without paging anyone
Redis does not gate startup. Cache and notification connections are supervised per pod, so bringing the platform up
before Redis is ready is supported: pods serve from their in-process cache and reconnect on a jittered exponential
backoff capped at 30 seconds, with no attempt limit. Reconnection checks that Redis is serving requests rather than
only accepting connections, so an endpoint still loading a snapshot is retried instead of treated as healthy. While
disconnected, cached reads stay correct but remain private to each pod, and change notifications use each listener's
own reconcile interval. Watch dalp_redis_client_unavailable_seconds, labeled per redis_role, so you can tell which
of the two clients, cache or notifications, is the one that dropped. The RedisClientLatchedOffline alert (severity
warning) confirms roughly six minutes into a sustained outage, identifying a pod that has not recovered from more
than a brief restart.
An indexer's head socket drops and indexing keeps moving anyway
The Ledger DataLedger Data lets an integration answer a holder list, a transfer history, or a compliance check with one query, instead of decoding chain events itself.Read the concept indexer keeps two independent sources of chain head information, and
only one is a live socket to the chain node. Polling is not a fallback that engages when the socket drops; it runs
unconditionally as the correctness backstop, and the socket only buys latency on top of it. A connection-state gauge
reports three values (connected, disconnected, or circuit broken), and a separate dalp_didx_ws_unavailable_seconds
series tracks how long the socket has been down, because the state gauge alone cannot distinguish a two-second blip
from a standing outage. The IndexerWebSocketHeadLatchedOffline alert (severity warning) confirms roughly six
minutes into a sustained outage, keyed per chain, long enough that an ordinary reconnect never pages. Expect indexing
to keep advancing while it fires: a slower head signal is not the same as a stalled indexer. Confirm during
verification that indexing progress keeps moving while the alert fires; progress stopping alongside it points at the
polling path instead, and is a different fault.
Values that matter
global.pgbouncer.enabled, defaulttrue. Also switches howdappanddapireach PostgreSQL, not only whether an extra pod runs. See Subcharts.dapp.migrator.hookType, defaultpre. Sets whether a failed migration gates the release at all. See Migrations are a release gate; index rebuilds are not.dapp.migrator.timeout, default600seconds. Bounds every retry combined, not each attempt on its own.overprovisioning.enabled, defaultfalse. Reserves standing rollout headroom so an upgrade does not stall on a tight scheduler pool.- Chart version, pinned per install. An explicit pin on every install and upgrade keeps a release a deliberate, reviewed change rather than whatever landed in the registry that day.
- Gateway CRD bundle version, tracks
support'sgateway-helmpin. Drifts independently of thedalpchart version; check both. See Gateway API CRDs ship separately.
Frequently asked questions
Does turning off a subchart delete its data?
Disabling a subchart removes its rendered resources from the release on the next upgrade. External databases are separate, but PVC retention and underlying volume deletion depend on the workload and storage policies. Inspect the rendered change and confirm a recoverable backup before disabling a stateful component.
How often do the Gateway API CRDs need reapplying?
Only when support's gateway-helm dependency pin changes, not on every dalp upgrade. Gateway API CRDs ship separately above covers why reapplying is a separate step from a dalp release.
Does dapi's indexer autoscale independently of its api role?
Yes, and so does the runner: the api role ranges 2 to 3 replicas at 80% CPU utilization, the runner ranges 2 to 3 replicas at 75% CPU utilization, and the indexer ranges 2 to 10 replicas at 65% CPU utilization. A replica-count change on one role leaves the other two, and each role's own rollout strategy, untouched.
Does the migration job still run if I disable dapp?
No. The migration job is a dapp subchart template, so dapp.enabled: false skips the whole subchart, migration hook included, the same way it skips every other dapp object. A deployment without dapp must arrange the required database migration separately before starting services against the new schema.