SettleMint
DeploymentInstall

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 Instance, 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.yaml

Supply 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 path a dalp release moves through, from the Gateway API CRD prerequisite to a pod serving traffic.Five states in sequence, with three off-ramps. First, the Gateway API CRD bundle must already be applied to the cluster, once per cluster and before install; skipping it fails HTTPRoute creation at helm install or upgrade time. Second, the migration hook runs as a pre-install or pre-upgrade Helm hook, before any workload rolls out; if it fails, the release fails immediately and the job survives only until the next helm upgrade retry reclaims it, so its logs need reading before then. Third, the workload rollout begins, with each subchart rolling on its own update strategy. Fourth, the startupProbe clears, on a boot budget sized per workload: 180 seconds for dapi, 300 seconds for dapp. Fifth, the readinessProbe clears and the pod starts serving traffic, on a budget that also differs per workload: 10 seconds for dapi's Postgres-bound check, 3 seconds for dapp's process-only check. An index rebuild or shard relocation on the indexer or runner workload runs behind an already-healthy deployment and is not part of this gate.The path a dalp release moves through, from the Gateway API CRD prerequisite to a pod serving traffic.The same figure as the desktop variant, drawn portrait for phone widths. Five states in sequence, with three off-ramps drawn inline: the Gateway API CRD bundle must already be applied to the cluster, once per cluster and before install, or HTTPRoute creation fails at install or upgrade time; the migration hook runs next and gates the release if it fails, and the failed job survives only until the next helm upgrade retry reclaims it; the workload rollout begins on each subchart's own strategy; the startupProbe clears on a per-workload boot budget of 180 seconds for dapi and 300 seconds for dapp; and the readinessProbe clears and the pod serves traffic on a budget of 10 seconds for dapi's Postgres-bound check and 3 seconds for dapp's process-only check. An index rebuild or shard relocation on the indexer or runner workload runs behind an already-healthy deployment and never gates the release.

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

SubchartCondition
commonalways 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
pgbouncerglobal.pgbouncer.enabled, on by default
postgres-exporterpostgres-exporter.enabled, on by default
blockscoutblockscout.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 deploymentEnable this
Standard EVM asset issuance, no explorer, no investor-facing portalKeep 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 explorerblockscout.enabled: true. Blockscout ships as its own subchart with its own dedicated PostgreSQL database.
Serves investor-facing primary or secondary market accessinvestorPortal.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 probes 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 Data 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, default true. Also switches how dapp and dapi reach PostgreSQL, not only whether an extra pod runs. See Subcharts.
  • dapp.migrator.hookType, default pre. Sets whether a failed migration gates the release at all. See Migrations are a release gate; index rebuilds are not.
  • dapp.migrator.timeout, default 600 seconds. Bounds every retry combined, not each attempt on its own.
  • overprovisioning.enabled, default false. 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's gateway-helm pin. Drifts independently of the dalp chart 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.

On this page