Skip to content
WebShield Docs

Postgres database

A managed Postgres database for apps — creation, connection, backups and restore. All with examples.

Postgres is a managed relational database for apps. Each database is a Postgres instance of its own, reachable only by the apps of its project. You connect with an ordinary connection string from an environment variable — servers, backups and updates are on us.

If you need “key — value” rather than SQL, see Valkey; functions have their own database — D1.

In the control panel: project card → Databases block → Create. Set a name, a size (up to 20 GB) and, if needed, a replica on another node that takes over if the main instance fails.

From the terminal:

Terminal window
webshield apps db create db --size 2 # a database "db" of 2 GB
webshield apps db create db --size 2 --replica # the same, with a replica
webshield apps db list # the databases of the project
webshield apps db delete db # delete

The size only grows, without downtime. All databases and on-disk Valkey stores of a project share 40 GB; the change log takes another quarter of the database size, and a replica doubles everything.

No need to copy the connection string: put a reference to the database into an app variable, and it is replaced with the real value on deployment.

Reference Value
${{db.url}} connection string of database db
${{db.host}}, .port, .user, .password, .database separate parts
Terminal window
webshield apps env set DATABASE_URL='${{db.url}}' --redeploy

The app connects as owner app to database app; there is no superuser access.

The app receives the connection string in the DATABASE_URL environment variable. Node.js with the pg library:

import pg from "pg";
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
await client.query(
"CREATE TABLE IF NOT EXISTS hits (id serial PRIMARY KEY, at timestamptz DEFAULT now())",
);
await client.query("INSERT INTO hits DEFAULT VALUES");
const { rows } = await client.query("SELECT count(*) AS n FROM hits");
console.log(rows[0].n); // 1

Python with psycopg:

import os
import psycopg
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
conn.execute("CREATE TABLE IF NOT EXISTS hits (id serial PRIMARY KEY)")
conn.execute("INSERT INTO hits DEFAULT VALUES")
row = conn.execute("SELECT count(*) FROM hits").fetchone()
print(row[0]) # 1

The app creates its schema on start or with migrations — whichever suits you.

Backups are automatic: changes ship continuously, a full copy is taken daily. Data can be brought back to any moment of the last 7 days.

Restore next to a database creates a new database from the backup — either the latest state or the state at a chosen moment. The original keeps running, so you can compare the two and pull out what you need.

From the terminal:

Terminal window
webshield apps db window db # from and to which moment
webshield apps db restore db --as db2 --time 2026-10-21T15:00:00+03:00

Without --time it restores the latest state. The window opens with the first full backup, taken a few minutes after the database is created. The last few minutes of writes reach the archive with up to five minutes of delay: if you need everything up to the final write, restore the latest state.

To switch the app to the restored database, change the reference in its variable (${{db.url}} → ${{db2.url}}) and press Redeploy. The new database has its own password — the reference fills it in for you. Delete the old database once you no longer need it.

When the database itself runs out of space, writes fail with No space left on device while reads keep working. The size can be raised without downtime — do it in advance, before the errors.