A dply database is a Postgres, MySQL, or MongoDB server that belongs to one app. dply starts it, puts its address and password in the app's environment on the next deploy, backs it up continuously, and lets it sleep when nothing is connected so you pay for compute only while it is used. Use one when your app needs a real relational or document database. For small, read-heavy data at the edge, see Edge SQL (D1) instead.
Note
A database needs a card on the account. It is billed to that card as usage. SQLite does not need one.
Engines
| Engine | Version | Port | Login | Database |
|---|---|---|---|---|
| Postgres | 17 | 5432 |
app |
app |
| MySQL | 8.4 | 3306 |
app |
app |
| MongoDB | 7 | 27017 |
app |
app |
| SQLite | a file in the app |
Every server engine accepts TLS connections only. Plain-text connections are refused.
SQLite is a file at /tmp/database.sqlite inside the app. It is saved while the app runs and restored when the app wakes, and one instance serves the app so the file stays consistent. Migrations run each time the app starts. It has no backups, statistics, or console.
Manage SQLite
Choose the SQLite card on the app's map. Its sheet shows:
- The file: where it is in the app, the env the app gets, and the size and time of the last saved copy. While the app runs, it copies the file to dply's object storage every 20 seconds, and it puts that copy back when it wakes. Writes made in the last few seconds before the app sleeps can be lost, so use a dply database for data you can't afford to lose. Download a copy gives you that file (a link that works for 10 minutes; it needs permission to change the app).
- Tools, for Laravel and Rails apps: Migrate, Status, Seed, Prepare (Rails) and Roll back, run in the live app. If the running app still has another database from its last deploy (say you just detached Postgres), they tell you to redeploy instead of running.
- Add a database, for data that several instances or queue workers share. The first one you add becomes the app's primary and takes over
DB_*.
Create a database
- In your app, open Overview.
- Choose Add resource, then Database.
- Under Create new, pick Postgres, MySQL or MongoDB, and enter a Name (letters, numbers and dashes). Or choose Attach existing to use a database your organization already has.
- Choose Create database (or Attach). A card for it appears on the app's map. It starts at the smallest size, a 1 GB disk and 5 minutes' sleep; change those on its card (see below).
- Deploy the app.
The database starts on its first connection. The first start creates its disk and takes a few seconds, once. After that the app receives these values on every deploy:
| Engine | Environment variables set by dply |
|---|---|
| Postgres | DB_CONNECTION=pgsql, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_SSLMODE=require, DATABASE_URL |
| MySQL | DB_CONNECTION=mysql, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD, MYSQL_ATTR_SSL_CA, DATABASE_URL |
| MongoDB | MONGODB_URI, MONGO_URL, MONGODB_DATABASE |
| SQLite | DB_CONNECTION=sqlite, DB_DATABASE=/tmp/database.sqlite |
A value you save yourself in Environment variables wins over the one dply sets.
An app can have several databases. The first is its primary and uses the names above. Each other database uses the same names behind its own prefix, taken from its name: a database named analytics sets ANALYTICS_DB_HOST, ANALYTICS_DB_PASSWORD, ANALYTICS_DATABASE_URL and the rest (ANALYTICS_MONGODB_URI for MongoDB), and its card shows a Laravel connection that reads them. Migrations on deploy run against the primary. Choose Make primary on another database's card to swap which one uses the plain names; the old primary moves to its own prefix. Preview deployments never receive any of these values.
Databases are available to apps with server code: container apps, and SSR or hybrid apps. On an SSR or hybrid app the values arrive as Worker bindings (env.DATABASE_URL), and the Worker needs a driver that opens TCP sockets with the nodejs_compat flag. A database pool in front of the database saves the connection setup on every request.
Sizes and disk
| Size | Memory | Compute units |
|---|---|---|
| 0.25 vCPU | 1 GB | 0.25 |
| 0.5 vCPU | 2 GB | 0.5 |
| 1 vCPU | 4 GB | 1 |
| 2 vCPU | 8 GB | 2 |
| 4 vCPU | 16 GB | 4 |
The 1, 2 and 4 vCPU sizes appear in the size list once dply enables the larger database nodes. Until then only 0.25 and 0.5 vCPU can be picked. These sizes always stay on: they do not sleep when idle, and picking one sets Sleep to Stays on. Moving back to 0.25 or 0.5 vCPU gives you the sleep choice again. A new large database can take a few minutes to start the first time, while a node is added for it.
Disks come in 1 GB, 3 GB, 5 GB, 10 GB, and 25 GB. You can change the size and the disk later from the same sheet. A new size applies the next time the database wakes. A disk only grows: picking a smaller one is refused.
Each size is tuned for its memory. Postgres gets a quarter of it for shared buffers and caps the write-ahead log at a quarter of the disk. MySQL gives the buffer pool half the memory. MongoDB sizes the WiredTiger cache to the plan.
Suggested resizes
dply watches each database's memory and how many reads come from memory, and suggests a different size when it would help. It never resizes on its own, because a resize restarts the database.
- Bigger: when the database has held over 85% of its memory, or served under 90% of reads from memory, for the last 6 hours it ran.
- Smaller: when it has used under 35% of its memory for 7 days, with nearly every read from memory.
Every database on the app gets its own suggestions, the primary and the others. The suggestion appears at the top of the database's sheet with the reason and the price change, and you get a notification that links to it. Choose one:
- Resize now: the database restarts at the new size the next time the app connects. Open connections drop once.
- Resize tonight: at 03:00 in your organization's time zone. Choose Cancel on the sheet to undo it before then.
- Dismiss for 30 days: the same suggestion stays hidden for 30 days.
Only people who can change the app see these buttons.
Sleep and wake
Pick how long the database waits after the last connection closes before it sleeps: 1 minute, 5 minutes, 15 minutes, or Stays on. A sleeping database stops its compute and keeps its data on its disk. The next connection wakes it in about a third of a second; the client waits while it does.
Compute is billed only while the database is awake. The disk is billed whether it is awake or asleep. Stays on bills every hour.
Anything that keeps a connection open keeps the database awake. A queue worker polling a database queue is the common case. Put queues on Valkey if you want the database to sleep.
Connect
Open the database card, then the Connect tab. It shows the Host, Port, User, Password (choose Show), a Connection URL, and a command to open a shell. The host has the form <id>.db.dply.io.
Laravel
The next deploy sets DB_CONNECTION, the host, the password, and DATABASE_URL, so the stock config/database.php works unchanged. To run migrations on each start, open Sleep, scaling, region on the app card and turn on Run migrations when a container starts (migrate --force --isolated).
// config/database.php works as shipped. For Postgres, dply-edge also sets:
// DB_SSLMODE=require
$users = DB::table('users')->count();
Database pooler
When the database is far from the app, opening a new TLS connection to it can add a few hundred milliseconds to every request. For a Laravel app on a dply Postgres, open Sleep, scaling, region and turn on Database pooler (PgBouncer). Apps created since 2026-11-06 start with it on. After the next deploy, each container runs PgBouncer, and requests connect to it locally while it keeps the connections to the database open. The app's DB_HOST, DB_PORT, DB_SSLMODE and DATABASE_URL point at PgBouncer automatically; nothing in config/database.php changes.
- PgBouncer runs in session mode, so transactions, advisory locks and
LISTENwork as before. - Each instance keeps at most 8 connections open. Postgres allows 50 in all, and dply keeps 5 free and leaves room for your queue workers, so an app that can run many instances gets fewer per instance. A request that finds every connection busy waits for one.
- It applies to FrankenPHP and php-fpm. Worker mode (Octane), queue workers and all-in-one apps keep their connections open, so they connect directly.
- PgBouncer listens on
127.0.0.1without TLS. If yourconfig/database.phphard-codes'sslmode' => 'require'(orverify-ca,verify-full), the deploy leaves the pooler off and says so in the build log. Change it toenv('DB_SSLMODE', 'prefer')(the stock value) and redeploy. - To see the pool, open the app and use Check now next to where it runs. It shows how many of the instance's database connections are busy and idle, and how many requests are waiting for one. Requests that keep waiting mean the pool is too small: lower the app's maximum instances or move queue workers to fewer processes.
- The database still sleeps when the app is idle, and the next request wakes it through PgBouncer.
- Connecting through PgBouncer takes about a millisecond. dply also skips Laravel's
set search_path to "public"on every connect to a dply Postgres (pooled or not), since the database already defaults topublic; asearch_pathyou set yourself is still sent. - Each query still makes one round trip to the database. Only placing the app near its database shortens that.
Node
// Postgres
import pg from 'pg';
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
// MongoDB
import { MongoClient } from 'mongodb';
const mongo = new MongoClient(process.env.MONGODB_URI);
const db = mongo.db(process.env.MONGODB_DATABASE);
A shell
# Postgres
psql "postgresql://app@<id>.db.dply.io:5432/app?sslmode=require"
# MySQL: the mysql client needs the host as the TLS server name
mysql -h <id>.db.dply.io -P 3306 -u app -p --ssl-mode=REQUIRED --tls-sni-servername=<id>.db.dply.io app
The shell asks for the password from the Connect tab. Connecting wakes the database if it is asleep.
Important
The database address is public, protected by TLS and the password. Your client must send the host name (SNI) during the TLS handshake. Connecting by IP address does not work.
Read-only login
For Postgres and MongoDB, Connect can create a second login, app_ro, that can read every table and change nothing. Use it for BI tools such as Metabase. Choose New password to create or rotate it; the password is shown once. Turn off removes it.
MySQL latency
MySQL sends each parameterised query in two round trips. Set DPLY_MYSQL_ONE_ROUND_TRIP=true in Environment and redeploy to send each in one; parameters are then escaped into the query by PHP instead of bound by MySQL.
Region
Every database runs in the region shown on the sheet (currently New York). Container apps that use a dply database run near it by default. The Overview tab shows the measured round trip from the app at its last deploy. See Data regions.
Backups and restore
Every change is streamed to storage, and a full backup runs daily. Backups are kept for 7 days on Starter, 14 on Pro and 30 on Team, and a weekly check restores each Postgres database from its backups to prove they work. This works the same for all three engines: Postgres uses its write-ahead log, MySQL its binlog, and MongoDB its oplog.
To restore:
- Open the database card, then the Backups tab.
- Under Restore to a point in time, enter a Time (UTC) or use a Quick pick.
- Choose Restore and confirm.
Restoring replaces the data with how it was at that second. It takes a few minutes. The app keeps its address and password. The Backups tab also shows when the last full backup ran and whether saving recent changes is healthy.
To look before you replace anything, enter a name under Or restore into a new database named and choose Restore as new. The data as it was at that time goes into a new database of the same engine, size and disk, and the original is left as it is. The copy is attached to the app with its own env prefix, so detach it if the app should not use it, and delete it when you are done: it bills like any database. Changes from the last minute or so before the time you pick may not have reached the backups yet.
Export and import
Under Export and import, Export now writes a full copy of the data as one file and keeps it beside the backups. Download saves it; Load loads it back.
| Engine | Export format | Import your own dump |
|---|---|---|
| Postgres | pg_dump -Fc |
Yes, a pg_dump -Fc file |
| MySQL | mysqldump, gzipped |
No |
| MongoDB | mongodump --archive |
Yes, a mongodump --archive file, gzipped or not |
To import, choose Get upload command, run it where the file is (the link works for an hour), then choose Load it. For Postgres, tables in the file are replaced and others are left alone. For MongoDB, everything in the database is replaced.
Look inside
The database sheet has tabs for day-to-day work. None of them wakes the database until you load or refresh something. For a database that isn't the primary, open its card and choose Stats, console & backups: the same tabs, apart from Connect and Settings, which are on its card.
- Overview: state, size, disk use, awake hours this month, and round-trip time from the app.
- Statistics: data size, tables, rows, connections, cache hit rate, and the largest tables. Load live stats wakes the database.
- Queries: the queries that took the most total time, and what is running now. You can cancel a running query.
- Health: disk use, tables that probably need an index, indexes nothing uses, dead rows, and Postgres extensions (Turn on).
- Console: one read-only statement at a time, up to 200 rows and 15 seconds.
- Settings: Migrate, Seed, Prepare, and Roll back, and Run migrations when a container starts.
Pricing
Compute is billed per second while awake. Storage is billed per GB-month on the disk size you picked, awake or asleep.
| Size | Memory | Per second awake | Per hour awake |
|---|---|---|---|
| 0.25 vCPU | 1 GB | $0.00000833 | $0.03 |
| 0.5 vCPU | 2 GB | $0.0000167 | $0.06 |
The larger sizes, once enabled, stay on all month: 1 vCPU is $0.18 an hour (about $129.60 a month), 2 vCPU $0.24 (about $172.80) and 4 vCPU $0.48 (about $345.60), plus the disk.
Storage, and compute per compute unit (the 0.25 vCPU size is a quarter of one):
| Meter | Price | Unit |
|---|---|---|
| Compute | $0.0000333 | per compute-unit-second (1 vCPU, 4 GB) |
| Storage | $0.20 | per GB-month |
A 0.25 vCPU database that stays on all month (720 hours) with a 1 GB disk is about $21.80. The same database awake 8 hours a day is about $7.40. This bills at the invoice line Databases. Usage shows on the app's bill and in Usage & metering.
What each database's card shows
Each database's card has its own Tools (Laravel apps): Migrate, Status, Seed and Roll back run with --database set to that database, using its prefixed env. Laravel runs every migration against the connection you give it, so set $connection on the migrations that belong to it. The app needs dply/laravel 1.2 or later for this.
Every database has everything on this page: statistics, the console, backups and restore, export and import, suggested resizes, and the disk, connection, memory and backup alerts. The primary's card opens them from Stats & backups. Another database's card shows its disk use, connections and last backup, updated hourly without waking it, and opens the rest from Stats, console & backups. Its awake time and storage are its own; the primary's Awake this month counts only the primary.
From the API and the CLI
The API and the CLI list an app's databases and, for any of them, run a read-only query, start and list exports, and restore to a point in time:
dply-edge edge databases list
dply-edge edge databases query reports "select count(*) from orders"
dply-edge edge databases export reports
dply-edge edge databases restore reports "2026-09-28 14:30"
dply-edge edge databases restore reports "2026-09-28 14:30" --as-new reports-check
Creating, attaching, resizing and deleting a database are in the dashboard.
Detach or delete a database
Open the database's card, then under Manage:
- Detach takes it off this app. The database, its data and its bill stay in your organization, so you can attach it to another app, or back to this one, with Add resource → Database → Attach existing. When you detach the primary, the next database becomes primary; with none left, the app goes back to SQLite.
- Delete destroys it. Type its name to confirm. A database attached to other apps can't be deleted until you detach it there.
Databases no app uses
A detached database keeps running and billing for its disk. Projects → Databases lists every Postgres, MySQL and MongoDB database in your organization, with the apps using each one. From there you can:
- Attach it to an app. Tick as its primary to make it the app's
DB_*connection. - Detach it from an app.
- Delete it, once no app uses it.
Redeploy the app after attaching so it picks up the connection.
To move to another engine, add a database with the new engine, move your data, make it primary, then delete the old one.
Warning
Deleting a database deletes its disk and all of its backups at once. It cannot be undone. Choose Export now and Download first if you want to keep the data.