Requirements
A self-hosted RememberStack is a set of containers from one Compose file: PostgreSQL, SeaweedFS object storage, an API process and twelve pipeline workers. The Compose file builds or pulls everything it needs. This page lists what has to be true of the machine and the accounts around it.
Docker and Compose
You need Docker Engine with the Compose v2 plugin: the docker compose
command, not the older docker-compose script. compose.yaml uses a
top-level name, depends_on conditions such as
service_completed_successfully, profiles, and an optional env_file
(required: false), which needs Compose 2.24.0 or later.
Run the tokenless quickstart only on Docker Engine 28.0.0 or later
(docker version --format '{{.Server.Version}}'). Compose publishes the
API on 127.0.0.1 only, and before Engine 28.0.0 a port published on
127.0.0.1 can still be reached by other hosts on the same local network.
On an older engine, turn on API authentication before the first start:
set REMEMBERSTACK_SELFHOST_API_BEARER_TOKEN and
REMEMBERSTACK_SELFHOST_REQUIRE_API_AUTH=true in .env (see
A shared secret).
The project states no other minimum Docker or Compose version. Its own
continuous-integration check runs docker compose up --build --detach --wait
on a current GitHub Actions Ubuntu runner.
Image architectures
Two images from this project make up the stack. Both are published for
linux/amd64 and linux/arm64, so an arm64 machine (Apple Silicon,
Graviton, Ampere) runs them natively, without emulation:
| Image | Used by |
|---|---|
ghcr.io/writeitai/remember-stack:0.17.2 | The API, the workers and setup |
ghcr.io/writeitai/remember-stack-postgres:19beta3-0.17.2 | PostgreSQL |
Compose pulls both from GitHub Container Registry. It builds an image from
source (Dockerfile or Dockerfile.postgres) only when the pull fails, or
when you pass --build. Building PostgreSQL compiles one extension and
takes a few minutes. The third image, SeaweedFS, is also published for both
architectures.
PostgreSQL 19 (beta)
RememberStack requires PostgreSQL 19, which is a beta release. The
Compose file pins postgres:19beta3.
It needs 19 because the memory's graph is a SQL/PGQ property graph
(CREATE PROPERTY GRAPH, queried with GRAPH_TABLE). PostgreSQL 19 is the
first release with SQL/PGQ, so entity neighbourhoods and paths run live
inside the same database that holds the facts, with no separate graph store
to build or keep in sync.
Running a beta has a consequence you should plan for: there is no promise that a data directory created by one PostgreSQL 19 prerelease opens under a later one. See Upgrades and migrations.
Extensions
The migrations create these extensions:
| Extension | Version | Used for |
|---|---|---|
vector (pgvector) | 0.8.6 | Semantic search vectors and HNSW indexes |
pg_textsearch | 1.3.1 | BM25 keyword search |
pg_partman | 5.5.0 | Monthly partitioning of large tables |
pgcrypto | contrib | UUID generation and digests |
pg_trgm | contrib | Fuzzy name matching during entity resolution |
fuzzystrmatch | contrib | Phonetic name matching (daitch_mokotoff) |
unaccent | contrib | Accent folding of names |
btree_gist | contrib | The time-range exclusion constraint on relations |
pg_textsearch has no PostgreSQL 19 package yet. Dockerfile.postgres
downloads a pinned source revision from timescale/pg_textsearch, applies
a small compatibility patch (docker/pg_textsearch-pg19.patch) and
compiles it. pgvector and pg_partman come from the PostgreSQL apt
archive at the exact versions above. remember ops graph-catalog ensure
checks these three versions (see Operating the pipeline).
Server settings
Compose starts PostgreSQL with:
shared_preload_libraries=pg_textsearch,pg_partman_bgw
pg_partman_bgw.dbname=<REMEMBERSTACK_POSTGRES_DB>
pg_partman_bgw.role=<REMEMBERSTACK_POSTGRES_USER>
max_connections=<REMEMBERSTACK_POSTGRES_MAX_CONNECTIONS, default 300>Everything else stays at the image defaults.
Running the engine against a PostgreSQL server you manage yourself is not a documented path. If you try it, the server must match everything on this page: version 19, the extensions at those versions, and the preload settings.
S3-compatible object storage
Original files, converted Markdown and other artifacts, and filesystem
snapshots live in object storage, in three buckets (remember-raw,
remember-artifacts, remember-corpusfs). The setup step creates them.
Compose runs SeaweedFS for this
(image chrislusf/seaweedfs, pinned to 4.44 by digest). The engine talks
to it through the S3 API with path-style addressing and SigV4 signing. It
writes every object with a conditional create (If-None-Match: *), so an
object is never silently replaced. Only SeaweedFS is exercised by the
project's checks. Another S3-compatible store has to support path-style
requests, conditional writes and user metadata on objects.
SeaweedFS stores objects in volumes of at most 1 GiB. Compose allows 100
of them, so the bundled store holds up to about 100 GiB before writes fail
(each bucket also reserves 7 volumes on its first write). Volumes are not
preallocated, so unused capacity takes no disk. To store more, raise
-volume.max on the object-store service in compose.yaml. The engine
sets no S3 lifecycle (expiry) rules and does not need them: it deletes
objects itself, during a hard forget.
Model provider keys
| Key | Required | Used for |
|---|---|---|
OpenRouter API key (REMEMBERSTACK_OPENROUTER_API_KEY) | Yes | Every model call: structure, claim extraction, entity resolution, fact adjudication, and all embeddings, including query embeddings for search |
Mistral API key (REMEMBERSTACK_MISTRAL_OCR_API_KEY) | Only if you route PDFs or images to OCR | The mistral_ocr and image_ocr_description converters |
A second OpenRouter key (REMEMBERSTACK_IMAGE_DESCRIPTION_API_KEY) | Only if you route images to image_ocr_description | The vision-model description of an image |
TypeSafe AI key (REMEMBERSTACK_TYPESAFE_API_KEY) | Only with REMEMBERSTACK_FACT_ADJUDICATION_ENGINE=jev | The alternative fact adjudicator |
The engine starts with the placeholder OpenRouter key from .env.example,
but the first document or search fails at the first model call. The model
calls cost money on your OpenRouter account; see
Models and providers.
Sizing
No hardware minimum has been measured, and the project publishes no throughput figures. What follows is what the configuration and schema fix, so you can size a machine from your own corpus.
Where it has run. Continuous integration starts the full stack on a standard GitHub-hosted Ubuntu runner, which for a public repository has 4 CPUs and 16 GB of memory. It ingests a document and waits for every stage there; it does not measure speed.
What uses CPU and memory. Most pipeline work is waiting on model calls to OpenRouter, so the engine's own processes are light. PostgreSQL does the local heavy lifting: search, graph queries and SQL queries all run in it. The query-side memory each of those may take is bounded (see the table below and Scaling).
What uses disk. Two volumes grow with the corpus:
postgres-dataholds every chunk, claim, fact, observation and entity. Each of these also carries a 1,536-dimension embedding, about 6 KB, plus its share of the vector index.object-store-dataholds each uploaded file as sent, its converted Markdown and other artifacts, and filesystem snapshots. The bundled store is capped at about 100 GiB (see S3-compatible object storage).
Nothing is deleted automatically; a hard forget removes a document's data.
Database connections. Compose starts PostgreSQL with
max_connections from REMEMBERSTACK_POSTGRES_MAX_CONNECTIONS, 300 by
default (the image default is 100). Each engine process opens
connections as it needs them, up to these ceilings:
| Process | General pool | Other | Ceiling |
|---|---|---|---|
api | 15 (5 kept open, 10 more under load) | retrieval pool 4, graph pool 4 | 23 |
Each worker-… | 15 (5 kept open, 10 more under load) | 1 held open to listen for new work | 16 |
The ceilings of the default stack add up to 23 + 12 × 16 = 215, which fits
within 300 with room to spare. They are ceilings, not what a stack holds:
pools open connections only on demand. Nothing caps the total, though, so
budget against the ceilings when you add replicas or API processes: each
worker replica can add 16 connections and each API process 23, plus any
increase in the retrieval and graph pool sizes. When the sum passes the limit,
raise it by setting REMEMBERSTACK_POSTGRES_MAX_CONNECTIONS in .env; see
Scaling.
Other configured bounds:
| Bound | Default |
|---|---|
Containers started by docker compose up | 15: postgres, object-store, setup (exits), api, 12 workers |
| Items one worker process runs at once | 1 |
| Claims per second per worker process | 20, burst 20 |
| Graph connection pool in the API | 4 connections, at most 2 graph queries at once |
Graph query work_mem | 16,384 KiB |
| Retrieval connection pool in the API | 4 connections, at most 4 retrievals at once |
| SQL query sandbox | 5 s default and 15 s maximum statement time, 16 MiB work_mem, 8 statements at once per deployment |
PostgreSQL max_connections | 300 (REMEMBERSTACK_POSTGRES_MAX_CONNECTIONS) |
Scaling explains each bound and how to change it.