greatmemory's server mode is the same single binary (or a single ~158 MB container) with a few environment variables. This page takes you from the local-first defaults to a server other machines and agents can reach.
What changes in server mode
The defaults are local-first: loopback bind, SQLite, no auth. Three things change for a server:
GM_HOST=0.0.0.0 # bind beyond loopback (default 127.0.0.1)
GM_API_KEYS=gm_long_random_key # REQUIRED before exposing the port
GM_CORS_ORIGINS=https://app.example.com # replaces the permissive localhost default
API keys
By default there is no auth — fine on 127.0.0.1, not fine anywhere else. Before binding 0.0.0.0 or putting greatmemory behind a proxy, set GM_API_KEYS to one or more long random values:
GM_API_KEYS="$(openssl rand -hex 32)" GM_HOST=0.0.0.0 gmem serve
Every /v1 route except /v1/healthz (left open for load-balancer probes) — and the /mcp endpoint — then requires Authorization: Bearer <key>. Keys are compared in constant time. In v0.1 they are held in server memory in plaintext; treat them like any other secret in your env or secret manager.
Clients pass the key via GM_API_KEY:
GM_URL=https://memory.example.com GM_API_KEY=gm_... gmem status
CORS
With GM_CORS_ORIGINS unset, the server allows any localhost origin — which makes local web-app development work out of the box. Setting it to an explicit comma-separated list replaces that policy entirely: only the listed origins are allowed, and the permissive localhost behavior is disabled. That is how you "turn off" the development default in production. An origin that fails to parse is a startup error, not a silent drop.
SQLite or Postgres?
| SQLite (default) | Postgres + pgvector | |
|---|---|---|
| Setup | nothing — the whole store is one file in GM_DATA_DIR | set GM_DB=postgres://... (pgvector extension required) |
| Best for | single instance, simple ops, easy backups | managed databases, ephemeral containers, multi-instance |
| Backup | copy one file (or SQLite online backup) | pg_dump and your existing tooling |
| Persistence | needs a durable /data volume | the database is the durable state |
Either way, GM_DATA_DIR still holds the embedding model cache — it re-downloads on demand, so it never needs backing up.
Docker Compose
SQLite variant
The docker-compose.yml in the source tree builds the image and persists data (SQLite database + model cache) in a named volume:
services:
greatmemory:
build: .
ports:
- "7437:7437"
volumes:
- gm-data:/data
environment:
GM_API_KEYS: gm_change_me
volumes:
gm-data:
docker compose up -d
curl -s http://127.0.0.1:7437/v1/healthz
The image binds 0.0.0.0:7437 inside the container (GM_HOST=0.0.0.0, GM_DATA_DIR=/data) and runs as a non-root user.
Postgres variant
For multi-instance servers or managed databases, use docker-compose.postgres.yml, which starts a pgvector/pgvector:pg17 database and points greatmemory at it via GM_DB:
services:
greatmemory:
build: .
ports:
- "7437:7437"
volumes:
- gm-data:/data # still mounted: holds the embedding model cache
environment:
GM_DB: postgres://greatmemory:change_me@db:5432/greatmemory
GM_API_KEYS: gm_change_me
depends_on:
- db
db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: greatmemory
POSTGRES_PASSWORD: change_me
POSTGRES_DB: greatmemory
volumes:
- pg-data:/var/lib/postgresql/data
volumes:
gm-data:
pg-data:
docker compose -f docker-compose.postgres.yml up -d
Change the default credentials before exposing anything.
TLS
gmem serves plain HTTP; terminate TLS in front of it — a cloud load balancer, Caddy, or nginx. Keep GM_HOST=127.0.0.1 (or a private network) and let the proxy be the only public listener:
server {
listen 443 ssl;
server_name memory.example.com;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://127.0.0.1:7437;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Note: the streamable-HTTP MCP endpoint (
/mcp) validates a loopbackHostin v0.1, so it won't work through a public hostname — proxy only the/v1API and use stdio MCP locally.
Production checklist
GM_API_KEYSset to long random valuesGM_CORS_ORIGINSset to your real origins (disables the localhost-any default)- TLS terminated by a proxy or load balancer; greatmemory bound to loopback or a private network
- Data dir (or Postgres) on persistent storage with backups scheduled
/v1/healthzwired into health checks;/v1/statsinto monitoring —rss_bytesis the number to watch, and it should stay flat
Next steps
Cloud-specific walkthroughs: AWS · Azure · Google Cloud