canton-network-docs/Validator Deployment
Migrating off PostgreSQL 14
PostgreSQL 14, the version all Splice deployment options ship today, reaches
end-of-life on 2026-11-12.
For Kubernetes deployments, the
The
For LocalNet, read the LocalNet notes below
first: the names differ and there is no
LocalNet has no
Wherever the procedure invokes
LocalNet data is disposable, so tearing the environment down with
splice-postgres Helm chart is deprecated and
will not be supported after that date. Published chart versions remain
available, but receive no further updates. Nodes that use the chart as their
database must move to a PostgreSQL instance provisioned outside of the Splice
Helm charts before that date. A managed database service such as Amazon RDS,
Google Cloud SQL, or Azure Database for PostgreSQL is recommended. A PostgreSQL
server operated by your own database team follows the same procedure.
Docker Compose deployments and LocalNet do not use the chart; they run the
postgres:14 image directly. For these, migrating means replacing the bundled
instance with one running a supported major version. Bumping the image tag alone
does not work: a newer PostgreSQL server refuses to start on a data directory
initialized by an older major version, so the data moves with the same dump and
restore procedure. Moving to an externally hosted PostgreSQL works the same way.
This guide describes a one-time migration with planned downtime. All databases are
copied from the chart-managed instance to the target instance with pg_dump and
pg_restore, and the Splice applications are then configured to connect to the
target. The procedure applies to validator and SV nodes alike; SV nodes repeat the
copy and reconfiguration for each Postgres release they installed (typically
sequencer-pg, mediator-pg, participant-pg, and apps-pg), while
validator nodes typically have a single release named postgres.
Use the section matching your deployment:
- Kubernetes deployments with the Splice Helm charts: follow Migration procedure on Kubernetes.
- Docker Compose validator deployments: follow Docker Compose deployments.
- LocalNet development environments: follow Docker Compose deployments with the name substitutions from the LocalNet notes.
Preparing the target instance
Provision the target PostgreSQL before the downtime window:- Use a PostgreSQL version supported by Splice, ideally in the same region and zone as your cluster to keep latency low.
- Create the
cnadminuser, with the password stored in the Kubernetes secret referenced by your values files (postgres-secretsin the default validator setup). The user must have theCREATEDBprivilege: the Splice application charts run an init container that creates an application’s database when it does not exist yet. - Create a database named
cantonnet. It stays empty, but the init containers connect to it as their maintenance database. - Ensure the instance is reachable from the pods in your cluster.
- Review the connection limit of the target. The chart ran with a very high
max_connectionssetting; managed services derive the limit from the instance size. Size the instance so that the limit comfortably exceeds the combined pool sizes of the applications connecting to it; as a reference point, a validator node with default settings holds around 80 connections.
pg_dump supports dumping from servers of older major versions.
Migration procedure on Kubernetes
The commands below use thevalidator namespace and the default release and host
name postgres. Adjust names, namespaces, and credentials for your deployment,
and repeat the copy for each Postgres instance of an SV node. Set the connection
parameters used by all following commands:
kubectl run commands below start plain client pods; if your cluster
requires tolerations or service-mesh annotations for pods to run, add them with
--overrides.
-
Enumerate the databases. Every database on the chart-managed instance must be
copied. The database names are set by
persistence.databaseNamein each application’s values file; with default values a validator instance holdscantonnet(maintenance),cantonnet_validator, andparticipant_<migration id>, and the SV instances holdcantonnet_sequencer,cantonnet_mediator,cantonnet_sv, andcantonnet_scan. Cross-check your list against the instance, excluding only the templates and the unusedpostgresdefault database:Every name in the output must be attributable to one of your values files. Deployments that overridepersistence.databaseNameshow those names here instead of the defaults. Databases left behind by earlier migration IDs show up too; copy them unless you have deliberately retired them. -
Stop the applications. Scale every Splice application in the namespace down
to zero. The Postgres chart runs as a StatefulSet, so it keeps running:
Do not restart the applications until the copy has finished. Dumps taken while the applications run are not consistent across databases, and an application that starts against the target before the restore writes to the empty databases and makes the restore fail. Scaling with
--allalso stops deployments in the namespace that are not part of the Splice Helm releases, such as monitoring agents. Step 6 does not restore those; scale them back up yourself afterwards. -
Create the databases on the target.
cantonnetis skipped: it already exists on a target prepared as described above.The< /dev/nullmatters here and in the next step: an attachedkubectl runpod otherwise consumes the loop’s input and silently skips databases. -
Copy each database. Stream the dump directly into the target to avoid
intermediate storage:
The
--no-owner --no-privilegesflags are required when the target user lacks superuser rights, which is the case on all managed database services. To keep a dump file as an additional safety artifact, writepg_dumpoutput to a volume first and runpg_restorefrom it; the flags stay the same. The restore requires empty target databases. Errors such asschema "debug" already existsmean the target database has been written to already, either by an application that was started too early or by an interrupted restore attempt. Drop and recreate that database on the target, then repeat the copy for it: -
Point the applications at the target. Update the password secret to the
target credentials:
SV nodes update the secret belonging to the migrated instance instead (for example
apps-pg-secret,sequencer-pg-secret). List the application releases withhelm list -n validator; if the original values files are not at hand, recover each release’s current values withhelm get values \<release\> -n validator. The recovered values may show the host as a cluster-internal FQDN such aspostgres.validator.svc.cluster.local; any host that resolves to the source instance qualifies for the change below. In every values file that contains apersistencesection referencing the old instance, set the target connection: -
Restart and verify. Run
helm upgradefor each application release with the updated values files, in the same order as the original installation:Use the release names fromhelm listand the chart version your node already runs. This recreates the deployments and scales them back up. Then verify:-
all pods reach the
Runningstate and their readiness checks pass, - the wallet UI shows the expected balances,
- a small transaction, for example a transfer, completes successfully,
-
the applications are connected to the target and the old instance is idle:
The application databases must show active connections here, and the same query against the old instance must show none.
-
all pods reach the
-
Decommission the chart. Once the node has been verified, remove the old
instance and its storage:
Until this step the source instance is untouched, so a rollback only requires reverting the secret and values changes and running
helm upgradeagain.
Docker Compose deployments
Docker Compose deployments run PostgreSQL as thepostgres-splice service using
the official postgres image, pinned by SPLICE_POSTGRES_VERSION in .env.
A major version upgrade of that image requires the same dump and restore procedure,
because a PostgreSQL server does not start on a data directory initialized by an
older major version. The commands below upgrade a validator node in place from
PostgreSQL 14 to 17; connecting to an externally hosted PostgreSQL instead works
the same way, with SPLICE_DB_SERVER, SPLICE_DB_PORT, SPLICE_DB_USER,
and SPLICE_DB_PASSWORD in .env pointing at the external instance.
Run all commands from the deployment directory (the one containing
compose.yaml and .env), with the deployment variables loaded into the
shell:
.env file to source.
-
Stop all services, then start only Postgres again. Scoping the commands to the
compose project keeps unrelated containers on the host untouched:
Dumps taken while the applications run are not consistent across databases; do not proceed until everything except Postgres is stopped.
-
Dump every database, using the client tools of the target version:
-
Stop the Postgres container and set the old data volume aside. This keeps
the pre-migration data directory until the migration is verified, the same
way the Kubernetes procedure leaves the old instance installed until step 7:
-
Set
SPLICE_POSTGRES_VERSION=17in.envand start only the Postgres service. Edit the value in the file rather than overriding it from the shell: a later restart from a fresh shell would fall back to the old version, and the server refuses to start on the migrated data directory. After editing, reload the file (set -a; source .env; set +a) — the value sourced earlier is still exported in your shell and takes precedence over the file. Confirm withecho $SPLICE_POSTGRES_VERSIONbefore continuing, and treat the version check below as a gate: proceed only when it prints the new version.PARTICIPANT_DB_NAMEis normally injected bystart.shand must be supplied here, or the entrypoint fails on an emptyCREATE DATABASEstatement and the container restarts in a loop. Warnings about other unset variables can be ignored; they belong to services that are not started. If the container was started with the wrong version or withoutPARTICIPANT_DB_NAME, the fresh volume has already been initialized and must be removed again before retrying (docker rm -f splice-validator-postgres-splice-1 && docker volume rm splice-validator_postgres-splice). The volume set aside in step 3 is unaffected. The entrypoint initializes a fresh data directory and creates empty databases for theCREATE_DATABASE_*variables in its environment. Create everything from your list that does not exist yet, before restoring: -
Restore every dump:
The restore requires empty target databases, exactly as in step 4 of the Kubernetes procedure: if a database was written to by an early application start or an interrupted restore, drop and recreate it, then restore it again. A
could not translate host nameerror means the Postgres service from step 4 is not running. - Start the deployment as usual, then verify as in step 6 of the Kubernetes procedure. Once verified, delete the volume set aside in step 3 and the dump files.
SPLICE_POSTGRES_VERSION in .env, and start
the deployment as usual. Your regular
backups remain
the safety net for everything beyond the migration window.
LocalNet
LocalNet development environments follow the same steps with these name substitutions:| \ | Validator deployment | LocalNet |
|---|---|---|
Compose project (-p) | splice-validator | localnet |
| Postgres container | splice-validator-postgres-splice-1 | postgres |
| Postgres service | postgres-splice | postgres |
| Docker network | splice-validator_splice_validator | localnet |
| Data volume | splice-validator_postgres-splice | localnet_postgres |
| Database password | SPLICE_DB_PASSWORD in .env | supersafe (DB_PASSWORD) |
| Postgres version setting | SPLICE_POSTGRES_VERSION in .env | POSTGRES_VERSION env var |
.env file; instead of sourcing one, export the password once
so the commands above work verbatim:
docker compose with -f compose.yaml, use
the full invocation you start LocalNet with instead — the environment files
supply required variables such as PARTY_HINT, and POSTGRES_VERSION comes
from the shell:
down -v and
starting fresh on the new version is usually simpler. An executable end-to-end
version of the LocalNet migration, including verification, is maintained at
scripts/test-postgres-migration.py in the Splice repository.
Notes for managed database services
- Managed services do not grant superuser access. The
--no-owner --no-privileges` flags on `pg_restoreare therefore mandatory, and the restore must run as the same user that the applications use (cnadmin), so that restored objects end up owned by it. - The Splice application schemas live in per-application schemas inside each
database (for example
participantanddebugin a participant database).pg_dumpincludes them automatically; no schema-specific flags are needed. - Amazon RDS: create the instance with the
cnadminmaster user. Restoring with--exit-on-errorsurfaces permission problems immediately. - Google Cloud SQL: create
cnadminthrough the Cloud SQL API or console so it receives thecloudsqlsuperuserrole, and connect either over private IP or through the Cloud SQL Auth Proxy sidecar. - Verify that the instance’s
max_connectionsaccommodates all applications before the first restart; connection exhaustion appears as applications failing readiness checks after the migration.