Skip to content

Docker & Local Dev

AutoMem provides two local development paths: Docker Compose (recommended) for a complete self-contained stack, and bare Python for lightweight API-only development against external database instances.


FeatureDocker ComposeBare Metal
External AccessPorts published on all host interfaces by default (see below)app.py listens on all interfaces
Data PersistenceNamed volumes (manual backup)External DB dependent
Use CaseFull-stack developmentAPI development only
Services Includedapp.py, FalkorDB, Qdrantapp.py only

Prerequisites for bare Python:

RequirementVersionPurpose
Python3.10+Runtime for app.py and background workers
GitAny recentRepository cloning

Docker Compose provides a complete, isolated stack with FalkorDB, Qdrant, and the Flask API running in containers with persistent volumes.

graph TB
    subgraph host["Host Machine"]
        Make["make dev<br/>docker compose up"]

        subgraph docker["Docker Network<br/>automem_default"]
            API["flask-api<br/>app.py<br/>localhost:8001"]
            Falkor["falkordb<br/>localhost:6379"]
            QdrantLocal["qdrant<br/>localhost:6333"]
        end

        subgraph volumes["Docker Volumes"]
            FalkorVol["falkordb_data<br/>/data"]
            QdrantVol["qdrant_data<br/>/qdrant/storage"]
        end
    end

    subgraph tools["Development Tools"]
        Curl["curl<br/>localhost:8001"]
        Python["Python Scripts<br/>scripts/"]
        Tests["pytest<br/>tests/"]
    end

    Make --> API
    Make --> Falkor
    Make --> QdrantLocal

    API --> Falkor
    API --> QdrantLocal

    Falkor --> FalkorVol
    QdrantLocal --> QdrantVol

    Curl --> API
    Python --> API
    Tests --> API
  • Docker 20.10+
  • Docker Compose 2.0+
  • Git

Clone the server repository and open its directory.

Terminal window
git clone https://github.com/verygoodplugins/automem.git
cd automem

Set the provider in the repository’s .env before starting the stack. For on-server embeddings, use:

EMBEDDING_PROVIDER=local
VECTOR_SIZE=1024

This selects FastEmbed with BGE Large for a new 1024-dimension collection. For Ollama or a hosted embedding API, use the corresponding settings in Embedding Generation.

Compose publishes the API and database ports on all host interfaces by default. On a shared network, restrict port exposure and set your own API and admin tokens before startup.

Method 1 — Makefile (recommended):

Terminal window
make dev

This runs docker compose up --build and rebuilds the Flask API image if Dockerfile or requirements.txt have changed.

Method 2 — Direct Docker Compose:

Terminal window
docker compose up --build

Confirm the API is reachable with curl http://localhost:8001/health, then connect your agent to the existing endpoint. When prompted, enter the API URL and the value of the server’s AUTOMEM_API_TOKEN.

ServiceImage/BuildPortsPurposeHealth Check
flask-apiBuilt from Dockerfile8001AutoMem Flask API with background workersNone (depends on FalkorDB health)
falkordbfalkordb/falkordb:latest6379 (Redis), 3000 (UI)Graph database (canonical memory storage)Password-aware redis-cli ping every 10s
qdrantqdrant/qdrant:v1.11.36333 (REST), 6334 (gRPC)Vector search database (optional)None (service_started)

Default service URLs:

  • API: http://localhost:8001
  • FalkorDB: localhost:6379 (Redis protocol)
  • FalkorDB UI (built-in, official): http://localhost:3000 — use this for local graph inspection
  • Qdrant: http://localhost:6333

Local FalkorDB UI vs /viewer. The FalkorDB browser at http://localhost:3000 is the official local graph-inspection UI shipped inside the falkordb container. The /viewer path on the AutoMem API is the production entrypoint — it redirects to the standalone automem-graph-viewer app when GRAPH_VIEWER_URL is set, and does not serve a local UI. If you run that standalone viewer locally alongside the Docker stack, note that it may also default to http://localhost:3000, so you will need to change one of the ports (for example, set PORT for the viewer) to avoid a collision.

Docker Compose defines named volumes for persistent data and a bind mount for the source tree:

VolumeContainer PathPurposePersistence Level
falkordb_data/dataRDB snapshots + AOF (append-only file)High (every 60s or 1 key change)
qdrant_data/qdrant/storageVector collections + write-ahead logHigh (write-ahead log)
fastembed_models/root/.config/automem/modelsDownloaded ONNX embedding modelsMedium (cache, re-downloadable)
. (bind mount)/appSource code (see note below — editing files does not auto-reload the running server)N/A (host filesystem)
./backups/falkordb/backupsManual RDB exportsN/A (host filesystem)
./backups/qdrant/backupsManual snapshot exportsN/A (host filesystem)

FalkorDB persistence settings (REDIS_ARGS in docker-compose.yml):

  • --save 60 1 — Create RDB snapshot every 60 seconds if at least 1 key changed
  • --appendonly yes — Enable AOF for durability
  • --appendfsync everysec — Sync AOF to disk every second

The persistence directory itself is set separately via FALKORDB_DATA_PATH=/data (not via a --dir flag in REDIS_ARGS): FalkorDB’s container entrypoint always appends --dir $FALKORDB_DATA_PATH after REDIS_ARGS, so any --dir value inside REDIS_ARGS would be silently overridden.

Required variables for the Docker Compose stack:

VariableDocker Compose DefaultPurposeNotes
PORT8001Flask API portMust match container port mapping
AUTOMEM_API_TOKEN${AUTOMEM_API_TOKEN:-test-token}Client authenticationSet via shell or .env
ADMIN_API_TOKEN${ADMIN_API_TOKEN:-test-admin-token}Admin endpoint authenticationSet via shell or .env
FALKORDB_HOSTfalkordbFalkorDB service nameDocker internal DNS resolution
FALKORDB_PORT6379FalkorDB portStandard Redis port

Optional variables with defaults:

VariableDocker Compose DefaultPurposeNotes
FLASK_ENVdevelopmentFlask environment modeSet for convention, but not read by app.py — see hot-reload note below
FLASK_DEBUG"1"Flask debug flagSet for convention, but not read by app.py — see hot-reload note below
FALKORDB_PASSWORD${FALKORDB_PASSWORD:-}FalkorDB authenticationEmpty by default (no auth)
QDRANT_URLhttp://qdrant:6333Qdrant endpointDocker internal URL
QDRANT_API_KEY${QDRANT_API_KEY:-}Qdrant authenticationNot required for local Qdrant
OPENAI_API_KEY${OPENAI_API_KEY:-}OpenAI API accessOptional. auto tries it after Voyage; without it, auto tries configured Ollama, then FastEmbed, then placeholder embeddings
VOYAGE_API_KEY${VOYAGE_API_KEY:-}Voyage AI API accessUsed when EMBEDDING_PROVIDER=voyage or auto-selected
EMBEDDING_PROVIDER${EMBEDDING_PROVIDER:-auto}Provider selectionauto|voyage|openai|ollama|local|placeholder
VECTOR_SIZE${VECTOR_SIZE:-1024}Embedding vector dimensionReported as vector_dimensions.configured in /health
AUTOMEM_MODELS_DIR/root/.config/automem/modelsFastEmbed model cacheMust match volume mount path

Environment variable resolution order inside Docker Compose:

  1. Process environment — export OPENAI_API_KEY=sk-... before running docker compose up
  2. .env file — Create .env in project root with OPENAI_API_KEY=sk-...
  3. Docker Compose defaults — Fallback values in docker-compose.yml

Docker Compose manages startup order using depends_on conditions:

  • FalkorDB has an active health check (password-aware redis-cli ping every 10s, 5 retries)
  • Flask API waits for condition: service_healthy on FalkorDB — it will not start until FalkorDB is accepting connections
  • Qdrant uses condition: service_started — the Flask API starts as soon as the Qdrant container starts and handles connection failures gracefully
Container PortDefault Host PortServiceProtocolPurpose
80018001flask-apiHTTPAutoMem REST API
63796379falkordbTCP (Redis)FalkorDB graph queries
30003000falkordbHTTPFalkorDB built-in web UI
63336333qdrantHTTPQdrant vector search API
63346334qdrantgRPCQdrant gRPC API

Each host port can be overridden with an environment variable (set in your shell or .env): AUTOMEM_API_HOST_PORT (API), FALKORDB_HOST_PORT (Redis), FALKORDB_BROWSER_HOST_PORT (FalkorDB UI), QDRANT_HOST_PORT (Qdrant REST), and QDRANT_GRPC_HOST_PORT (Qdrant gRPC). Only the host side changes: container ports and the service-to-service addresses below stay the same. If you change AUTOMEM_API_HOST_PORT, use that port instead of 8001 when you call the Compose stack from the host.

These mappings name no host IP, so Docker publishes every port on all host interfaces, not only on localhost. See Restricting Network Exposure before you run the stack anywhere other than a trusted machine.

Flask API connects to dependencies using service names (FALKORDB_HOST=falkordb, QDRANT_URL=http://qdrant:6333). Docker Compose automatically creates DNS entries for each service name on the automem_default bridge network.

Makefile commands reference:

CommandUnderlying ActionPurposeData Loss Risk
make devdocker compose up --buildStart all services, rebuild images if Dockerfile changedNone
make stopdocker compose downStop containers, preserve volumesNone
make logsdocker compose logs -f flask-apiFollow Flask API logs in real-timeNone
make test-integrationStart services, run pytest -rs -m integration, keep runningRun integration test suite against local Docker stackNone (uses test tokens)
make cleandocker compose down -vStop containers, remove volumesHigh — deletes all memory data

No hot-reload — restart required after code changes:

The Flask API container mounts the project directory as a volume, and docker-compose.yml sets FLASK_ENV=development / FLASK_DEBUG=1. However, app.py starts the server via run_default_server(), which always calls app.run(host="::", port=port, debug=False) — the debug flag is hardcoded, so FLASK_ENV/FLASK_DEBUG have no effect and Werkzeug’s auto-reloader never activates. Editing a Python file will not reload the running server; restart the container to pick up changes: docker compose restart flask-api (or make stop && make dev).

Running integration tests:

make test-integration orchestrates a full test run:

  1. Starts Docker Compose with test tokens (AUTOMEM_API_TOKEN=test-token, ADMIN_API_TOKEN=test-admin-token)
  2. Waits 5 seconds for service initialization
  3. Runs pytest with AUTOMEM_RUN_INTEGRATION_TESTS=1
  4. Leaves services running for debugging

Direct execution of app.py without containerization. Requires external database instances.

  • External FalkorDB instance on port 6379
  • Optional: External Qdrant instance on port 6333
  • Python 3.10+ virtual environment

Step 1 — Create virtual environment and install dependencies:

Terminal window
git clone https://github.com/verygoodplugins/automem.git
cd automem
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Step 2 — Configure environment:

Create .env in project root or export variables:

Terminal window
FALKORDB_HOST=localhost
FALKORDB_PORT=6379
AUTOMEM_API_TOKEN=your-dev-token
ADMIN_API_TOKEN=your-admin-token
PORT=8001
# Optional: enable vector search
QDRANT_URL=http://localhost:6333
# Optional: enable real embeddings
OPENAI_API_KEY=sk-...

Step 3 — Run the application:

Terminal window
python app.py

Expected startup output:

2026-07-16 12:00:00,000 | INFO | automem.api | Starting Flask API on port 8001
* Running on http://[::]:8001
Press CTRL+C to quit

app.py logs a single line before initializing FalkorDB, Qdrant, and the enrichment/embedding/consolidation workers — there is no per-step “connecting to…” or “worker started” logging. FalkorDB and Qdrant connection failures are caught internally and degrade gracefully (the service starts with that dependency marked unavailable — check /health below); sys.exit(1) is reserved for genuinely fatal initialization errors, such as a Qdrant vector-dimension mismatch with autodetection disabled. The server binds to [::] (IPv6 dual-stack) on port 8001.


After starting either method, verify AutoMem is operational:

Terminal window
curl http://localhost:8001/health

Expected response (healthy):

{
"status": "healthy",
"falkordb": "connected",
"qdrant": "connected",
"memory_count": 0,
"vector_count": 0,
"sync_status": "synced",
"vector_dimensions": {
"configured": 1024,
"effective": 1024,
"collection": 1024,
"mismatch": false
},
"enrichment": {
"status": "running",
"queue_depth": 0,
"pending": 0,
"inflight": 0,
"processed": 0,
"failed": 0
},
"timestamp": "2026-07-16T12:00:00+00:00",
"graph": "memories"
}

Response fields:

FieldTypeDescription
statusstringOverall health: "healthy" or "degraded" (either database disconnected, or vector/graph counts have drifted apart)
falkordbstringFalkorDB connection: "connected" or "disconnected"
qdrantstringQdrant connection: "connected" or "disconnected"
memory_countintegerTotal memories in the graph
vector_countinteger or nullTotal points in the Qdrant collection; null if the count couldn’t be collected (e.g. Qdrant disconnected)
sync_statusstring"synced", "drift_detected" (fewer vectors than memories), "orphaned_vectors" (more vectors than memories), or "unknown" (one of the counts is unavailable, so nothing to compare)
vector_dimensionsobjectConfigured vs. effective vs. collection embedding dimension, and whether they mismatch
enrichment.statusstringWorker thread state: "running" or "stopped"
enrichment.queue_depthintegerJobs currently queued
enrichment.pending / .inflightintegerJobs waiting vs. actively processing
enrichment.processed / .failedintegerLifetime success/failure counters
graphstringFalkorDB graph name (default: memories)
ProblemCauseSolution
503 Service UnavailableFalkorDB not runningdocker compose up -d falkordb
503 Service UnavailableIncorrect FALKORDB_HOSTRailway uses .railway.internal; Docker uses falkordb
503 Service UnavailableWrong FALKORDB_PORTDefault is 6379
503 Service UnavailableAuth failureCheck FALKORDB_PASSWORD matches both services

After the health check passes, store and retrieve a test memory:

Terminal window
# Store a memory
curl -X POST http://localhost:8001/memory \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"content": "Prefer PostgreSQL for relational data", "type": "Preference"}'
# Recall memories
curl "http://localhost:8001/recall?q=database+preferences" \
-H "Authorization: Bearer test-token"

Successful response indicators:

  • POST returns 201 Created with memory_id
  • GET /recall returns non-empty results array
  • Memory appears with match_type: "vector" or "keyword"

Docker Compose is optimized for development. Production deployments require additional hardening:

AspectDocker Compose (Dev)Production Recommendations
Debug ModeCompose exports FLASK_DEBUG=1; the server runs with debug=FalseRemove the debug exports from the compose file. AutoMem has no log-level variable: the server logs at INFO
API TokensDefaults to test-token, test-admin-tokenGenerate cryptographically secure tokens (32+ chars)
Port ExposurePorts published on all host interfaces (no host IP in the mappings)Bind to 127.0.0.1; to filter instead, use Docker’s DOCKER-USER iptables chain or an external firewall (on Linux, ufw and firewalld rules do not apply to Docker-published ports); use a reverse proxy (nginx, Traefik) and expose only necessary ports
Volume BackupsManual exports to ./backups/Automated backups to S3/remote storage
Resource LimitsUnlimitedSet deploy.resources.limits in docker-compose.yml
Restart Policyrestart: unless-stoppedUse restart: always with health checks
SSL/TLSHTTP onlyTerminate SSL at reverse proxy or use Traefik
MonitoringDocker logsExternal monitoring (Prometheus, health checks)

docker-compose.yml lists each published port without a host IP (for example "${AUTOMEM_API_HOST_PORT:-8001}:8001"), so Docker binds it on all host interfaces. That is standard Docker behavior: other machines that can reach this host can reach the stack, not only localhost. The compose file also falls back to test-token and test-admin-token when AUTOMEM_API_TOKEN and ADMIN_API_TOKEN are unset, and it sets no FalkorDB password or Qdrant API key by default.

Before you use the stack on a shared network or a server:

  • Bind to loopback. Add 127.0.0.1: to each port mapping you keep, for example "127.0.0.1:${AUTOMEM_API_HOST_PORT:-8001}:8001". If you filter the ports instead, use rules in Docker’s DOCKER-USER iptables chain or a firewall outside the host, such as a cloud security group. On Linux, ufw and firewalld rules do not apply to Docker-published ports, so ufw deny 6379 does not close the FalkorDB port.
  • Drop ports you do not use. The Flask API reaches FalkorDB and Qdrant over the Compose network (FALKORDB_HOST=falkordb, QDRANT_URL=http://qdrant:6333), so only your own tools need the 6379, 3000, 6333, and 6334 host ports.
  • Set real secrets. Put strong AUTOMEM_API_TOKEN and ADMIN_API_TOKEN values in .env, and set FALKORDB_PASSWORD, before you expose any port.

When to use Docker Compose vs. Railway:

ScenarioRecommended Deployment
Single developer, local-only accessDocker Compose on local machine
Team collaboration, remote access neededRailway
Self-hosted production, existing infrastructureDocker Compose with reverse proxy + backups
Prototyping, short-term experimentsDocker Compose (no cloud costs)
Production with minimal ops overheadRailway (managed backups, monitoring)
Air-gapped environments, strict data localityDocker Compose on self-hosted infrastructure

Data can be migrated using backup/restore scripts:

  1. Export from Docker Compose:

    Terminal window
    # Export FalkorDB RDB
    docker compose exec falkordb redis-cli SAVE
    docker compose cp falkordb:/data/dump.rdb ./backups/falkordb/
    # Export Qdrant snapshot
    curl -X POST http://localhost:6333/collections/memories/snapshots
  2. Import to Railway:

    Use the GitHub Actions backup workflow to populate Railway from exported snapshots.