New Ory Agent Security is now live! Claim your complimentary test drive. Get Started!

Skip to main content

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.

DatabaseDriver 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.

note

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​

VariableValue
DB_ENGINEcustom
DB_DRIVER@boxyhq/polis-driver-dynamodb
DB_URLThe DynamoDB endpoint, such as https://dynamodb.eu-west-1.amazonaws.com
DB_DRIVER_OPTIONSOptional 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​

VariableValue
DB_ENGINEcustom
DB_DRIVER@boxyhq/polis-driver-mongodb
DB_URLThe 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​

VariableValue
DB_ENGINEcustom
DB_DRIVER@boxyhq/polis-driver-redis
DB_URLThe 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.