> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/installation/kubernetes/back-up-and-restore.md).

# Back up and restore

Back up a Kubernetes deployment and restore it — the database, the deployment secret, the secret store, the volumes and the configuration, with the commands for each.

A Kubernetes deployment holds its state in five places, and a restore needs all five. **A managed database backup on its own is not a backup of the deployment** — it is a backup of the database, and a restored database is a set of tables the platform cannot read without the credentials that open it.

Do this before the deployment carries anything you would mind losing, and restore it once before you need to.

## What this builds, and how long it takes

|                |                                                                                     |
| -------------- | ----------------------------------------------------------------------------------- |
| **You get**    | A backup set you can rebuild a working deployment from, and a rehearsed restore     |
| **Time**       | An hour to set up; the run time depends on your data volume                         |
| **Hosts**      | The machine you deployed from                                                       |
| **Downtime**   | None for the backup. A restore replaces live data                                   |
| **Reversible** | Restoring replaces data — always restore into a separate namespace or cluster first |

## Before you start

* A deployment verified with [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md).
* `kubectl`, Helm, `jq` and a database client for your engine, on the machine you run this from.
* **Somewhere to put the backup that is not this cluster.** A backup inside the thing it protects is not a backup.

## Set these first

{% code overflow="wrap" %}

```bash
export NS=<the namespace you deployed into>
export APP=<your deployment name>
export DB_HOST=<your database endpoint>
export DB_ADMIN=<an administrative database account>
export BACKUP_DIR=/var/backups/openiam
export STAMP=$(date +%F)
export SCHEMAS="openiam activiti groovy auditlog batchtasks pbac certification privacy"
mkdir -p "$BACKUP_DIR"
```

{% endcode %}

**Set `STAMP` once per run** so every artefact carries the same date and is restored as a set. A restore from a mismatched set is the most common way a rehearsed restore fails.

***

## What to back up

|  # | What                                              | Why it matters                                                                               |
| -: | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|  1 | **The eight database schemas**                    | The identity model, workflow state, audit records, campaigns                                 |
|  2 | **The `secrets` secret**                          | Every credential the deployment generated. Without it the restored database cannot be opened |
|  3 | **The secret store's data**                       | Where the platform keeps the credentials it uses at run time                                 |
|  4 | **The secret store's unseal keys and root token** | Recorded at deployment. **Not recoverable if lost**                                          |
|  5 | **The deployment configuration**                  | The settings the deployment was built from, so the same release can be rebuilt               |

**Search indexes are rebuilt from the database after a restore**, rather than being restored — a console operation covered in the [Administration Guide](/administration-guide/admin.md). On a large population that rebuild takes longer than the rest of the restore, so allow for it when you measure recovery time.

**Snapshot the volumes as well if your cluster can.** The graph, search and message-broker tiers hold state on volumes, and a volume snapshot makes a restore materially faster. Step 6 covers it. **Confirm during your restore rehearsal what your deployment actually needs** — the rehearsal in *Verify it* is what tells you, and it is the reason to run one before you depend on it.

***

## Back up

### 1. The database

Take all eight schemas as one consistent set.

**On a managed service**, use the provider's own backup and confirm the retention is what you intend:

{% code overflow="wrap" %}

```bash
# AWS
aws rds describe-db-instances --db-instance-identifier "$APP" \
  --query 'DBInstances[0].BackupRetentionPeriod'

# Azure
az postgres flexible-server show --name "$APP" --resource-group "$AZ_RG" \
  --query 'backup.backupRetentionDays'
```

{% endcode %}

**Expected:** a non-zero number. Several providers default to none.

**For a dump you hold yourself**, from the deployment machine:

{% code overflow="wrap" %}

```bash
# MySQL or MariaDB
for db in $SCHEMAS; do
  mysqldump -h "$DB_HOST" -u "$DB_ADMIN" -p \
            --single-transaction --routines --triggers "$db" \
            > "$BACKUP_DIR/openiam-${db}-$STAMP.sql"
done
```

{% endcode %}

{% code overflow="wrap" %}

```bash
# PostgreSQL
for db in $SCHEMAS; do
  pg_dump -h "$DB_HOST" -U "$DB_ADMIN" -Fc "$db" \
          > "$BACKUP_DIR/openiam-${db}-$STAMP.dump"
done
```

{% endcode %}

**What you should see:** eight files, none of them empty.

{% code overflow="wrap" %}

```bash
ls -l "$BACKUP_DIR"/openiam-*-"$STAMP".* | wc -l
```

{% endcode %}

### 2. The deployment secret

This is the one that is easy to miss and impossible to reconstruct.

**Export them stripped of server-managed metadata.** A plain `kubectl get -o yaml` carries the original namespace, the object's UID, its resourceVersion, its creation timestamp and its managedFields. Applying that unchanged into a different namespace fails, and applying it into the same namespace can fail on a resourceVersion conflict — which is exactly the moment you least want a surprise.

{% code overflow="wrap" expandable="true" %}

```bash
strip() {
  kubectl get secret "$1" -n "$NS" -o json \
    | jq 'del(.metadata.namespace,
              .metadata.uid,
              .metadata.resourceVersion,
              .metadata.creationTimestamp,
              .metadata.generation,
              .metadata.managedFields,
              .metadata.ownerReferences,
              .metadata.selfLink,
              .metadata.annotations."kubectl.kubernetes.io/last-applied-configuration",
              .status)'
}

strip secrets          > "$BACKUP_DIR/openiam-secret-$STAMP.json"
strip globalpullsecret > "$BACKUP_DIR/openiam-pullsecret-$STAMP.json"
chmod 600 "$BACKUP_DIR"/openiam-*secret-"$STAMP".json
```

{% endcode %}

**Confirm the namespace is gone from both files** before you store them:

{% code overflow="wrap" %}

```bash
grep -c '"namespace"' "$BACKUP_DIR"/openiam-*secret-"$STAMP".json
```

{% endcode %}

**Expected: `0` from each.** A non-zero count means the file will only restore into the namespace it came from.

**Confirm it is complete** before storing it — the key names, not the values:

{% code overflow="wrap" %}

```bash
kubectl get secret secrets -n "$NS" -o jsonpath='{.data}' | jq -r 'keys[]' | wc -l
```

{% endcode %}

**Expected:** the same count each time you take a backup. A smaller number means the configuration release was rebuilt and the credentials have rotated.

**Store this file where your organisation keeps credentials**, not alongside the database dump. It holds every account the deployment uses.

### 3. The secret store's data

The secret store keeps its data in its backend, inside the cluster. Take a snapshot from the backend's own pod and copy it out:

{% code overflow="wrap" %}

```bash
kubectl exec -n "$NS" "${APP}-consul-server-0" -- \
  consul snapshot save /tmp/openiam-secrets.snap

kubectl cp -n "$NS" "${APP}-consul-server-0:/tmp/openiam-secrets.snap" \
  "$BACKUP_DIR/openiam-secrets-$STAMP.snap"

kubectl exec -n "$NS" "${APP}-consul-server-0" -- rm -f /tmp/openiam-secrets.snap
```

{% endcode %}

**What you should see:** a file of a few hundred kilobytes.

{% code overflow="wrap" %}

```bash
ls -lh "$BACKUP_DIR/openiam-secrets-$STAMP.snap"
```

{% endcode %}

**If the pod name is different**, find it:

{% code overflow="wrap" %}

```bash
kubectl get pods -n "$NS" -l app=consul,component=server
```

{% endcode %}

**If access control is enabled on the backend**, the snapshot command needs a token — pass it with `CONSUL_HTTP_TOKEN` in the `kubectl exec` environment.

### 4. The unseal keys and root token

These were produced once, when the secret store bootstrapped, and **the cluster does not hold a copy you can retrieve**. They should already be with your break-glass credentials. Confirm that now rather than during a restore.

### 5. The deployment configuration

From the deployment materials directory:

{% code overflow="wrap" %}

```bash
tar -czf "$BACKUP_DIR/openiam-config-$STAMP.tar.gz" \
    env.sh setup-no-tf.sh terraform.tfvars .deploy .ssl .apache
```

{% endcode %}

**On the Terraform route, include the state**:

{% code overflow="wrap" %}

```bash
tar -czf "$BACKUP_DIR/openiam-config-$STAMP.tar.gz" \
    env.sh terraform.tfvars main.tf iam.auto.tfvars terraform.tfstate .deploy .ssl .apache
```

{% endcode %}

**The state file contains credentials.** Store it with the same care as the deployment secret.

### 6. Volume snapshots, if your driver supports them

The graph, search and broker tiers hold state on volumes. A volume snapshot makes a restore faster and, for the graph and broker tiers, is the safer choice: **take snapshots where your cluster supports them**, and use your restore rehearsal to establish what your deployment needs without them.

{% code overflow="wrap" %}

```bash
kubectl get pvc -n "$NS"
kubectl get volumesnapshotclass
```

{% endcode %}

If a snapshot class exists, take snapshots through it on your cluster's usual schedule. If none exists, skip this — nothing on this page depends on it.

### 7. Move it off the cluster

{% code overflow="wrap" %}

```bash
ls -l "$BACKUP_DIR"
```

{% endcode %}

Copy the whole directory to wherever your organisation keeps backups, and remove the local copies of the secret and the state file once they are stored.

***

## Restore

**Restore into a separate namespace or a separate cluster first.** Confirm the result, then decide about production.

### 1. Restore the database

Restore the provider's backup to a new instance, or replay the dumps:

{% code overflow="wrap" %}

```bash
# MySQL or MariaDB
for db in $SCHEMAS; do
  mysql -h "$DB_HOST" -u "$DB_ADMIN" -p "$db" < "$BACKUP_DIR/openiam-${db}-$STAMP.sql"
done
```

{% endcode %}

{% code overflow="wrap" %}

```bash
# PostgreSQL
for db in $SCHEMAS; do
  pg_restore -h "$DB_HOST" -U "$DB_ADMIN" -d "$db" --clean "$BACKUP_DIR/openiam-${db}-$STAMP.dump"
done
```

{% endcode %}

**Restore all eight together.** They are one logical dataset.

### 2. Create the namespace and apply the deployment secret first

**This is the step that makes the restore work.** The deployment generates credentials only when the configuration release does not already exist. Putting the saved secret in place beforehand means the restored deployment uses the credentials the restored database already knows.

{% code overflow="wrap" %}

```bash
kubectl create namespace "$NS"
kubectl apply -n "$NS" -f "$BACKUP_DIR/openiam-secret-$STAMP.json"
kubectl apply -n "$NS" -f "$BACKUP_DIR/openiam-pullsecret-$STAMP.json"
```

{% endcode %}

**`-n "$NS"` is what places them**, because the files no longer name a namespace. That is deliberate: the same backup restores into the namespace it came from or into a new one, and restoring into a new one is how you rehearse without touching production.

**Confirm before going further:**

{% code overflow="wrap" %}

```bash
kubectl get secret secrets -n "$NS" -o jsonpath='{.data}' | jq -r 'keys[]' | wc -l
```

{% endcode %}

**Expected:** the count you recorded at backup.

### 3. Deploy the same release

Unpack the configuration and deploy, pointed at the restored database:

{% code overflow="wrap" %}

```bash
tar -xzf "$BACKUP_DIR/openiam-config-$STAMP.tar.gz"
./setup-no-tf.sh
```

{% endcode %}

**Use the release the backup came from**, not the current one. `OPENIAM_VERSION_NUMBER` in `env.sh` and `setup-no-tf.sh` must be the version that was running. Upgrading and restoring at the same time gives you two things to diagnose instead of one.

On the Terraform route, `terraform apply` with the restored `terraform.tfvars` and state.

### 4. Restore the secret store's data

Once the secret store's backend is running:

{% code overflow="wrap" %}

```bash
kubectl cp -n "$NS" "$BACKUP_DIR/openiam-secrets-$STAMP.snap" \
  "${APP}-consul-server-0:/tmp/openiam-secrets.snap"

kubectl exec -n "$NS" "${APP}-consul-server-0" -- \
  consul snapshot restore /tmp/openiam-secrets.snap

kubectl rollout restart -n "$NS" statefulset "${APP}-vault"
```

{% endcode %}

**Restart the application services afterwards** so they pick up the restored credentials:

{% code overflow="wrap" %}

```bash
kubectl rollout restart -n "$NS" deployment -l app.kubernetes.io/instance
```

{% endcode %}

### 5. Verify

Work through [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) in full, then sign in and **run a test connection on one managed system**. That last check is the one that proves the secret store was restored correctly — it is the only step that exercises a credential the platform holds rather than one you typed.

***

## Verify it

**A backup you have never restored is a hope, not a plan, and a rehearsed restore is a release gate for this deployment — not an optional exercise.** Do it before the deployment carries anything you would mind losing, and at least once:

1. Restore into a separate namespace by the procedure above.
2. Sign in to the console.
3. Confirm users, roles and managed systems are present.
4. Run a **test connection** on one managed system.
5. Confirm the search tier returns results, or rebuild the indexes and confirm afterwards.

**Then record how long it took.** That number is your recovery time, and it is the one your business continuity plan needs — not an estimate.

## If it goes wrong

| What you see                                       | What it means                                                                 | What to do                                                 |
| -------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------- |
| A dump file is empty or very small                 | The account cannot read that schema                                           | Grant it read access to all eight and run again            |
| `kubectl exec` into the backend pod fails          | The pod name is different, or the backend is not running                      | `kubectl get pods -n "$NS" -l app=consul,component=server` |
| `consul snapshot save` returns a permission error  | Access control is enabled on the backend                                      | Pass a token with `CONSUL_HTTP_TOKEN`                      |
| The restored deployment generates new credentials  | The `secrets` secret was applied after the deployment, not before             | Remove the namespace and start again from step 2           |
| The restore starts but sign-in fails               | The secret and the snapshot are from different runs                           | Restore a matched set — that is what `STAMP` is for        |
| A restore succeeds but a managed system test fails | The secret store's data was not restored, or came from a different deployment | Repeat step 4 and restart the services                     |
| The search tier returns nothing                    | The indexes were not backed up, by design                                     | Rebuild them from the console                              |
| Pods will not pull images                          | The pull secret was not applied                                               | Apply it, or run the deployment again to recreate it       |

## What this procedure does not cover

Your organisation's backup schedule, retention and off-site policy; your database team's own regime, which must cover all eight schemas; and the connected systems. OpenIAM manages accounts in other systems, and restoring OpenIAM to an earlier point does not roll those back.

## Related

* [*Deploy with Helm*](/installation/kubernetes/deploy-with-helm.md)
* [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md)
* [*Managed databases*](/installation/rpm/cloud/managed-databases.md)
* [*Back up and restore*](/installation/rpm/rpm-single-node/back-up-and-restore.md) — the RPM equivalent

*Applies to OpenIAM 2026.8.1.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/installation/kubernetes/back-up-and-restore.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
