Skip to content

Database Configuration

Deckyard stores its data in PostgreSQL. PostgreSQL is the only storage backend: the old file-based backend (JSON on disk) was removed in 1.x.

  • PostgreSQL: presentations, versions, users, settings, libraries, published decks and everything else except media.
  • Uploads: uploaded media stays on disk in server/uploads/ (override with UPLOADS_DIR), unless you use an external media provider.
  • Data directory: server/data/ (override with DATA_DIR) now only holds the deck-thumbnail cache, plus whatever an older install left behind.

The compose stack ships its own postgres service (PostgreSQL 16, data in the pg_data volume) and the app container uses it out of the box. Nothing needs to be set, and migrations are applied automatically at container start.

To use a managed database instead, set the connection variables below in .env. Do not set DATABASE_HOST=localhost for compose: inside the container localhost is the app itself, not the database (the bundled one is the host postgres). The container entrypoint refuses to start on a localhost value.

When you run npm start from a checkout, point the connection variables at your own PostgreSQL and apply the migrations once (and again after each update):

Terminal window
npm run db:migrate

Here DATABASE_HOST=localhost is the normal value.

Using individual variables:

Terminal window
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=deckyard
DATABASE_USER=deckyard
DATABASE_PASSWORD=your-secure-password

Using a connection URL:

Terminal window
DATABASE_URL=postgres://deckyard:password@localhost:5432/deckyard

When DATABASE_URL is set it is the complete override: host, port, database, user and password all come from the URL, never a mix with the DATABASE_* variables. The SSL and pool settings below still apply on top. The app, db:migrate and db:import all read the same settings.

STORAGE_MODE defaults to postgres and can be left unset. postgres is its only accepted value; file, postgresql or anything else stops the server at boot.

SSL is enabled by default for any host other than localhost or 127.0.0.1.

Terminal window
# Disable SSL (e.g. an internal network)
DATABASE_SSL=false
# Allow self-signed certificates (e.g. managed database services)
DATABASE_SSL_REJECT_UNAUTHORIZED=false

The compose stack sets DATABASE_SSL=false by default, since the bundled database runs on the internal Docker network.

Terminal window
DATABASE_POOL_MIN=2 # Minimum connections (default: 2)
DATABASE_POOL_MAX=10 # Maximum connections (default: 10)

Schema migrations live in server/db/migrations/ and are tracked in a _migrations table, so running them again is a no-op.

  • Docker: the container entrypoint runs pending migrations before the server starts, retrying while the database comes up.
  • Without Docker: the server does not migrate on its own; run npm run db:migrate after installing or updating.
Terminal window
npm run db:migrate # Apply pending migrations
npm run db:migrate:status # Show applied and pending migrations
npm run db:migrate:down # Roll back the most recent migration

In a running compose stack, prefix these with docker compose exec app.

An install that used file storage still has its decks under server/data/. Deckyard refuses to start when it finds decks on disk while the database holds none, so you get a stopped server instead of an empty organization. Import the data once:

Terminal window
npm run db:migrate # not needed on Docker; the entrypoint already did this
npm run db:import # add -- --dry-run to preview first

In compose: docker compose exec app npm run db:import. The import is idempotent and never modifies your files. It covers presentations, tags, the image and slide libraries, slide collections and published presentations; version history is moved by a schema migration. Remove STORAGE_MODE=file from your environment if it is still set.

Once PostgreSQL is verified to hold your data, the leftover files can be cleaned up with scripts/prune-legacy-data.js. Do not delete them by hand: the boot check uses server/data/presentations/ to detect an un-imported install.

Back up the database together with server/uploads/. On the compose stack:

Terminal window
docker compose exec postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup.sql

Outside compose, back up your own PostgreSQL the same way, plus server/uploads/.

VariableDefaultDescription
STORAGE_MODEpostgresStorage backend; postgres is the only accepted value
DATABASE_URL(unset)Full connection URL; overrides the host, port, name, user and password variables
DATABASE_HOSTlocalhost (postgres in compose)PostgreSQL host
DATABASE_PORT5432PostgreSQL port
DATABASE_NAMEdeckyardDatabase name
DATABASE_USERdeckyardDatabase user
DATABASE_PASSWORD(empty; deckyard in compose)Database password
DATABASE_SSLtrue for non-localhost hosts (false in compose)Enable SSL
DATABASE_SSL_REJECT_UNAUTHORIZEDtrueReject self-signed certs
DATABASE_POOL_MIN2Minimum pool connections
DATABASE_POOL_MAX10Maximum pool connections
DATA_DIRserver/dataData directory (thumbnail cache, legacy files)
UPLOADS_DIRserver/uploadsUploaded media directory