Documentation · Managing your service

Working with databases

Provisioning a managed database, connecting to it safely, and why hosting one inside your app container keeps causing restarts.

Updated 2026-09-25 · 6 min read

Every plan includes database slots, provisioned from the Databases tab. Using a managed database instead of running one inside your app container is the single highest-value change most people can make.

Provision and connect

Create the database from the Databases tab. You get a host, port, database name, username and password, and the total count depends on your plan — from one on Starter up to eight on Max.

Put the credentials in a connection string and store that string as a single environment variable on your service:

# PostgreSQL
DATABASE_URL=postgresql://user:password@host:5432/database

# MySQL / MariaDB
DATABASE_URL=mysql://user:password@host:3306/database

# MongoDB
MONGODB_URI=mongodb://user:password@host:27017/database

One variable rather than five means there is one place to change and one place to get wrong.

Install the driver on the container

The client library has to be installed in your app's container, which is separate from the database itself. In Python, for example, psycopg2 or psycopg2-binary for PostgreSQL. A connection error mentioning a missing module is this problem, not a database problem.

Why not run the database inside your app container

It looks efficient — one service, no network hop — and it reliably causes problems:

  • Memory contention. A database reserves a buffer pool sized to a large fraction of available RAM, well beyond its working set. With your app in the same container, the two fight over the same limit and the kernel kills whichever is larger. This presents as random restarts under load.
  • Data loss on redeploy. Files uploaded over SFTP replace application code. A database in the same container gets wiped by a deploy unless you are deliberate about it.
  • Backups miss it. Backups capture your service files, not the data directory of a database you installed yourself.
  • It blocks upgrades. Any change to the app risks the database, which is a bad trade for independent scaling.

Use the managed slot. If you genuinely need to self-host — because of a specific extension or version — put it in its own service, sized for the database rather than the app, and accept the extra complexity knowingly.

Connection limits matter more than throughput

A 512 MB container cannot host fifty database connections — each one carries its own memory overhead in both the app and the database. Connection pools exist for this reason.

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 5,
  idleTimeoutMillis: 30_000,
});

Keep max small on small plans. If you see “too many connections” from the database while the app is idle, the pool is not being reused — a new connection per request defeats the pool entirely.

Migrate on deploy, not on start

Do not run migrations from the application entry point. On a restart, several instances can start simultaneously, and concurrent migrations on the same schema deadlock or partially apply.

Run migrations as an explicit step before or during deploy, and let the app assume the schema is already correct. If you deploy frequently, a scheduled task that runs migrations after a git pull keeps the two in step.

These guides describe behaviour that is common across container platforms. Where a setting is specific to your service — your assigned port, your SFTP credentials, your startup command — it is shown in the panel rather than here, so check the Startup and Files tabs for your own values.

Found an error or something unclear? Let us know so we can correct it.