# How to Set Up PostgreSQL Persistence for OpenMAIC: A Complete Docker Guide

> Set up PostgreSQL persistence for OpenMAIC with this Docker guide. Configure your DATABASE_URL, build flags, and launch the server-persistence profile for reliable data storage.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: how-to-guide
- Published: 2026-09-03

---

**To set up PostgreSQL persistence for OpenMAIC, copy `.env.example` to `.env.local`, configure the `DATABASE_URL` connection string pointing to the Docker PostgreSQL service, set the required build-time environment flags, and launch the `server-persistence` Docker Compose profile.**

By default, OpenMAIC stores classroom state in the browser using IndexedDB. For production stability and multi-user support, you need to enable server-side persistence using PostgreSQL. This guide explains how to configure the **`server-persistence`** profile in the THU-MAIC/OpenMAIC repository to persist data across container restarts using PostgreSQL 16.

## Prerequisites and Environment Configuration

OpenMAIC requires specific environment variables to connect the Next.js application to the PostgreSQL backend. These settings must be configured before building the Docker images.

### Copy the Environment Template

Start by creating your local environment file from the provided template:

```bash
cp .env.example .env.local

```

The `.env.example` file contains placeholders for all required variables, including database connection details and authentication tokens.

### Configure the Database Connection String

Edit `.env.local` and set `DATABASE_URL` to point at the PostgreSQL container that Docker Compose will start:

```text
DATABASE_URL=postgres://openmaic:openmaic-dev@postgres:5432/openmaic

```

This connection string uses the following components:
- **`openmaic`** – the database user created automatically on first container start
- **`openmaic-dev`** – the default password (controlled by `PERSISTENCE_POSTGRES_PASSWORD`)
- **`postgres`** – the Docker service name resolving to the database container
- **`5432`** – the default PostgreSQL port
- **`openmaic`** – the target database name

### Set Persistence Tokens

Add a development token to `.env.local` for local authentication between the client and server:

```text
PERSISTENCE_DEV_TOKEN=openmaic-local-dev

```

**Important:** The value must match the `NEXT_PUBLIC_PERSISTENCE_TOKEN` you set during the Docker build step.

## Launching the Server Persistence Profile

Unlike runtime variables, **`NEXT_PUBLIC_PERSISTENCE` and `NEXT_PUBLIC_PERSISTENCE_TOKEN` are build-time flags**. You must export them before Docker builds the image to ensure the client bundle includes the correct API endpoints.

Run the following command to build and launch the stack:

```bash
NEXT_PUBLIC_PERSISTENCE=1 \
NEXT_PUBLIC_PERSISTENCE_TOKEN=openmaic-local-dev \
docker compose --profile server-persistence up --build

```

This command starts three key components defined in [`docker-compose.yml`](https://github.com/THU-MAIC/OpenMAIC/blob/main/docker-compose.yml):

- **`openmaic`** – The Next.js application running on Node 22
- **`postgres`** – PostgreSQL 16 with the node-postgres driver
- **`openmaic-postgres`** – A named Docker volume that persists database files across container restarts

The `openmaic-postgres` volume retains all data. Note that `PERSISTENCE_POSTGRES_PASSWORD` only initializes the superuser password on the **first** volume creation; changing this variable later will not update existing credentials.

## Verifying Database Persistence

Once the containers are running, confirm that data persists outside the browser.

### Test Via the Application

1. Navigate to `http://localhost:3000` (default port).
2. Create a classroom or upload a course document.
3. Refresh the browser page or run `docker compose restart`.
4. Verify the data remains loaded, confirming it is stored in PostgreSQL rather than IndexedDB.

### Inspect Data Directly

Connect to the running PostgreSQL container using the `psql` client to inspect tables directly:

```bash
docker exec -it $(docker ps -q -f name=postgres) psql -U openmaic -d openmaic

```

You can query the runtime state tables to verify that classroom data is committed to disk.

## Key Implementation Files

Understanding the source files helps debug connection issues or extend persistence functionality.

### `.env.example`

This template in the repository root defines all required environment variables, including `DATABASE_URL` and `PERSISTENCE_DEV_TOKEN`, ensuring consistent configuration across development environments.

### [`docker-compose.yml`](https://github.com/THU-MAIC/OpenMAIC/blob/main/docker-compose.yml)

The Compose configuration defines the `server-persistence` profile, service dependencies between the OpenMAIC app and PostgreSQL, and the named volume `openmaic-postgres` for data durability.

### `packages/@openmaic/storage/runtime-store.pg.ts`

This TypeScript file implements the PostgreSQL-backed persistence layer used by the server-side API. It handles connection pooling, query execution, and serialization of classroom state to relational tables.

### [`app/workspace/page.tsx`](https://github.com/THU-MAIC/OpenMAIC/blob/main/app/workspace/page.tsx)

The frontend entry point checks `NEXT_PUBLIC_PERSISTENCE` at build time to determine whether to instantiate the IndexedDB client-side store or proxy requests through the server persistence API.

### `packages/docs/content/docs/deployment.mdx`

The official documentation file contains human-readable deployment instructions and advanced configuration options for production PostgreSQL setups.

## Summary

- **Copy** `.env.example` to `.env.local` and set `DATABASE_URL` to `postgres://openmaic:openmaic-dev@postgres:5432/openmaic`.
- **Export** `NEXT_PUBLIC_PERSISTENCE=1` and matching `NEXT_PUBLIC_PERSISTENCE_TOKEN` as build-time flags before running Docker commands.
- **Launch** the stack using `docker compose --profile server-persistence up --build` to start both the Node.js application and PostgreSQL 16.
- **Persist** all classroom state and course data in the named Docker volume `openmaic-postgres`, ensuring survival across container restarts.

## Frequently Asked Questions

### Why does changing the PostgreSQL password environment variable not affect my existing data?

The `PERSISTENCE_POSTGRES_PASSWORD` variable only initializes the database superuser password when the `openmaic-postgres` volume is created for the first time. Because the volume persists across container restarts, subsequent changes to this environment variable do not alter the existing PostgreSQL user credentials or database files.

### Can I connect OpenMAIC to an external PostgreSQL database instead of the Docker container?

Yes. Update the `DATABASE_URL` in `.env.local` to point to your external instance (e.g., `postgres://user:pass@db.example.com:5432/openmaic`). Ensure the database is network-accessible from the OpenMAIC container and that the schema is compatible with the queries implemented in `packages/@openmaic/storage/runtime-store.pg.ts`.

### What is the difference between client-side and server-side persistence in OpenMAIC?

Client-side persistence stores classroom state in the browser's IndexedDB, as implemented in [`app/workspace/page.tsx`](https://github.com/THU-MAIC/OpenMAIC/blob/main/app/workspace/page.tsx). Server-side persistence activates when `NEXT_PUBLIC_PERSISTENCE=1` is set at build time, causing the application to route all storage operations through HTTP API calls to the PostgreSQL backend defined in the `DATABASE_URL`.

### Is it safe to use the development token in production environments?

No. The `PERSISTENCE_DEV_TOKEN` and `NEXT_PUBLIC_PERSISTENCE_TOKEN` are intended strictly for local development. Never use these default values in public deployments. For production, implement a proper authentication layer and avoid exposing `NEXT_PUBLIC_PERSISTENCE` unless your backend API includes robust authorization checks.