Running Ory Polis on DynamoDB, MongoDB or Redis
Ory Polis has built-in support for SQL databases (Postgres, CockroachDB, YugabyteDB, MySQL, MariaDB, Microsoft SQL Server and
SQLite) and for PlanetScale. Amazon DynamoDB, MongoDB and Redis are supported through driver packages that are not part of the Ory
Polis image or of the @boxyhq/saml-jackson library, so that Ory Polis does not carry the client libraries of every database it
can run on. This page explains how to run Ory Polis with one of these drivers.
| Database | Driver package |
|---|---|
| Amazon DynamoDB | @boxyhq/polis-driver-dynamodb |
| MongoDB | @boxyhq/polis-driver-mongodb |
| Redis | @boxyhq/polis-driver-redis |
The packages are released together with Ory Polis and carry its version number. Install the version that matches the image or library you run.
External drivers are not available in the Ory Polis Enterprise License FIPS 140-3 image (polis-oel-fips), which loads no code
outside the image. Run FIPS mode on a SQL database or on PlanetScale.
Build an image with the driver
The driver package has to be present in the container. Build an image on top of the Ory Polis image that installs the package
under /opt/polis-drivers, a directory that is on the image's NODE_PATH. Pin both the Polis image and the package to the same
version:
FROM node:24-alpine AS drivers
WORKDIR /opt/polis-drivers
RUN npm install --omit=dev @boxyhq/polis-driver-dynamodb@<polis version>
FROM ory/polis:<polis version>
COPY --from=drivers /opt/polis-drivers /opt/polis-drivers
Use @boxyhq/polis-driver-mongodb or @boxyhq/polis-driver-redis in place of the DynamoDB package for those databases. For the
Ory Polis Enterprise License, use the polis-oel image from your Ory Enterprise registry as the base image in place of
ory/polis.
Then start the image with DB_ENGINE=custom, DB_DRIVER set to the package name, and the
variables the driver needs, listed below. Everything else, such as DB_ENCRYPTION_KEY, DB_TTL and DB_PAGE_LIMIT, applies to
external drivers as it does to the built-in databases.
Amazon DynamoDB
| Variable | Value |
|---|---|
DB_ENGINE | custom |
DB_DRIVER | @boxyhq/polis-driver-dynamodb |
DB_URL | The DynamoDB endpoint, such as https://dynamodb.eu-west-1.amazonaws.com |
DB_DRIVER_OPTIONS | Optional JSON: {"region":"eu-west-1","readCapacityUnits":5,"writeCapacityUnits":5} |
region defaults to us-east-1, the capacity units to 5. The driver takes its AWS credentials from the AWS SDK's default
credential chain: an IAM role attached to the ECS task, EKS pod or EC2 instance, or AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY where there is none. It creates the jacksonStore and jacksonIndex tables with their indexes on first
start, so the credentials need permission to create tables the first time.
DynamoDB cannot skip records, so list calls ignore pageOffset and page by the pageToken the previous page returned. The SCIM
endpoints an identity provider calls page with startIndex and have no token, so on DynamoDB an identity provider reads at most
DB_PAGE_LIMIT users or groups of a directory. Do not run directory sync on DynamoDB with
directories larger than DB_PAGE_LIMIT.
MongoDB
| Variable | Value |
|---|---|
DB_ENGINE | custom |
DB_DRIVER | @boxyhq/polis-driver-mongodb |
DB_URL | The connection string, such as mongodb://host:27017/polis |
The driver takes no DB_DRIVER_OPTIONS. It creates the jacksonStore collection and its indexes every time it starts (the first
start of a version that adds an index waits for it to be built on a collection that has documents) and applies the one schema
change MongoDB has had, the namespace field added in August 2023, unless
DB_MANUAL_MIGRATION is true. With manual migration, run the migration from a checkout
of the Ory Polis repository, which records what it applied in the same changelog collection the migrations in the repository
used before they moved into the driver package:
cd drivers/mongodb && pnpm install
DB_URL=mongodb://host:27017/polis pnpm run db:migration:run
Versions of the driver before the namespace_created and indexes_created indexes created namespace_1 and indexes_1 instead.
The driver no longer uses them and leaves them in place, because instances of an earlier version still read through them during a
rolling upgrade. Drop them once every instance is upgraded:
db.jacksonStore.dropIndex("namespace_1")
db.jacksonStore.dropIndex("indexes_1")
Redis
| Variable | Value |
|---|---|
DB_ENGINE | custom |
DB_DRIVER | @boxyhq/polis-driver-redis |
DB_URL | The connection string, such as redis://host:6379 |
The driver takes no DB_DRIVER_OPTIONS and needs no migrations. It needs Redis 6.2 or later, for the ZMSCORE and
ZRANGE ... BYSCORE commands its list reads use. It connects with RESP2, so it works with proxies that reject RESP3. Redis
Cluster is not supported: a record and its index keys are written in one script and must live in one keyspace.
Upgrading from a built-in driver
Earlier versions of Ory Polis ran these databases with DB_ENGINE=dynamodb, DB_ENGINE=mongo and DB_ENGINE=redis. Those values
are no longer accepted and Ory Polis refuses to start with them. The driver packages read and write the same tables, collections
and keys as the built-in drivers did, so no data migration is needed: build the image as described above, set DB_ENGINE=custom
and DB_DRIVER, and keep DB_URL.
For DynamoDB, DB_DYNAMODB_REGION, DB_DYNAMODB_RCUS and DB_DYNAMODB_WCUS are removed. Move them to DB_DRIVER_OPTIONS as
region, readCapacityUnits and writeCapacityUnits. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keep their meaning.
For MongoDB, the migrate-mongo migrations that were in the Ory Polis repository now live in the driver package, and the
DB_MANUAL_MIGRATION step for MongoDB is run from there, as described above. A deployment that ran the earlier migrations is up
to date.
With the npm library
The @boxyhq/saml-jackson library loads a driver the same way. Install the driver package next to the library and pass:
const jackson = await require("@boxyhq/saml-jackson").controllers({
db: {
engine: "custom",
driverModule: "@boxyhq/polis-driver-dynamodb",
url: "https://dynamodb.eu-west-1.amazonaws.com",
driverOptions: { region: "eu-west-1" },
},
// ...
})
db.driverModule takes a package name or a path, and db.driverOptions the driver's settings. The library also accepts a driver
object built by your own code as db.driver. Library users on DynamoDB move the removed db.dynamodb option to
db.driverOptions.
Other databases
Any database can be supported the same way. A driver module exports { apiVersion, new } and implements the DatabaseDriver
interface the @boxyhq/saml-jackson package exports; the contract is described under DB_DRIVER,
and the three packages in the drivers directory of the Ory Polis repository
are the reference implementations. A driver package imports only types from @boxyhq/saml-jackson: at runtime the library is
bundled into the Ory Polis image and cannot be resolved from /opt/polis-drivers.
Observability
Ory Polis instruments its built-in databases for OpenTelemetry, but not external drivers: it does not know which client library a
driver uses, and the instrumentation packages for every database a driver might use are not part of the image. A driver that wants
its queries in Polis traces instruments itself. Polis sets the global OpenTelemetry tracer provider at startup, before any driver
is loaded, so a driver can depend on @opentelemetry/api and the instrumentation package for its client library, and register the
instrumentation with registerInstrumentations from @opentelemetry/instrumentation before the client library is first loaded,
for example in a module the driver's entry point imports ahead of the client. Spans created through @opentelemetry/api reach the
exporter Polis configured; when Polis runs without OpenTelemetry they are discarded. The DynamoDB, MongoDB and Redis driver
packages do not emit spans at present.
Testing a driver
The Ory Polis repository has a database test suite that exercises everything Polis expects of a driver: records and their TTLs,
indexes, conditional writes, deletes, pagination by offset and by cursor, encryption, and concurrent use. It runs against the
built-in databases in CI, and against a driver of yours when POLIS_TEST_DRIVER names it. From a checkout of the repository, with
the database the driver connects to running:
pnpm install
cd npm
POLIS_NO_ANALYTICS=1 \
POLIS_TEST_DRIVER=/path/to/your/driver \
POLIS_TEST_DRIVER_URL=<connection URL> \
POLIS_TEST_DRIVER_OPTIONS='{"option":"value"}' \
JACKSON_DB_TEST_ENGINES=custom \
node --require ./test/support/register-ts.cjs --test --test-timeout=600000 test/db/db.test.ts
POLIS_TEST_DRIVER is what DB_DRIVER would be: a package name resolvable from the npm directory or from NODE_PATH, or an
absolute path to the package directory with its build output in place. POLIS_TEST_DRIVER_URL and POLIS_TEST_DRIVER_OPTIONS
become DB_URL and DB_DRIVER_OPTIONS. The driver is tested twice, with and without an encryption key, under the engine name
custom; JACKSON_DB_TEST_ENGINES=custom keeps the run to it, so none of the built-in databases need to be running. The file is
run directly because the package's test script runs every suite in the repository.
The suite expects the full contract, including skipping records by offset and honouring the sort order, so a driver with limits of
its own, as the DynamoDB driver has, fails the cases it cannot meet; those are the limits to document for its users. Unit tests of
the driver itself, against a fake client, belong in the driver package: the packages in drivers/ run theirs with node --test
and a fake of the database client, and are the pattern to follow.
