> ## Documentation Index
> Fetch the complete documentation index at: https://formbricks.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrade to Formbricks v6

> Prepare a self-hosted v5 installation for SpiceDB authorization during a maintenance window.

Formbricks v6 uses SpiceDB for authorization. Self-hosted installations can upgrade directly from a
supported v5 version during a maintenance window; a bridge release is not required for this path.
PostgreSQL remains the source of roles and grants. SpiceDB stores the derived authorization graph.

<Warning>
  Use only an upgrade pair listed as tested in the target release notes. The
  `formbricks-authzed-prepare` executable is available only in images containing this change, not in
  previously published release candidates. This page is not evidence that a particular v5/v6 pair
  has passed upgrade and restore testing.
</Warning>

## What runs where

Deployment tooling owns maintenance, workload shutdown, infrastructure, and restart. The release image
owns database migrations and authorization preparation. Neither the application nor the chart maintains
an activation receipt, a migration authority switch, or a second authorization engine for this process.

`formbricks-authzed-prepare` is a preparation command, **not an unattended installer**. It does not
discover or stop writers, suspend controllers, take backups, or restart workloads. Its confirmations are
operator acknowledgements, not proof that maintenance is active. Complete and verify those steps first.

The command reuses the image's existing migration runner and `formbricks-authzed upgrade prepare` and
`upgrade check` implementations. It does not automatically prune unexplained relationships.

## Before the maintenance window

* Rehearse with a restored copy of the installation. Measure migrations, backfill, verification, and
  restoration time; use those measurements to communicate the maintenance window.
* Pin the target Formbricks image by digest and the matching chart/Compose files. Preserve the current
  images, chart, values, secrets, and volume names. Do not combine this change with a PostgreSQL major
  upgrade, a storage migration, or unrelated Hub/Cube changes.
* Prepare private SpiceDB connectivity, dedicated datastore credentials, its reviewed version, and
  completed datastore migrations. Retain existing operator ownership; never install a second operator
  over an existing cluster-wide operator. See [SpiceDB operations](/docs/self-hosting/advanced/authzed-operations).

Identify **all writers**, including Formbricks replicas, Hub/Hub workers, workflow workers, scheduled
jobs, import jobs, integrations, and administrative scripts. Include workloads outside the application
namespace or Compose project that write to the same databases. Identify Argo CD/Flux, image updaters,
autoscalers, and other controllers that can recreate those workloads.

Record their original state for restoration. Do not print Secret contents or expanded Compose
configuration into shared CI logs. Prepare and render the exact target manifests before maintenance.

## Perform the upgrade

<Steps>
  <Step title="Enter and verify maintenance">
    Serve maintenance at the ingress/reverse proxy and stop accepting new work. Suspend the identified
    controllers and schedules, stop all writers, and wait for in-flight work and database transactions
    to finish. Suspending a CronJob does not stop an already-running Job. Scaling a Deployment to zero
    is insufficient if an autoscaler or GitOps controller can restore it.

    Verify there are no remaining writer pods, containers, Jobs, external scripts, or active writer
    transactions. Keep only required infrastructure and the dedicated preparation process running.
    Keep maintenance active through preparation, restart, and access verification.
  </Step>

  <Step title="Take a recoverable backup">
    Take consistent backups after writers stop, including the application database, existing SpiceDB
    datastore, and any other datastore changed by the selected release. Retain secrets, configuration,
    storage state, and exact source images. Confirm the restore procedure has been tested.

    Online backups taken earlier are useful but are not a substitute for a known recovery point that
    includes the final writes accepted before maintenance.
  </Step>

  <Step title="Prepare using the target image">
    Run the command below using the exact target image and the same application database and AuthZed
    configuration that v6 will use. It first validates configuration and authenticated SpiceDB health,
    then runs application migrations, schema preparation, outbox drain, relationship repair, and a
    separate full verification. `AUTHZED_CONSISTENCY` must be `fully_consistent`.

    ```bash theme={null}
    formbricks-authzed-prepare --backup-confirmed --writers-stopped
    ```

    A non-empty, different SpiceDB schema is not overwritten automatically. Review `schema check` and
    the release's schema changes, then repeat with `--expected-current-digest sha256:<reviewed-digest>`.
    Only one preparation/schema-writing process may run at a time.
  </Step>

  <Step title="Deploy and test v6 while maintenance remains active">
    Only after preparation exits `0`, deploy the same image digest. Retain `fully_consistent` and the
    same datastore bindings. With Helm, set `authzed.migrationAcknowledged=true`; this is an explicit
    operator acknowledgement, not a database activation record. The upgrade hook only verifies the
    prepared graph; it does not apply schemas or backfill while old workloads are running.

    Verify the actual pod/container image IDs. Through restricted access, test an allowed user, a
    denied user, a scoped API key, organization/workspace lists, and a grant revocation. Verify outbox
    delivery, rerun `formbricks-authzed upgrade check`, and check operational-error metrics. `/health`
    alone does not establish authorization readiness.
  </Step>

  <Step title="Resume service">
    Restore controller and worker settings only after their desired manifests point to the tested v6
    release. Remove maintenance after the checks pass. Monitor authorization errors, outbox age,
    revocation delays, and scheduled relationship audits. Keep backups until upgrade acceptance ends.
  </Step>
</Steps>

## Docker and one-click

Keep the **same Compose project name, database images, and volumes**. Prepare a target Compose file
that retains customized SMTP, storage, networking, and Traefik settings. Pin `formbricks`,
`formbricks-migrate`, `authzed-ops`, and `authzed-initialize` to the same target image digest. Do not copy a
fresh-install Compose file over an existing installation without reviewing those differences.

After stopping the old project's writers and verifying external writers/controllers are stopped,
start only the required infrastructure from the reviewed target configuration. Never use `down -v`.

With infrastructure already healthy, run the existing operations service without starting dependencies:

```bash theme={null}
docker compose --profile authzed-ops run --rm --no-deps \
  --entrypoint formbricks-authzed-prepare authzed-ops \
  --backup-confirmed --writers-stopped
```

Supply your normal `--project-name`, `--env-file`, and `-f` options when customized. The operator service
must receive the application database URL, migration credentials when different, and the six AuthZed
client variables. The distributed default uses the bundled PostgreSQL database; customize it when your
installation uses a managed database. No database or SpiceDB port needs public exposure.

After successful preparation, a one-click installation also sets
`FORMBRICKS_AUTHZED_V6_MIGRATION_ACKNOWLEDGED=true` before its explicit v6 update. Do not invoke an
unattended update or move a floating image tag until the release-specific installation tests pass.

## Helm and GitOps

Prepare SpiceDB before the application upgrade. Keep the current database/storage settings and existing
Secrets. During maintenance, use an explicit, temporary Kubernetes Job with the target Formbricks image,
the same database/AuthZed environment bindings, and this command:

```yaml theme={null}
apiVersion: batch/v1
kind: Job
metadata:
  name: formbricks-v6-prepare
spec:
  backoffLimit: 0
  activeDeadlineSeconds: 7200
  template:
    spec:
      restartPolicy: Never
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
      containers:
        - name: prepare
          image: ghcr.io/formbricks/formbricks@sha256:<target-image-digest>
          command: ["formbricks-authzed-prepare"]
          args: ["--backup-confirmed", "--writers-stopped"]
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
          envFrom:
            - secretRef:
                name: formbricks-maintenance-env
```

This is a template, not a ready-to-apply manifest: set the namespace, digest, resource requests/limits,
image-pull credentials, network policy, and environment bindings for your installation. The example
Secret must contain the required runtime/migration environment; the chart's application Secret does
not necessarily contain `DATABASE_URL` or AuthZed values that it normally injects separately. Prefer
explicit `valueFrom.secretKeyRef` bindings to existing Secrets when possible. Never put secrets on a
command line or commit their values.

Wait for Job completion and inspect its sanitized result before running `helm upgrade` or resuming an
Argo CD/Flux reconciliation. Preparation is not a Helm migration hook or a permanent chart component.
Delete the Job and any temporary environment Secret after acceptance; retain only sanitized evidence.
There is no activation table to clean up.

## Results and recovery

Success prints one JSON result and exits `0`:

```json theme={null}
{"status":"prepared","code":"upgrade_prepared","restartRequired":true}
```

Blocked preparation exits `2`, without a `restartRequired` field. It never authorizes restarting workloads,
including when arguments were rejected before any work ran. The code identifies the failed step:

| Code                                | Operator action                                                                                          |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `upgrade_maintenance_required`      | Complete and acknowledge backup and writer shutdown.                                                     |
| `upgrade_invalid_arguments`         | Check options and the reviewed digest format.                                                            |
| `upgrade_timeout_unavailable`       | Use the supported release image containing `timeout`.                                                    |
| `upgrade_configuration_invalid`     | Correct the required runtime environment and fully consistent mode.                                      |
| `upgrade_spicedb_unhealthy`         | Check datastore migrations, networking, TLS, and token configuration.                                    |
| `upgrade_database_migration_failed` | Diagnose the migration in a restricted operator session; do not restart v5 blindly.                      |
| `upgrade_graph_not_ready`           | Inspect `schema check`, outbox status, and aggregate `upgrade check`; investigate invalid/orphaned data. |
| `upgrade_verification_failed`       | Keep maintenance active and resolve remaining graph or outbox drift.                                     |

Each subprocess has a 30-minute limit. If the rehearsal exceeds it, budget a longer maintenance window
and run the existing migration and operational commands individually under a reviewed Job deadline.
Interrupted containers can exit with a signal code rather than JSON; any nonzero exit remains blocked.

The command never restarts a workload on success, failure, or interruption. Retry preparation with the
same image after correcting the cause, provided the interrupted migration is safe to rerun. Never mark
a failed Prisma migration applied without investigating it.

### Diagnose migration failures privately

The wrapper deliberately does not offer raw output in shared Job logs: migration errors can contain
credentials, SQL, or customer identifiers. In a restricted PostgreSQL operator session, inspect failed
Prisma migration records in `_prisma_migrations` (`migration_name`, `started_at`, `finished_at`,
`rolled_back_at`, and `logs`). Treat `logs` as sensitive; do not paste it into CI, tickets, or Slack.
For failures before Prisma records the attempt, check connectivity and permissions in that restricted
session. If a migration must be retried, first establish whether partial changes need repair, then use
the target image's `node /home/nextjs/packages/database/dist/scripts/apply-migrations.js` under maintenance.
Capture any diagnostic output only in operator-controlled storage; never enable it on normal startup.

After database changes, **`helm rollback` or changing the image is not necessarily a safe rollback**.
Remain in maintenance and either complete the v6 preparation or restore the coordinated backup and
matching v5 images/configuration. Resuming writes before restoration risks losing those new writes.

## Later v6 upgrades

The same read-only `upgrade check` gate also applies to v6.x updates. With an unchanged canonical
SpiceDB schema and a clean graph/outbox, no additional authorization preparation is needed. One-click
stops the stack before application migrations and starts it only after they succeed. Helm keeps its
existing application-migration lifecycle; follow the target release's compatibility and maintenance notes.

If a release changes the canonical SpiceDB schema, this flow deliberately blocks the upgrade until
explicit maintenance preparation has completed with that target image and a reviewed current digest.
Use the procedure above again; the persistent acknowledgement is not permission to skip preparation.
An online, backward-compatible schema rollout would need its own reviewed and tested release procedure;
neither the Helm hook nor the one-click updater infers that a schema change is safe while writers run.

## Cloud is a separate rollout path

Formbricks Cloud can use a temporary, immutable bridge artifact to project relationships while legacy
authorization still serves requests, followed by a brief controlled authorization-write pause. That
artifact is not a self-hosting prerequisite and does not restore a legacy evaluator to the normal v6
application. Continued read/respondent traffic is a rehearsal requirement, not an unconditional promise
of zero downtime.
