Skip to content

Database Operations

If your application uses a PostgreSQL database (database: enabled: true in appmecca.yaml), AppMecca provides commands to back up, restore, and manage your data.

Backups moved. The old mecca db * subcommand group has been retired entirely — there is no mecca db create, mecca db backup, or mecca db drop. Backups are now an app-level primitive that covers both your database and any drives you declare — see Backups. This page keeps the deeper database context (identity, lifecycle, connection) but the CLI surface for taking and restoring backups lives on mecca app *.

Overview

AppMecca creates a managed PostgreSQL database for your app. Your application connects to it using environment variables that AppMecca injects automatically — see Connecting to your database below.

The app-level backup primitive lets you:

  • Back up your database + every declared drive as a single atomic bundle
  • Restore from a previous bundle (full or partial via --db-only / --drives-only)
  • List available bundles for an app
  • Download a bundle as pre-signed URLs for off-platform archival

See Backups for the full CLI reference.

Database Lifecycle

The database lifecycle is entirely implicit — there are no create or drop commands.

Moment What happens
Created On the app's first deploy, if appmecca.yaml sets database: enabled: true. The platform provisions the database, its user, and a generated password, then injects the connection variables into every container. Re-deploying is idempotent — an existing database is reused, never recreated.
Backed up Automatically before every deploy, and on demand with mecca app backup.
Dropped By mecca app delete <app>, which takes a safety backup first. Nothing else drops it.
# In appmecca.yaml, alongside your containers: and deploy: sections.
# These two lines are the entire opt-in.
database:
  enabled: true

Need the database before your app boots?

Run migrations from your container's entrypoint. The database exists and the connection variables are already injected by the time your process starts, so an entrypoint that runs migrations before handing off to your server is the supported pattern. If you need to load an existing dataset instead, use restore from upload — it works on an app you have only registered, before any deploy.

Connecting to Your Database

AppMecca injects these variables into every container of a database-enabled app:

Variable Contains
DATABASE_URL The full connection string. Prefer this — it is the one value that is always correct.
DB_HOST Database hostname
DB_PORT Port (5432)
DB_NAME Database name
DB_USER Username
DB_PASSWORD Password

Database and user names are opaque — never construct them

The database and user names are generated hashes such as am_a1b2c3d4e5f6g7h8. They are not {app}_db / {app}_user and the naming scheme is not part of the contract. Always read DATABASE_URL (or the DB_* variables); code that builds a name from the app name will connect to nothing.

Connection limits

Declare how many connections each replica of your app opens:

database:
  enabled: true
  max_connections: 10    # default 5, range 1-50

The platform sums max_connections × replicas across every database-enabled app before admitting a deploy, and refuses deploys that would exhaust the shared database. Tune it to match your framework's pool setting (SQLAlchemy pool_size, Hikari maximumPoolSize, pg.Pool max), and if you genuinely need more than 50, ask your operator rather than raising the declaration.

Creating a Backup

mecca app backup myapp -e dev

Add -b to target a specific branch. Omit it and AppMecca resolves your production branch automatically — it does not silently assume main:

mecca app backup myapp -e dev -b feature/payments

The backup runs asynchronously. The command queues it and returns an action ID immediately; it does not wait.

Expected output
✓ Backup queued 7c2f91ad-4b0e-4d55-9f2a-1e6b3c8d0a44  2 drive(s)

  Status: mecca queue status 7c2f91ad-4b0e-4d55-9f2a-1e6b3c8d0a44
  List:   mecca app backups myapp

Options

Flag Short Description
--description "..." -d Free-form label, shown in mecca app backups
--branch -b Git branch (omit to resolve your production branch)
--quiesce Advisory marker only — records intent in the manifest, does not stop your services

Adding a description

Descriptions help you remember why a backup was taken:

mecca app backup myapp -e dev -d "before v2.0 migration"

Following progress

Use the action ID the command printed:

mecca queue status 7c2f91ad -e dev

mecca queue status takes an action ID (a full or partial UUID prefix), not an app name.

Back up before risky changes

Always take a backup before running database migrations, deploying a major version, or making any change that modifies your schema. It takes seconds and can save hours of recovery work.

Listing Backups

To see all available backup bundles for an app:

mecca app backups myapp -e dev
Expected output
Backups: myapp (main)
Last modified         Prefix                                                          Type
2026-01-15 10:23:45   backups/dev/myapp/main/manual/20260115-102345-pre-v2-migration  bundle v1
2026-01-14 08:00:00   backups/dev/myapp/main/pre_deploy/20260114-080000               bundle v1
2026-01-13 08:00:00   backups/dev/myapp/main/pre_deploy/20260113-080000               bundle v1

The Prefix column is what you pass to mecca app restore and mecca app download. A bundle is a directory of objects, not a single file.

Limiting and filtering

# One branch only (omit -b to resolve the production branch)
mecca app backups myapp -e dev -b feature/payments

# Cap the number of rows (default 20, max 200)
mecca app backups myapp -e dev -n 5

# Machine-readable output
mecca app backups myapp -e dev --json

All list-backups options

Flag Short Description
--limit -n Max number of bundles to show (default 20, max 200)
--branch -b Git branch (omit to resolve your production branch)
--json Output as JSON (a global flag, available on every command)

There is no --type or --since filter — the bundle type is shown as part of each prefix (manual, pre_deploy, pre_drop), so filter with grep if you need to.

Restoring from a Backup

To restore your database to a previous state:

mecca app restore myapp backups/dev/myapp/main/manual/20260115-102345 -e dev --force

The --force flag skips the confirmation prompt. Without it, you will be asked to confirm.

The restore is asynchronous — the command queues it and prints an action ID:

Expected output
Restore queued: 31d0c7fe-2a55-4f1c-b6de-9c04e2f7a118

  Status: mecca queue status 31d0c7fe-2a55-4f1c-b6de-9c04e2f7a118

Restore options

Flag Short Description
--force Skip confirmation
--branch -b Branch to restore into. Required when the app has more than one branch — an omitted branch is refused rather than guessed
--db-only Restore the database, leave drives untouched
--drives-only Restore drives, leave the database untouched
--drives <names> Comma-separated subset of drives to restore
--in-place Skip the safe atomic-swap and extract directly over live drive contents

Restore replaces everything

A restore resets the existing database and reloads it from the bundle. Any data written after the backup was taken will be lost. Take a fresh backup before restoring, so you have a snapshot of the current state.

A restore is all-or-nothing

The database restore runs as one transaction: if any part of it fails, the database is left exactly as it was, and the error says so. One exception: PostgreSQL large objects. They are backed up with the rest of your data, but the dump commits them in a transaction of its own, after your table data and before your indexes and constraints — so a failure after that point can leave part of the restore applied. The platform checks for exactly this after every failed restore, and the error then says "the restore was NOT atomic … the database's previous contents were replaced and it holds part of the dump" instead of "NOT modified" — restore an earlier bundle. A bundle restore removes your app's large objects with everything else, including those its runtime role owns (see the runtime role), and restores the bundle's. A large object owned by any other role is left alone and can block the restore (the message says so). While it runs, the app's idle database connections are closed and its queries wait until the restore finishes; each waiting query holds one of the database server's connection slots, which are shared with other apps, so restore at a quiet time or scale the app down first. A restore that would need more than half of the database server's lock table is refused before it starts, with the database untouched and a message saying so.

Cross-branch restore

You can restore a bundle taken from one branch into another. This is useful for cloning production data into a feature branch for testing:

# Back up the production branch
mecca app backup myapp -e dev -d "for-feature-test"
# Restore that bundle into the feature branch
mecca app restore myapp <bundle-prefix> -e dev -b feature/payments --force

Restore workflow

A typical restore workflow looks like:

# 1. Take a backup of the current state (just in case)
mecca app backup myapp -e dev -d "before restore"

# 2. List bundles to find the prefix you want
mecca app backups myapp -e dev

# 3. Restore from the target bundle
mecca app restore myapp backups/dev/myapp/main/pre_deploy/20260114-080000 -e dev --force

# 4. Follow it to completion
mecca queue status <action-id> -e dev

# 5. Restart the app so it reconnects to the restored database
mecca app restart myapp web -e dev --force

Downloading a Backup

To get a bundle off-platform (for local development or archival):

mecca app download myapp backups/dev/myapp/main/manual/20260115-102345 -e dev

This prints one pre-signed URL per object, one per line — it does not write any files itself:

Expected output
https://…/database.sql.gz?X-Amz-Expires=3600&…
https://…/drives/media.tar.gz?X-Amz-Expires=3600&…
https://…/manifest.json?X-Amz-Expires=3600&…

Fetch them with whatever you like:

mecca app download myapp <bundle-prefix> -e dev | xargs -n1 curl -fsSLO
Flag Description
--expires-in <seconds> URL lifetime. Default 3600 (1 hour), max 86400 (24 hours)

Using a downloaded backup locally

The database half of a bundle is a gzipped SQL dump. To load it into a local PostgreSQL database:

gunzip -c database.sql.gz | psql -h localhost -U postgres myapp_dev

Direct download

The URLs point straight at AppMecca object storage, so the transfer runs between your machine and the storage service. It does not pass through the AppMecca API or a deploy host.

Restoring from a Dump You Uploaded

Bringing an existing database onto AppMecca — a local dev DB, a snapshot from another host, a pg_dump a colleague sent you — is a restore from upload: you upload a dump and AppMecca replaces the app's database with it.

This replaces every table in the app's database

A safety backup is taken first, so a wrong file is recoverable. But the app loses its database connections for the duration, and in-flight requests will error. Restore into a non-production branch first if you can.

Take the dump

Recommended — custom format. It Just Works, whatever roles or grants exist in your source database:

pg_dump -Fc mydb > prod.dump

Plain SQL also works, but it must be taken with --no-owner --no-acl:

pg_dump --no-owner --no-acl mydb | gzip > prod.sql.gz

A plain dump taken with default flags will be refused

A default pg_dump embeds ALTER … OWNER TO <your-local-role> and GRANT statements. Those roles do not exist on AppMecca's managed database, so the restore would abort partway through. AppMecca scans the dump and refuses it up front, before touching your database, with a message telling you to re-dump. Nothing is lost — just re-dump as shown above.

A plain dump is loaded as data and schema, never as a psql script

AppMecca loads a plain dump with psql's backslash commands switched off, so a line such as \! … or \i … is refused by psql and nothing after it runs; the restore then fails and your database is rolled back from the safety backup, and the error names the first line of your file that begins with a backslash. A dump made by pg_dump never contains such commands: text in your data, function bodies or comments that happens to start a line with a backslash is restored normally and is never refused. A file that joins several pg_dump outputs together is refused before your database is touched — upload one dump per restore. The custom format (pg_dump -Fc) cannot carry psql commands at all, which is another reason it is recommended.

Restore it

Open your app in the dashboard → Database tab → choose the file → confirm (you type the app name) → watch the streaming log.

Or from the CLI:

mecca app restore-upload myapp --file prod.dump -e dev

It asks you to confirm, then to type the app name, uploads the file, and queues the restore. Add -b to choose the branch whose database is replaced. Omit it and AppMecca uses the app's only branch — and the confirmation names it. An app with more than one branch is refused until you pass -b, because guessing which database to replace is the one mistake that cannot be undone:

mecca app restore-upload myapp --file prod.dump -b feature/payments -e dev
Flag Short Description
--file The local dump to upload (custom -Fc format recommended)
--branch -b Branch whose database is replaced. Required when the app has more than one branch — an omitted branch is refused rather than guessed
--quiesce Mark the pre-restore safety backup as quiesced (advisory — your app keeps running)
--force Skip the yes/no prompt. The typed app name is still required

The dump uploads straight into AppMecca object storage — it never passes through the AppMecca API.

Works even if the app has never been deployed

You can restore into an app you've only registered — the database doesn't have to exist yet. AppMecca provisions it on demand as part of the restore, so there's no "deploy once first" step. Restore is branch-aware: pick the branch in the dialog, or pass -b on the CLI, to target a specific branch's database.

Checking on it

The restore runs on the platform, so you can close your terminal. The dashboard streams the log live and shows the result summary below.

From the CLI, the result summary — per-table row counts, and whether it rolled back — is one command away once the restore finishes:

mecca app restore-upload-status myapp <action-id>

You can also follow the queue row:

mecca queue status <action-id>

That shows state, error, and which host claimed it — useful when a restore has not started yet or has failed outright.

What you get back

On success, the result lists every restored table with its record count:

Restored 14 tables, 128,904 rows.

  users            1,204
  orders          98,317
  order_items     28,551  (approx)
  …

(approx) means the exact count took longer than five seconds and this is PostgreSQL's own row estimate — not a sign that rows are missing.

If it says it rolled back

A restore that fails partway is rolled back from the safety backup it took first, and the dashboard says so above the table list. When that happens the counts you see are the safety backup's, not your dump's — your database is as it was before you started.

A count marked approx means the exact count(*) exceeded a 5-second budget and AppMecca reported the planner's estimate instead — the data is there, only the number is approximate.

Limits and rules

Max size 5 GB
Formats -Fc custom (recommended), plain .sql, gzipped plain .sql.gz
Not accepted a gzipped custom dump (.dump.gz) — upload the .dump directly
Extensions the dump's extensions are created for you if they are on the platform allowlist (citext, pgcrypto, uuid-ossp, hstore, pg_trgm, ltree, …). Anything granting network or filesystem access (dblink, postgres_fdw, …) is refused — ask your operator.
Postgres version a dump from a newer major than the server is refused; older is fine

If something goes wrong

  • Refused before anything happened — bad format, forbidden statements, a disallowed extension, or a too-new Postgres. Your database is untouched. Fix the dump and retry.
  • Failed and rolled back — the dump applied partway then errored; AppMecca restored the safety backup. Your database is back as it was.
  • "the app kept reconnecting and held locks" — your app reopened its connection pool faster than the restore could take its locks. Your database is untouched. Scale the app to zero, then retry — that is the only way to release the app-held locks (the --quiesce flag is advisory and does not stop the app).
  • Both the restore and the rollback failed — rare, and AppMecca stops immediately rather than making it worse. The message names the safety bundle; restore it from the Backups tab. Your uploaded dump is kept too.

Deploys are blocked while a restore runs

A deploy started mid-restore could run migrations against a half-restored database, so the dashboard disables the Deploy button until the restore finishes.

How Backups Work

Behind the scenes:

  1. You run mecca app backup, which queues a backup action and returns its ID.
  2. A deploy agent claims the action.
  3. The agent runs pg_dump against your database and archives every declared drive.
  4. Each piece is compressed and written into AppMecca object storage under the bundle prefix.
  5. A manifest.json is written last, recording checksums and byte counts — its presence is what marks the bundle complete and restorable.

Bundles live under a backups/{env}/{app}/{branch}/… prefix and persist until the retention policy prunes them, or the environment is torn down.

Next Steps