# Upgrades & migrations

Roll forward to a new release with a backward-compatible schema and a zero-downtime deployment order.

## What it is

HelixIAM manages its database schema one of two ways:

- **Versioned Flyway migrations (default)** — in the app, the container image and the Helm chart alike. Versioned migration scripts are applied on startup, giving you an auditable, ordered migration history.
- **Legacy idempotent bootstrap** — the older `schema.sql` path, kept for existing installs that never adopted Flyway:

```bash
HELIX_MIGRATIONS_ENABLED=false
HELIX_SQL_INIT_MODE=always
```

When an install that used the legacy bootstrap adopts Flyway, set `HELIX_FLYWAY_BASELINE_VERSION` to that schema's version.

## Migration discipline

Migrations are **additive and backward-compatible**: a new release runs against the previous schema, and the previous release tolerates the new schema. This is what makes rolling upgrades safe.

!!! danger "Never edit a released migration"
    Once a migration has shipped and been applied anywhere, it is immutable. To change the schema, **add a new migration**. Editing an applied migration breaks checksum validation and corrupts the migration history.

## Zero-downtime rolling upgrade

!!! danger "Not for the upgrade to rc.9"
    The upgrade from rc.8 (or earlier) to rc.9 moves the server to Spring Boot 4: the old release cannot read sessions and tokens the new one writes, so old and new pods **must not** run side by side. Use `strategy.type=Recreate` or scale to zero — see [Upgrading to rc.8 / rc.9](upgrade-rc8-rc9.md#13-spring-boot-4-do-not-run-old-and-new-pods-side-by-side). The rolling procedure below applies to releases whose notes allow it. The chart's update strategy is the Helm value `strategy` (default Kubernetes' RollingUpdate, 25% / 25%).

Deploy in this order:

1. **Back up first.** Take a fresh database backup — see [Backup & disaster recovery](backup.md).
2. **Roll out helix-iam-server.** The first pod/instance to start applies any pending migrations; because
   migrations are backward-compatible, the still-running old instances keep working against the same
   schema during the rollout. Session and token state lives in PostgreSQL (and Redis, if configured), not
   in any one instance, so a rolling replacement keeps signed-in sessions. Short-lived sign-in state (QR
   sign-in, push approval, transaction signing, device enrolment, CIBA) is shared the same way after rc.12 (see
   [High availability](high-availability.md)); up to rc.12 it was per replica, so a sign-in in progress on a
   replaced pod may need to be retried. A release can note other one-off effects — read its upgrade notes.
3. **Deploy the console.**

Following this order, the service stays available during an upgrade.

## Confirm the running version

Before and after an upgrade, check which release is live and that it came up healthy. `/actuator/info` is **not** exposed, so read the version from what you deployed — the image tag:

```bash
kubectl get deploy helixiam -o jsonpath='{.spec.template.spec.containers[0].image}'   # Helm
docker compose images server                                                          # Docker Compose
```

Then confirm readiness. It is **anonymous** — no session or token — so it works from any host that can reach the server (see [Authenticating to the API](../getting-started/api-authentication.md) for `$HELIX_URL`):

```bash
# Readiness after the rollout settles — probe the group, not the aggregate
curl -s "$HELIX_URL/actuator/health/readiness"
```
```json
{"status":"UP"}
```

!!! tip "Verify the migration ran"
    With Flyway enabled, helix-iam-server logs the applied versions on startup, and `flyway_schema_history` in the database records every applied migration and its checksum. A `readiness` of `UP` after the restart confirms the schema reached the shape the new release expects. See [Observability & health](observability.md#over-the-monitoring-endpoints).

## How to upgrade

1. Read the release notes for the target version — for rc.7 → rc.8 / rc.9, the [upgrade guide](upgrade-rc8-rc9.md) lists **breaking** changes (trusted proxies, allowed hosts, encryption migration, write-only IdP secrets); for rc.10 → rc.11, see [Upgrading from rc.10 to rc.11](upgrade-rc10-rc11.md) (wallet `trusted_authorities` rolling upgrades, trusted-list signer grace, Jackson 3, `view-identity-providers`); for rc.11 → rc.12, see [Upgrading from rc.11 to rc.12](upgrade-rc11-rc12.md) (grandfathered identity-provider links and their start-up report, new `realm-admin` requirements, realm keys and ES256); for rc.12 → rc.13, see [Upgrading from rc.12 to rc.13](upgrade-rc12-rc13.md) (realms with `enabled=false`, `DB_ENCRYPTION` for the BSN migration and DigiD, changed DigiD usernames, the `bsn` claim opt-in); from rc.13 to the next release, see [Upgrading from rc.13 to 1.1.0-rc.1](upgrade-rc13-1.1.0-rc1.md) (single-use client assertions, wallet mappers, `IDP_BASE_URL` for the credential issuer, Flyway V90–V135); from 1.1.0-rc.13 to 1.1.0-rc.14, see [Upgrading from 1.1.0-rc.13 to 1.1.0-rc.14](upgrade-1.1.0-rc13-rc14.md) (claims only for the scopes granted, end-user-only userinfo, typed claims, a larger proxy header buffer for implicit and hybrid flows); from 1.1.0-rc.1 to the next release, see [Upgrading from 1.1.0-rc.1 to 1.1.0-rc.2](upgrade-1.1.0-rc1-rc2.md) (the `__Host-XSRF-TOKEN` cookie, `adminAllowedHosts`, the chart's default image, the console image).
2. Back up the database (`pg_dump`, see [Backup](backup.md)) and keep `DB_ENCRYPTION` with it.
3. Roll out helix-iam-server, then the console (a separate Deployment when `console.enabled`). With Helm, upgrade with `helm upgrade --reset-then-reuse-values`
   (Helm 3.14 or later), not `--reuse-values`: it keeps the values you set and adds the defaults of sections a newer
   chart introduced. With `--reuse-values` those sections are missing and the chart refuses to render
   (`values section "..." is missing`).
4. Confirm the deployed image tag and `GET /actuator/health/readiness` — see [Observability & health](observability.md).

## See also

- [Upgrading to rc.8 / rc.9](upgrade-rc8-rc9.md)
- [Upgrading from rc.10 to rc.11](upgrade-rc10-rc11.md)
- [Upgrading from rc.11 to rc.12](upgrade-rc11-rc12.md)
- [Upgrading from rc.12 to rc.13](upgrade-rc12-rc13.md)
- [Upgrading from rc.13 to 1.1.0-rc.1](upgrade-rc13-1.1.0-rc1.md)
- [Upgrading from 1.1.0-rc.1 to 1.1.0-rc.2](upgrade-1.1.0-rc1-rc2.md)
- [Upgrading from 1.1.0-rc.13 to 1.1.0-rc.14](upgrade-1.1.0-rc13-rc14.md) (claims per scope, end-user-only userinfo, typed claims, new client settings, migrations V192–V196)
- [Upgrading from 1.1.0-rc.12 to 1.1.0-rc.13](upgrade-1.1.0-rc12-rc13.md) (key custody phase 3: three migrations V189–V191, the `dataKey` readiness contributor, optional new chart keys, online data-key rotation)
- 1.1.0-rc.2 → 1.1.0-rc.3: no breaking changes and no new migrations; it fixes admin sign-in with MFA and the login page through the console, and publishes the quick-start artifacts (the compose file, the OCI chart, the jar).
- 1.1.0-rc.3 → 1.1.0-rc.4: no breaking changes and no new migrations.
- [Backup & disaster recovery](backup.md)
- [Observability & health](observability.md)
- [Security hardening](security.md)
- [Configuration](../getting-started/configuration.md)
