Database
Pengin-Pi-3 uses PostgreSQL for relational data and FerretDB for documents. Both live in one PostgreSQL instance — FerretDB is a gateway speaking the MongoDB wire protocol against a Postgres database with the DocumentDB extension installed.
┌────────────────────────────────────────┐
│ PostgreSQL │
│ ${FERRETDB_DB} ← documentdb ext │
│ ${DB_NAME} ← Django's tables │
└────────┬──────────────────┬────────────┘
│ │
psycopg2 (SQL) FerretDB gateway
│ │ :27017
Django ──── pymongo ──┘
One instance, one volume, one backup. That is the main reason for FerretDB over MongoDB here.
Environment variables are covered in Configuration; this page is about getting the services themselves running.
You may not need any of this
settings.py falls back to a local SQLite file when Postgres details are
incomplete. Clone, install requirements, migrate, runserver — you have a
working instance with no database server at all.
What you lose is the document store. Dynamic content types
(Document Store) need FerretDB and will raise ConnectionFailure
without it. Everything else works.
If you're contributing to the core and not touching dynamic slugs, skip to Migrations.
Docker (recommended)
The compose stack handles all of this. Two services:
- postgres —
ghcr.io/ferretdb/postgres-documentdb:17-..., which is stock Postgres 17 with the DocumentDB extension already built in and configured - ferretdb —
ghcr.io/ferretdb/ferretdb:2.7.0, the gateway
On first boot the image's own init scripts install the extension, and
docker-entrypoint-initdb/01-create-app-db.sh creates Django's database
alongside it. Nothing else to do.
The image is not optional. A stock postgres:17-alpine starts cleanly
and then fails every document operation with schema "documentdb_api" does not exist. See Deployment.
The rest of this page is for running the services directly on a machine.
PostgreSQL
Fedora
sudo dnf install postgresql-server postgresql postgresql-contrib
sudo dnf install libpq-devel gcc python3-devel # for psycopg2
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
Fedora's initdb defaults to ident authentication for local connections,
which Django can't use. Edit /var/lib/pgsql/data/pg_hba.conf and change the
host lines for 127.0.0.1/32 and ::1/128 from ident to scram-sha-256,
then:
sudo systemctl restart postgresql
Debian and Ubuntu
sudo apt install postgresql postgresql-contrib
sudo apt install libpq-dev build-essential python3-dev
sudo systemctl enable --now postgresql
Debian and Ubuntu initialize the cluster on install and default to
scram-sha-256 for host connections, so no pg_hba.conf edit is usually
needed.
Windows
Use WSL2 with Fedora, Debian, or Ubuntu and follow the instructions above inside it. The deployment scripts assume a POSIX environment, and native Windows Postgres plus FerretDB is not a combination anyone here has tested.
macOS
Untested by the maintainers. Homebrew provides postgresql@17; libpq
usually needs adding to your path before pip can build psycopg2. For
FerretDB, use Docker Desktop.
User and database
Create a role and a database for the application:
sudo -u postgres psql
CREATE USER penginpi WITH PASSWORD 'choose-something-real';
CREATE DATABASE penginpi OWNER penginpi;
GRANT ALL PRIVILEGES ON DATABASE penginpi TO penginpi;
\q
On Postgres 15 and later the role also needs schema rights, which are no longer implied by database ownership:
sudo -u postgres psql -d penginpi -c \
"GRANT ALL ON SCHEMA public TO penginpi;"
Skipping that produces permission denied for schema public on the first
migration.
Do not create tables by hand. Django's migrations own the schema — users, slugs, events, history tables, everything. Your job is the role and the empty database.
Verify the login works:
psql -h 127.0.0.1 -U penginpi -d penginpi -c '\conninfo'
FerretDB
Two pieces, and they must be installed in this order: the DocumentDB extension into PostgreSQL, then the FerretDB gateway.
This is the fiddly part of a bare-metal setup. If it fights you, run just these two as containers alongside your local Postgres — or run the whole stack in Docker and develop against it.
1. DocumentDB extension
Packages are published on the
FerretDB/documentdb releases page
— .deb for Debian and Ubuntu, .rpm for RHEL-family systems. Download the
package matching your PostgreSQL major version and your FerretDB version;
the two are released in step and mismatches are not supported.
Take the production package, not the -dev or -dbgsym variants — those
carry debugging features that significantly slow things down.
Debian / Ubuntu
sudo apt install postgresql-17-cron postgresql-17-rum \
libmongocrypt0 libbson-1.0-0
sudo dpkg -i /path/to/documentdb.deb
Fedora. Fedora is not an official RPM target — the published .rpm
packages target RHEL and CentOS, and FerretDB's own docs still mark RPM
support experimental. A RHEL package for your Postgres major version will
often install on Fedora, but you may hit dependency mismatches on
pg_cron, rum, or the Mongo C driver. If it doesn't install cleanly,
run FerretDB's Postgres image in a container rather than fighting it:
docker run -d --name pengin-pg \
-e POSTGRES_USER=penginpi \
-e POSTGRES_PASSWORD=choose-something-real \
-e POSTGRES_DB=postgres \
-p 5432:5432 \
-v pengin_pg_data:/var/lib/postgresql/data \
ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0
That gives you a correctly configured Postgres with the extension already in place, and you create the application database inside it exactly as above.
2. Configure and load the extension
Add to postgresql.conf:
shared_preload_libraries = 'pg_cron,pg_documentdb_core,pg_documentdb'
cron.database_name = 'postgres'
documentdb.enableCompact = true
shared_preload_libraries only takes effect on a full restart, not a reload:
sudo systemctl restart postgresql
Then create the extension in the database named by cron.database_name:
sudo -u postgres psql -d postgres -c \
'CREATE EXTENSION IF NOT EXISTS documentdb CASCADE;'
CASCADE pulls in documentdb_core, pg_cron, and the other dependencies.
This is why FERRETDB_DB and DB_NAME are separate settings. The
extension installs into one database via pg_cron; Django's tables live in
another. Both are in the same instance. Keep FERRETDB_DB=postgres and
cron.database_name = 'postgres' matching.
Verify:
sudo -u postgres psql -d postgres -c '\dx'
You want documentdb, documentdb_core, and pg_cron listed.
3. FerretDB gateway
Download from the FerretDB releases page.
sudo dpkg -i ferretdb.deb # Debian / Ubuntu
sudo rpm -i ferretdb.rpm # RPM-based
ferretdb --version
The packages ship a systemd unit. Point it at Postgres:
FERRETDB_POSTGRESQL_URL=postgres://penginpi:PASSWORD@127.0.0.1:5432/postgres
Note the database in that URL is FERRETDB_DB — the one holding the
extension — not the application database.
sudo systemctl enable --now ferretdb
Or skip the package and run it as a container against your local Postgres:
docker run -d --name pengin-ferretdb -p 27017:27017 \
-e FERRETDB_POSTGRESQL_URL="postgres://penginpi:PASSWORD@host.docker.internal:5432/postgres" \
ghcr.io/ferretdb/ferretdb:2.7.0
Wiring Django to it
In .env:
DB_ENGINE=django.db.backends.postgresql
DB_NAME=penginpi
DB_USER=penginpi
DB_PASSWORD=choose-something-real
DB_HOST=127.0.0.1
DB_PORT=5432
FERRETDB_DB=postgres
MONGODB_URI=mongodb://penginpi:choose-something-real@127.0.0.1:27017/
MONGODB_DB_NAME=penginpi
All four of DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST are
required together. Any one missing and settings falls back to SQLite with
a printed warning — the app starts, so the only symptom is that your data
isn't where you think it is. Check the startup log.
Full reference in Configuration.
Migrations
The whole project is a single Django app (main) with one migrations
directory, main/migrations/. There is no cross-app migration
coordination, and every model — User, Slug, Event, TeamRole, all the
history models — shares the main app label.
First run
python manage.py migrate
python manage.py createsuperuser
Under Docker the web container runs collectstatic and migrate
automatically on every start, so a fresh stack is already migrated. You still
need to create the superuser yourself:
docker compose exec web python manage.py createsuperuser
After changing models
python manage.py makemigrations
python manage.py migrate
Being one app, a bare makemigrations picks up everything. makemigrations main is equivalent and more explicit.
Review the generated file before committing it. Adding a non-nullable field to a populated table will prompt for a default — answer it deliberately rather than accepting whatever's offered, since that value gets written into every existing row.
Useful checks:
python manage.py makemigrations --check --dry-run # anything unmigrated?
python manage.py showmigrations # what's applied
python manage.py sqlmigrate main 0012 # the SQL it will run
Under Docker
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
The project directory is not bind-mounted into the web container — only
media, staticfiles, and the blocklist are. A migration generated inside
the container lives only in that container and vanishes on rebuild. Generate
migrations locally, commit them, then rebuild.
Migrations run automatically on container start
Fine for a single web container, which is the shipped configuration. If you scale to several, they will race — split migrations into a separate step before the rollout at that point.
Seed data
python manage.py seed_departments
Creates example departments and titles for the RBAC framework. Edit the lists for your own project. See RBAC.
There's one convention worth preserving: every department needs an "Employee" title, because the role-assignment form falls back to it. The seed command establishes that; departments you create by hand should follow.
Verifying
Postgres
python manage.py shell -c \
"from django.db import connection; print(connection.vendor); \
print(connection.settings_dict['NAME'])"
Should print postgresql and your database name. sqlite means the fallback
fired.
FerretDB
python manage.py shell -c \
"from util.ferretdb import get_mongo_db; \
print(get_mongo_db().list_collection_names())"
An empty list is correct on a fresh install — collections are created lazily
on first write. A ConnectionFailure means the gateway isn't reachable.
Direct inspection
mongosh "mongodb://penginpi:PASSWORD@127.0.0.1:27017/penginpi"
> show collections
> db.slug_dynamic_data.find().limit(5)
Common problems
| Symptom | Cause |
|---|---|
| schema "documentdb_api" does not exist | Extension not installed, or installed in a database other than cron.database_name |
| permission denied for schema public | Postgres 15+ needs the explicit schema grant |
| Running on SQLite unexpectedly | One of the four DB_* values is blank — check the startup log |
| ConnectionFailure: Could not connect to FerretDB | Gateway down, or MONGODB_URI wrong. Fails after a 5s timeout. |
| psycopg2 won't build | libpq-devel / libpq-dev missing before pip install |
| FATAL: Ident authentication failed | Fedora's default pg_hba.conf — switch to scram-sha-256 |
| pg_cron errors on start | shared_preload_libraries needs a full restart, not a reload |
| Migration exists in container, not in git | Generate migrations locally; the source isn't bind-mounted |
Backup
One dump covers both stores — they're the same instance:
pg_dump -h 127.0.0.1 -U penginpi penginpi > app.sql
pg_dump -h 127.0.0.1 -U penginpi postgres > documents.sql
Or everything at once:
pg_dumpall -h 127.0.0.1 -U penginpi > full.sql
Under Docker:
docker compose exec postgres pg_dumpall -U "$DB_USER" > full.sql
Take a backup before any git pull that includes migrations. Django
migrations are not reliably reversible, and the container applies them on
start without asking.
Two things that grow and want watching: history snapshots write a full copy
of an object's fields on every tracked edit (History and Auditing), and
with local file storage, uploads accumulate in media/ with nothing pruning
them (Configuration).
See Backup and Restore.
Next
- Configuration — environment variable reference
- Deployment — the Docker stack
- Document Store — how FerretDB is used
- Install — first-time setup
Contents
- Database
- You may not need any of this
- Docker (recommended)
- PostgreSQL
- Fedora
- Debian and Ubuntu
- Windows
- macOS
- User and database
- FerretDB
- 1. DocumentDB extension
- 2. Configure and load the extension
- 3. FerretDB gateway
- Wiring Django to it
- Migrations
- First run
- After changing models
- Under Docker
- Migrations run automatically on container start
- Seed data
- Verifying
- Common problems
- Backup
- Next
Pages Here
No sub-pages yet.
Page Info
Wiki: Docs
Created on Sep 21, 2026 by Stuart Anderson
Maintainers
| Editor | Last Activity |
|---|---|
| Stuart Anderson creator | Sep 21, 2026 |