Docs · Guides

Postgres databases.

Every project can have its own isolated Postgres database — its own database, its own role, unreachable from other tenants. One click to provision; the connection string arrives as DATABASE_URL.

Provisioning

Open your project’s Database tab and provision. You get a dedicated database and role, and the DSN is injected into your app as DATABASE_URL on the next deploy. The full connection details (host, port, user, password) stay visible on the tab for external tools. Databases share your organization’s storage pool with backups, objects and Data Workshop datasets (see the limits): a new database never raises the total.

import pg from 'pg';

// DATABASE_URL is injected automatically once a database is provisioned.
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });

const { rows } = await pool.query('SELECT now()');

Postgres version

When you provision, choose Postgres 17 (the default) or Postgres 18. The Database tab shows the exact running version — for example, 17.9 — not just the major line. An existing database stays on the version it was created with; nothing changes unless you choose.

To move an existing database to a different version, dump it and restore into a freshly provisioned one (pg_dump from the old, pg_restore into the new). Your DATABASE_URL is re-injected on the next deploy.

Migrations and seeds

Run migrations in your project’s release command (Settings → Build) — for example npx prisma migrate deploy or drizzle-kit migrate. It runs once per production deploy, after a successful build and before the new version starts, and never on previews. Avoid running migrations in your build script: builds also run for preview deploys, against the same database.

Multiple databases per project

A project can hold more than one database — for a read-heavy reporting store, a second service in the same repo, or staging fixtures kept apart from production data. Add one from the Database tab or with canner db create <name>: the first database stays DATABASE_URL, and each additional one is injected as DATABASE_URL_<NAME> (uppercased) on the next deploy. Starter includes one database per project; paid plans can add more.

Export, import and migration

Export a database from its tab, or with canner db export — a pg_dump you can restore anywhere with pg_restore (or plain gzipped SQL for psql). Export is available on every plan.

Import restores a pg_dump custom-format file into a Canner database (canner db import backup.dump), and migrate-in streams a database directly from any publicly reachable Postgres server — Heroku, Supabase, RDS, another Canner project — without a local file at all (canner db migrate-in --from <url>). Both require an empty target unless you opt in to replacing it, in which case Canner snapshots the previous contents first. Every restore is all-or-nothing: if it fails, the database is exactly as it was. Migrate-out sends a Canner database to a server you name; copy duplicates one Canner database into another. Import, migrate-in and migrate-out are available on every plan; copying (and the clone button) needs a paid plan.

Connecting from outside

A project database is always reachable from inside Canner — your app, Data Workshop, scheduled queries. To connect a local client, psql, or a BI tool directly, turn on external access from the database tab: it opens a TLS-only endpoint restricted to the IP addresses or CIDR ranges you allow, nothing else. Turn it off any time to close the endpoint again. Rotating the database password is disabled while external access is on for that database (turn access off, rotate, turn it back on) so outside connections don’t drop mid-rotation. Available on every plan.

Analytics on your data

Project databases show up as a source in Data Workshop: browse tables and run SQL from the dashboard. To read or write your database on a schedule — nightly rollups, reconciliation, cross-project moves — use scheduled SQL queries.

Backups

Canner’s own platform database is backed up nightly regardless of plan. For project databases, turn on a backup policy from the database tab (or canner db backup-policy <name> --enable --every 24 --keep 7): how often (minimum every hour) and how long to keep each backup (minimum one day, plus an optional cap on how many to keep). You can also take a backup on demand at any time with Back up now (or canner db backup <name>). Every backup is encrypted at rest with its own key — deleting a backup destroys that key immediately, so the file becomes unrecoverable before disk space is even reclaimed. Starter can back up once a day (a scheduled interval of at least 24 hours, and one backup per rolling 24 hours counting on-demand ones); paid plans can schedule as often as hourly.

Restoring defaults to a brand-new database, so nothing existing is ever touched by accident (Starter, with its one database per project, restores in place or inspects first instead); from the same panel you can instead restore in place into an existing database, which replaces its contents after Canner takes a snapshot of what was there (kept for 7 days, same as a replacing import). Backups count toward your organization’s storage pool, shown on the database tab, together with the recovery archive and any WAL held back for replication. Backups are the first thing to stop: at 100% new backups and uploads pause (scheduled backups are skipped and older ones pruned down to the most recent, rather than let backups themselves strand you). Database writes are locked only when the pool is 10% over its limit, and unlock again below 90%.

What a backup contains

A backup covers your data, not your configuration. It includes the database (schema and data; with the superuser backup identity, also privileges, policies and a roles export) and your uploaded files with their versions. It does not include environment variables, encryption keys or other secrets, domains or project settings.

Keep your own copy of the secrets. canner env --reveal --json prints them; store the output in a secrets manager, never next to the backup. A restore that comes back without the key that encrypts credentials inside your data (a payment provider token, say) cannot decrypt them.

Inspect a restore first

Choose Inspect first when you restore a backup (or recover to a moment in time) and Canner restores it into a temporary inspect database with its own connection string. It is never injected into your app and it deletes itself after 24 hours unless you keep or extend it. Look around with any client, or press Compare with live to see which tables are new, missing or different, with row counts. When you are happy, keep it as a database and copy it over the original. From the CLI: canner db backups <name> <id> --restore --inspect, canner db diff <inspect>, and canner db inspect <inspect> --keep.

Alerts and verified backups

Canner tells you when something needs attention: a backup fails, no backup has succeeded for two intervals, point-in-time archiving is failing, your storage is at 80% or 95% or full, or an inspect database is about to expire. Each problem is one incident: you are told once and reminded on a slow schedule while it lasts, and it resolves itself when it clears. Alerts go by email to every organization admin, or to the addresses you choose, and optionally to an https webhook signed with X-Canner-Signature: sha256=<HMAC-SHA256 of the body>. Manage them from the database overview or with canner db alerts.

A backup nobody has restored is only a hope, so overnight Canner restores your newest backup into a scratch database, reads every table back, deletes the scratch database and marks the backup Verified. A failed test raises an alert. You can also verify any backup on demand (canner db backups <name> <id> --verify), or switch the overnight tests off in the alert settings.

Roles for analysts and services

Your database’s owner credential can change anything, so don’t hand it around. Create named roles instead: each has its own password and connection string. A read-only role can read every table (including tables created later) and nothing else; a read and write role can also insert, update and delete. Neither can create, alter or drop anything, and neither can reach any other database. Roles are a feature of the dedicated instance (a role can read every table of the database it connects to, which is only safe on a server that belongs to one customer). Roles follow the database’s external-access allowlist, follow it when it moves or changes version, and lose write access along with the owner if the storage write-lock engages. Up to 10 per database: canner db roles <name> create bi --read-only. Rotating a role’s password, like the owner’s, needs external access switched off first.

Roles you create yourself in SQL (your database owner can, on the dedicated instance) appear in the same list, marked as created in SQL. A monitoring role needs pg_monitor, which a database owner cannot grant: tick it on the role here (or any of pg_read_all_stats, pg_read_all_settings, pg_stat_scan_tables) and we grant it on your instance. Only those four are offered, never one that reads data.

A read-write role never gets write access to your migration ledger — the table your migration tool (Rails, Django, Flyway, Prisma, Knex, Alembic, TypeORM and others) uses to track which migrations have run. If it’s under one of those tools’ default names, it’s kept read-only automatically the moment the role is created; no setup needed. Creating the role also checks the rest of your schema for tables that look like a migration or audit ledger by name and, if it finds any, suggests them back to you — a recommendation only, never applied without confirmation. Accept one, or protect any other table the same way, at any time: canner db roles <name> write-exclusions <role> <table>. It takes effect immediately, on the same connection string — no new role, no reconnecting.

Off-site backup copies

Backups live on the server that took them. To keep a copy somewhere else, add a destination: a bucket you own on any S3-compatible service (Backblaze B2, Wasabi, Cloudflare R2, AWS S3, MinIO…). You can add up to five (canner db offsite add …), each with a name, and press Test on each. Then choose, for each database, which destination its backups are copied to under Copy backups off-site to(canner db backup-policy <name> --offsite <destination>): different databases can use different buckets. Removing a destination stops the copies to it and ends the fallback for backups it held. Each backup is copied a few minutes after it finishes, newest first. What lands in your bucket is already encrypted with that backup’s own key, which stays with Canner: the bucket alone can’t be read, and deleting a backup (including by retention) deletes its copy and makes it permanently unreadable even if the remote delete has to be retried. If the local file is ever lost, restores and downloads fall back to the copy. You get an alert if copying keeps failing.

Memory for your app and your database

Your app and its dedicated Postgres instance share one memory budget. Part of it is shared: your plan’s allowance, used flexibly and throttled (never surprise-killed) when the server is busy. Part is guaranteed: 0.5 GB comes with the dedicated instance, and you can add Dedicated RAM in 1 GB blocks ($19 a month each) that nobody else can take. If you only need more memory for your app, App RAM ($4 a GB a month) adds to the shared part instead; see App RAM or Dedicated RAM? to choose. The database overview shows the split: by default Postgres takes half of the shared memory and all of the guaranteed memory, and you can move the slider or keep bought RAM for your app instead (canner db resources set --share 40). Apply sizes Postgres for its share (shared buffers, cache and connection limits) and puts a hard memory limit on the instance, so a runaway query is stopped inside your instance and never affects other customers; applying restarts the instance for a few seconds. Your app’s memory limit is lowered to its part of the budget and takes effect at its next deploy. Guaranteed RAM is limited to a share of each server, so a purchase can be refused when it is sold out.

Extensions

On the dedicated instance, each database has an extensions list: pgvector (vector), PostGIS, pg_trgm, unaccent, citext, hstore, pgcrypto, uuid-ossp, btree_gin, btree_gist, fuzzystrmatch, ltree, tablefunc and pg_cron(canner db extensions <name> install vector). They install at once and work for your own database roles. The list is fixed on purpose: extensions that can reach other servers or read the host’s files (postgres_fdw, dblink, file_fdw) are not offered. An installed extension can be updated to the server’s current version and removed; removal is refused while columns, indexes or functions still use it, so nothing is lost by accident. pg_cron loads a library when Postgres starts, so installing it restarts the instance, now or in your maintenance window, and it runs in one database of the instance. Its jobs run as the role that scheduled them.

Tuning and the maintenance window

Choose a workload preset (balanced, read-heavy, write-heavy or analytics) and set the timeouts you want: statement, idle-in-transaction and lock wait (canner db tuning --preset write-heavy --statement-timeout 30). These take effect at once without a restart, and stay within safe bounds; memory sizing still comes from your memory budget. The maintenance window is two hours, on the day and in the time zone you choose, in which planned restarts happen: anything that needs a restart (installing pg_cron, turning on logical replication) can be done now or wait for the window, and canner db restart --yes does it immediately.

Logical replication

Turn on logical replication to stream a database’s changes to another Postgres: a replica you run, a warehouse, another region (canner db replication <name> on --yes; it restarts the instance). Create a publication for the tables you want and a login of kind replication (canner db roles <name> create feed --replication) for the subscriber. It connects through the same address and allowlist as external access, which must be on for the database; that login can read the database and open replication connections, and nothing else: it cannot write, cannot create objects, cannot reach your other databases, and cannot open a whole-server copy.

A subscriber that stops reading would make Postgres keep WAL for it until the disk fills, so there is a limit: the most WAL one slot may hold back (2 GB by default, set it with canner db replication <name> cap 4096). Past it Postgres invalidates the slot instead, and the subscriber must be set up again. Keep the limit larger than your biggest transaction. Each slot shows how much it holds back and its health, WAL held back counts toward your storage, and you get an alert when a slot is falling behind, inactive for a day, or invalidated.

Follow an external server

The other direction: a database on your dedicated instance can receive the live changes of another Postgres server, to move over with no downtime or to keep a copy of another system (canner db subscriptions <name> add legacy --from-env SRC_URL --publication app_pub). Create the tables here first with the same names and columns (restore the schema), and a publication on the other server; then give Canner the connection URL of a user allowed to replicate. The existing rows are copied first, then changes stream in; the database shows how many tables are in step and when the last change arrived, and you can refresh (after adding tables to the publication), pause, resume or stop. Up to five subscriptions per database.

The other server must be reachable from the internet at a public address (private, loopback and cloud-metadata addresses are refused), and the connection is encrypted unless you say otherwise with sslmode. The password is typed into a masked field and handed to Postgres; Canner does not keep it. Changes are applied with the rights of your table owner, never as a superuser, so a trigger on your tables runs with your rights only. Two things to know: replicated changes run with an empty search_path, so trigger functions must write schema-qualified names (public.audit, not audit); and if a change cannot be applied (a missing table, a conflicting row) the subscription shows Failing, you get an alert, and it retries by itself once you fix the cause. Stopping removes the slot on the other server; if that server is gone, use --force.

Backups run as

On a dedicated instance, Canner reads your databases for backups, exports and copies as either the database owner (the default) or the instance superuser. Choose it under Backups, Backups run as. Only organization admins can change it, the change is recorded in the audit log, and switching to superuser starts a fresh backup of each database so you see it work straight away.

Use superuser when the owner cannot see all of its data. A table with row-level security that is forced (FORCE ROW LEVEL SECURITY) applies its policies even to its owner, so an owner-run backup stops with a row-level-security error. The superuser reads every row, and it does not depend on the owner’s password, so changing that password in SQL never breaks a backup.

A superuser-run backup keeps what a faithful restore needs besides the data: the grants and default privileges, and the roles the database uses (groups, logins, memberships, settings and database-level grants), including the password hashes, stored encrypted with the backup. Restoring it in Canner brings the data back with the same permissions, without you providing a file or any credentials: roles your server no longer has are recreated, and roles that exist keep their current password. Row-level security, policies and forced RLS come back as they were, and the verification test restore checks that every table that held rows still does. A point-in-time restore on such an instance does the same. Two things to know: a backup taken as superuser can only be restored onto the dedicated instance it came from (and over a database only while Backups run as is still superuser), and a restored copy with a different owner does not receive the original owner’s default privileges. Swapping a restored copy in over the live database with canner db copy keeps the permissions too, and the live database keeps its own default privileges.

To restore a downloaded backup on a server of your own, download the backup and its Roles (.sql) file, run psql -v dbname=mydb -f roles.sql as a superuser, then pg_restore --no-owner --dbname mydb backup.dump. The roles file contains password hashes, so only organization admins can download it. Backups taken as the owner have no roles file.

Every backup also records its schema & policies: a JSON file listing the tables and columns, indexes, constraints, functions, the row-level-security state of every table, the policies, the grants (including column grants and default privileges) and the roles involved, with the row counts taken at backup time and two fingerprints (one for the structure, one for the permissions). It holds no row data and no password hashes, so it is safe to attach to a ticket or an audit file. Download it with Schema & policiesnext to the backup, or from the API with ?compare=live to have it also state how the live database differs right now. A verification test restore compares the restored copy with this record, and fails if a table, a column, a policy or a grant did not come back. Backups taken before this existed have no such file; take a new one.

Encrypted recovery archive

Point-in-time recovery keeps two things on the server: a periodic base backup and the stream of WAL between them. Both are compressed and encrypted with AES-256 (authenticated) before they are written, with a key that is unique to your instance and stored sealed in Canner’s control database. Removing the instance removes the key, which makes any leftover copy unreadable. This protects the archive files themselves (a copy of the archive folder, a snapshot, a shipped copy); it is not disk encryption of the whole server. Instances that started archiving before this existed switch to encrypted files automatically, and their older files stay restorable.

To survive the loss of the server itself, send a second copy off-site: under point-in-time recovery choose a destination (one of your off-site buckets, see above) for Copy the recovery archive off-site(canner db pitr --offsite <destination>). New WAL and base backups are uploaded within minutes, already encrypted, and the copy follows your retention window: what the window drops locally is deleted from the bucket too. If files are ever missing on the server, a restore fetches them back from the bucket first. Choosing a different destination starts over with everything; turning point-in-time recovery off or removing the instance deletes the copy. As with off-site backups, the key stays with Canner, so the bucket alone cannot be read. It needs the archive to be encrypted, and the shipped files count toward your own bucket, not Canner’s storage pool.

Metrics, slow queries and logs

Every database has metrics on its page: connections, transactions, cache hit ratio and size over the last hour, day, week or 30 days (canner db metrics <name>). A database on the dedicated instance also shows the instance’s memory and CPU. Metrics are sampled every few minutes, so a brand-new database starts empty.

On the dedicated instance you can switch on query insights and logs (canner db monitoring on --yes). This turns onpg_stat_statements and the Postgres log collector and restarts the instance for a few seconds, so it is your call. You then get your slowest and busiest queries (constants replaced by $1, $2…) sorted by total time, average, calls or slowest run, and a searchable log of errors, warnings and statements slower than a threshold you set (500 ms by default), kept for seven days. Both are scoped to the database you are looking at, never the others. Note that log lines contain the text of slow and failing statements, including literal values. Turning it off removes both and restarts the instance again.

Dedicated instance and point-in-time recovery

For a database that needs its own server, a project can have a dedicated Postgres instance: a real, separate Postgres process with its own data directory and no memory cap, that holds some or all of that project’s databases. Choose the Postgres version when you create it (it is fixed for the instance), then add databases to it with canner db create <name> --instance, or move an existing one onto it with canner db move <name> --to instance. A move is a dump and restore with real downtime while it runs; the connection string does not change, and if it fails the database is left exactly as it was. You can move a database back to shared hosting the same way. It is a paid add-on, priced per project.

Each project needs its own unit ($9 a month). To get one, tick Dedicated instance when you create the project’s first database, press Upgrade on an existing database, or run canner db init --dedicated. If the project has no unit assigned yet, this adds one to your plan: the dashboard and the CLI show the price and ask you to confirm first (the CLI needs --yes), and nothing is charged before that. canner billing addons lists the units you own and where they are assigned, and canner billing preview dedicated_instance 3 shows what owning three would cost, without buying anything.

An instance can also run point-in-time recovery, on or off when you create it and changeable later. While it is on, Canner archives every change and takes a base backup on a schedule; you choose the window (1 to 35 days, 7 by default). To recover, pick a moment inside the window (or “latest”) and Canner recreates that database as it was then, as a brand-new database — nothing existing is touched (canner db pitr-restore <name> --to 2026-09-23T14:00:00Z). If you want it in place of the original, copy it over with canner db copy <restored> <name> --replace, which snapshots what was there first. The dashboard shows the restorable range and the health of the archive. Turning point-in-time recovery off permanently deletes the archive.

The recovery archive counts toward your organization’s storage pool, like backups. If the add-on lapses, the instance is stopped and its data is kept; nothing is deleted automatically. You can always move databases off it or export them. The recovery archive lives on the same server as the instance: it protects you from mistakes (a bad migration, an accidental delete), not from losing the server itself — unless you also copy it off-site (see above); Canner’s nightly off-server dumps cover the rest.