Skip to main content
VoicEra stores its documents in FerretDB, which speaks the MongoDB wire protocol on top of PostgreSQL. Application code uses 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 in docker-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:
So in-stack services connect to 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.
The same values become POSTGRES_USER and POSTGRES_PASSWORD, so one credential pair covers both layers. Change them in Security hardening.

Connecting by hand

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.
The volume 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.