pymongo and never knows the difference; operationally, your data lives in Postgres.
This page explains the arrangement and the two things that surprise people: the port numbers and the empty
MONGODB_AUTH_SOURCE. For field-level detail see Data model.FerretDB in one paragraph
FerretDB is a proxy that accepts MongoDB wire-protocol connections and translates them into SQL against a PostgreSQL server carrying the DocumentDB extension. You get MongoDB’s document model and driver ecosystem with PostgreSQL’s storage, backup, and operational tooling. VoicEra never depends on MongoDB Inc. software, and the licence stays permissive.The container pair
Two containers, pinned indocker-compose.yaml:
FerretDB reaches Postgres over
FERRETDB_POSTGRESQL_URL, and postgres must pass its pg_isready healthcheck before FerretDB starts.
Ports: 27018 outside, 27017 inside
This is the detail that trips people up.
The container always listens on
27017. The host mapping is 27018 so it cannot collide with a MongoDB you already run locally. Change it with FERRETDB_HOST_PORT.
.env.example ships MONGODB_PORT=27018 because the default assumes you are running the API on your host against the Dockerised database. Inside the stack, docker-compose.yaml overrides MONGODB_HOST=mongodb and MONGODB_PORT=27017. If you set these by hand, match them to where the process actually runs.The mongodb network alias
The FerretDB service publishes a network alias:
mongodb:27017. The alias keeps connection strings readable and means nothing in the application refers to “ferretdb” by name.
Authentication
Credentials are PostgreSQL users. FerretDB negotiates SCRAM against them, which is why the auth source is deliberately blank:apps/api/app/config.py builds the URI and appends authSource or authMechanism only when they are non-empty:
Setting
MONGODB_AUTH_SOURCE=admin — correct for real MongoDB, and what the old mono repo used — appends ?authSource=admin and authentication fails. Leave it empty unless you have pointed VoicEra at an actual MongoDB server.POSTGRES_USER and POSTGRES_PASSWORD, so one credential pair covers both layers. Change them in Security hardening.
Connecting by hand
- mongosh
- psql
- Compass
Collections and indexes
apps/api/app/database_init.py runs on every API start. It is idempotent: it creates any missing collection and ensures every index, so a fresh volume becomes a working database with no migration step.
Collections: Organizations, Users, Memberships, ProviderAuth, Agents, PhoneNumbers, KnowledgeDocuments, CallLogs, CallMetrics, Campaigns, QueuedRuns.
Fields and indexes are documented in Data model.
There is no Alembic or migration tool. Schema is enforced by Pydantic models at the edge, and indexes are reconciled at startup.
Backups
Back up PostgreSQL, not FerretDB — Postgres holds the bytes.voicera_oss_ferretdb_postgres_data is the other thing to snapshot. A full backup also needs MinIO (recordings and transcripts) and the Chroma volume (RAG vectors) — see Daily operations.
Differences from MongoDB you may hit
FerretDB implements most of the wire protocol, not all of it. Known limits relevant here:
If you swap in real MongoDB, set
MONGODB_AUTH_SOURCE=admin and point MONGODB_HOST and MONGODB_PORT at it. Nothing else in the application changes.
Related
- Data model — collections, fields, enumerations
- Environment variables — every
MONGODB_*setting - Ports and defaults
- Architecture