Using Redwoodjs with testcontainers


I hacked RedwoodJS API tests to work with PostgreSQL in a disposable container via testcontainers. I just got this working, so there are likely error conditions I don’t handle, and the code could be neater. For instance, my approach to the database URL environment variables could be made more precise.

However, since I wasn’t able to find any existing docs on how to do this, I figured what I have now is a step forward and worth sharing.


I wanted to make sure I always had a clean database when running tests. RedwoodJS tries to clean up after itself, but sometimes that doesn’t work, and when you’re doing more in your database than just Prisma models, you have to figure out your own cleanup strategy. That can be difficult to get right, so I figured I’d just throw away the database entirely by using testcontainers. Testcontainers launches a dependency in a Docker container.


api/$ yarn add  testcontainers @testcontainers/postgresql


import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';

let postgresContainer: StartedPostgreSqlContainer | undefined = undefined

export const initializeContainer = async () => {
  postgresContainer = await new PostgreSqlContainer().start();
  return postgresContainer

export const stopContainer = async () => {
  if (postgresContainer) {
    await postgresContainer.stop();

process.on('exit', stopContainer)

In api/jest.config.js add:

  globalSetup: '<rootDir>/api/src/lib/test/setup/jestGlobalSetup.ts',
  globalTeardown: '<rootDir>/api/src/lib/test/setup/jestGlobalTeardown.ts',

This was a little hairy because it messed up the Redwoodjs presets. I addressed that in the below:


import { initializeContainer } from "../testcontainers";

const rwJestPreset = require('@redwoodjs/testing/config/jest/api/jest-preset')

module.exports = async () => {

  const postgresContainer = await initializeContainer()

  const dbEnvVars = [

  for (const envVar of dbEnvVars) {
    process.env[envVar] = postgresContainer.getConnectionUri()

  if (rwJestPreset.globalSetup) {
    try {
      const presetGlobalSetup = require(rwJestPreset.globalSetup)
      return presetGlobalSetup()
    } catch (e) {
      //  do nothing?

Then api/src/lib/test/setup/jestGlobalTeardown.ts

import { stopContainer } from "../testcontainers";

const rwJestPreset = require('@redwoodjs/testing/config/jest/api/jest-preset')

module.exports = async () => {
  if (rwJestPreset.globalTeardown) {
    try {
      const presetGlobalTeardown = require(rwJestPreset.globalTeardown)
      return presetGlobalTeardown()
    } catch (e) {
      //  do nothing?
  await stopContainer()

I hope this is useful for someone. Perhaps RedwoodJS can one day support testcontainers out of the box.


Thanks for the writeup @ketan!

We’re not doing test containers, but I haven’t had any problems with our setup, might be, that we just don’t do enough with the database.

We use gitlab and gitlab ci has the concept of services, which we’re using:

  - yarn config set enableGlobalCache false
  # abort with an error code if the lock file got edited
  - yarn install --immutable
    name: test
  interruptible: true

  extends: .before_script_template
  stage: test
  - postgres:16
    POSTGRES_DB: projectname_test
    POSTGRES_USER: projectname_user
  - yarn rw test api --watch=false --collect-coverage --ci --reporters=default --reporters=jest-junit --showSeed
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
      - junit.xml