| title | Deployment |
|---|---|
| description | Build and run the membraned daemon, configure Postgres, and deploy with Docker Compose. |
Membrane ships a standalone daemon binary (membraned) that exposes the full gRPC API. This guide covers building the binary, command-line flags, Docker Compose, and deployment tier selection.
git clone https://github.com/BennettSchwartz/membrane.git
cd membrane
make buildThe binary is written to ./bin/membraned. Verify the build:
./bin/membraned --versionStart the bundled development database.
```bash
export MEMBRANE_POSTGRES_PASSWORD="$(openssl rand -hex 24)"
export MEMBRANE_POSTGRES_DSN="postgres://membrane:${MEMBRANE_POSTGRES_PASSWORD}@127.0.0.1:5432/membrane_test?sslmode=disable"
docker compose up -d
```
```bash
./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN"
```
```bash
./bin/membraned --config /etc/membrane/config.yaml
```
```bash
./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN" --addr 127.0.0.1:8080
```
| Flag | Type | Default | Description |
|---|---|---|---|
--config |
string |
"" |
Path to YAML config file |
--postgres-dsn |
string |
"" |
PostgreSQL DSN; required unless postgres_dsn or MEMBRANE_POSTGRES_DSN is set |
--addr |
string |
"127.0.0.1:9090" |
gRPC listen address (overrides config) |
--version |
bool |
false |
Print version and exit |
The repository includes a docker-compose.yml for running the Postgres + pgvector store:
services:
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: membrane_test
POSTGRES_USER: membrane
POSTGRES_PASSWORD: ${MEMBRANE_POSTGRES_PASSWORD:?set MEMBRANE_POSTGRES_PASSWORD to a generated development secret}
ports:
- "127.0.0.1:5432:5432"Start the database:
export MEMBRANE_POSTGRES_PASSWORD="$(openssl rand -hex 24)"
export MEMBRANE_POSTGRES_DSN="postgres://membrane:${MEMBRANE_POSTGRES_PASSWORD}@127.0.0.1:5432/membrane_test?sslmode=disable"
docker compose up -dThen start the daemon pointing at it:
./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN"The bundled service is development-only. Its host port is loopback-bound, and
Compose refuses to start until MEMBRANE_POSTGRES_PASSWORD is supplied.
Membrane requires PostgreSQL 14+ with the pgvector extension. The pgvector/pgvector:pg16 Docker image includes it pre-installed.
If you are using an existing Postgres instance, install the extension:
CREATE EXTENSION IF NOT EXISTS vector;Membrane runs Postgres schema migrations automatically on startup.
Embedding vectors are stored in a fixed-dimension pgvector column. The configured embedding_dimensions value must match the embedding model, and Membrane rejects empty, non-finite, all-zero, or wrong-dimension vectors before they reach Postgres. Dimension changes require a database migration because pgvector columns are typed by size, but same-dimension embedding_model changes are accepted: Membrane updates embedding metadata, filters lookup and search to the configured model, and lets startup backfill refresh records that only have stale vectors. Ordinary list queries use a stable salience, creation-time, and record-ID order. Vector searches use cosine distance and a stable record_id tie-breaker so equal-distance results stay deterministic.
Use your database provider's volume encryption, backups, and access controls for at-rest protection.
| Variable | Purpose |
|---|---|
MEMBRANE_POSTGRES_DSN |
PostgreSQL DSN |
MEMBRANE_EMBEDDING_ENDPOINT |
Embedding endpoint when embedding_endpoint is unset |
MEMBRANE_EMBEDDING_MODEL |
Embedding model when embedding_model is unset |
MEMBRANE_EMBEDDING_DIMENSIONS |
Embedding dimensions when the config value is still the default |
MEMBRANE_EMBEDDING_API_KEY |
API key for the embedding endpoint |
MEMBRANE_LLM_ENDPOINT |
Semantic extraction endpoint when llm_endpoint is unset |
MEMBRANE_LLM_MODEL |
Semantic extraction model when llm_model is unset |
MEMBRANE_LLM_API_KEY |
API key for the semantic extraction LLM endpoint |
MEMBRANE_INGEST_LLM_ENDPOINT |
Ingest interpretation endpoint when ingest_llm_endpoint is unset |
MEMBRANE_INGEST_LLM_MODEL |
Ingest interpretation model when ingest_llm_model is unset |
MEMBRANE_INGEST_LLM_API_KEY |
API key for the ingest-side interpretation endpoint |
MEMBRANE_API_KEY |
Bearer token for gRPC client authentication |
MEMBRANE_READ_MAX_SENSITIVITY |
Read sensitivity ceiling when read_max_sensitivity is empty |
MEMBRANE_READ_SCOPES |
Comma-separated gRPC read scopes; overrides the configured list when non-empty |
MEMBRANE_WRITE_MAX_SENSITIVITY |
Write sensitivity ceiling when write_max_sensitivity is empty |
MEMBRANE_WRITE_SCOPES |
Comma-separated gRPC write scopes; overrides the configured list when non-empty |
Environment variables are read at startup for the DSN, optional endpoint/model pairs, default embedding dimensions, secrets, and gRPC policy. Config file values take precedence over endpoint/model and sensitivity environment variables unless the matching field is empty; MEMBRANE_EMBEDDING_DIMENSIONS overrides only the default dimensions value; API-key environment variables are read only when the matching config field is empty. The comma-separated read/write scope variables replace the configured lists when non-empty. Endpoint and model values must still be configured as complete pairs, and API-key variables do not enable an embedding or LLM subsystem by themselves.
Create a config file and pass it with --config:
postgres_dsn: "" # set MEMBRANE_POSTGRES_DSN in the environment
listen_addr: "127.0.0.1:9090"
decay_interval: "1h"
consolidation_interval: "6h"
default_sensitivity: "low"
read_max_sensitivity: "low"
read_scopes: ["default"]
write_max_sensitivity: "low"
write_scopes: ["default"]
selection_confidence_threshold: 0.7
graph_default_root_limit: 10
graph_default_node_limit: 25
graph_default_edge_limit: 100
graph_default_max_hops: 1
rate_limit_per_second: 100
# Embedding-backed retrieval
embedding_endpoint: "https://api.openai.com/v1/embeddings"
embedding_model: "text-embedding-3-small"
embedding_dimensions: 1536
# LLM-backed semantic extraction
llm_endpoint: "https://api.openai.com/v1/chat/completions"
llm_model: "gpt-5-mini"
# Optional ingest-side interpretation
ingest_llm_enabled: true
ingest_llm_endpoint: "https://api.openai.com/v1/chat/completions"
ingest_llm_model: "gpt-5-mini"
# TLS
tls_cert_file: "/etc/membrane/tls.crt"
tls_key_file: "/etc/membrane/tls.key"The default loopback listener is appropriate for a local client or a colocated reverse proxy. Binding to a non-loopback address requires api_key; when API-key authentication is enabled on a non-loopback listener, configure the TLS files as well. allow_insecure_credentials: true is an explicit development-only escape hatch for trusted private networks.
./bin/membraned --config /etc/membrane/config.yamlChoose a tier based on your concurrency and feature requirements:
| Tier | Store | Embedding | LLM | When to use |
|---|---|---|---|---|
| 1 | Postgres + pgvector | — | — | Typed storage, graph edges, trust gating, decay, and revisions |
| 2 | Postgres + pgvector | Yes | — | Hybrid vector+salience ranking for all record types |
| 3 | Postgres + pgvector | Yes | Yes | Full system: background LLM extraction turns episodic traces into semantic facts |
The daemon handles SIGINT and SIGTERM. On receipt, it:
- Cancels the background context for decay, consolidation, and startup embedding backfill.
- Drains in-flight gRPC requests.
- Waits for Membrane background jobs to finish.
- Closes the database.
Startup embedding backfill keeps processing after individual record failures, then logs an aggregate error with the number of failed records. Pair this with embedding_coverage from GetMetrics to alert when pgvector coverage remains incomplete after startup.
This ordering prevents handlers from hitting a closed database. Avoid sending SIGKILL during an active consolidation run.