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.
Installation Method Comparison
Section titled “Installation Method Comparison”| Feature | Docker Compose | Bare Metal |
|---|---|---|
| External Access | Ports published on all host interfaces by default (see below) | app.py listens on all interfaces |
| Data Persistence | Named volumes (manual backup) | External DB dependent |
| Use Case | Full-stack development | API development only |
| Services Included | app.py, FalkorDB, Qdrant | app.py only |
Prerequisites for bare Python:
| Requirement | Version | Purpose |
|---|---|---|
| Python | 3.10+ | Runtime for app.py and background workers |
| Git | Any recent | Repository cloning |
Docker Compose (Recommended)
Section titled “Docker Compose (Recommended)”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
Prerequisites
Section titled “Prerequisites”- Docker 20.10+
- Docker Compose 2.0+
- Git
Starting the Stack
Section titled “Starting the Stack”Clone the server repository and open its directory.
git clone https://github.com/verygoodplugins/automem.gitcd automemSet the provider in the repository’s .env before starting the stack. For on-server embeddings, use:
EMBEDDING_PROVIDER=localVECTOR_SIZE=1024This 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):
make devThis runs docker compose up --build and rebuilds the Flask API image if Dockerfile or requirements.txt have changed.
Method 2 — Direct Docker Compose:
docker compose up --buildConfirm 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.
Service Details
Section titled “Service Details”| Service | Image/Build | Ports | Purpose | Health Check |
|---|---|---|---|---|
flask-api | Built from Dockerfile | 8001 | AutoMem Flask API with background workers | None (depends on FalkorDB health) |
falkordb | falkordb/falkordb:latest | 6379 (Redis), 3000 (UI) | Graph database (canonical memory storage) | Password-aware redis-cli ping every 10s |
qdrant | qdrant/qdrant:v1.11.3 | 6333 (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 athttp://localhost:3000is the official local graph-inspection UI shipped inside thefalkordbcontainer. The/viewerpath on the AutoMem API is the production entrypoint — it redirects to the standaloneautomem-graph-viewerapp whenGRAPH_VIEWER_URLis 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 tohttp://localhost:3000, so you will need to change one of the ports (for example, setPORTfor the viewer) to avoid a collision.
Volume Configuration
Section titled “Volume Configuration”Docker Compose defines named volumes for persistent data and a bind mount for the source tree:
| Volume | Container Path | Purpose | Persistence Level |
|---|---|---|---|
falkordb_data | /data | RDB snapshots + AOF (append-only file) | High (every 60s or 1 key change) |
qdrant_data | /qdrant/storage | Vector collections + write-ahead log | High (write-ahead log) |
fastembed_models | /root/.config/automem/models | Downloaded ONNX embedding models | Medium (cache, re-downloadable) |
. (bind mount) | /app | Source code (see note below — editing files does not auto-reload the running server) | N/A (host filesystem) |
./backups/falkordb | /backups | Manual RDB exports | N/A (host filesystem) |
./backups/qdrant | /backups | Manual snapshot exports | N/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.
Environment Variables
Section titled “Environment Variables”Required variables for the Docker Compose stack:
| Variable | Docker Compose Default | Purpose | Notes |
|---|---|---|---|
PORT | 8001 | Flask API port | Must match container port mapping |
AUTOMEM_API_TOKEN | ${AUTOMEM_API_TOKEN:-test-token} | Client authentication | Set via shell or .env |
ADMIN_API_TOKEN | ${ADMIN_API_TOKEN:-test-admin-token} | Admin endpoint authentication | Set via shell or .env |
FALKORDB_HOST | falkordb | FalkorDB service name | Docker internal DNS resolution |
FALKORDB_PORT | 6379 | FalkorDB port | Standard Redis port |
Optional variables with defaults:
| Variable | Docker Compose Default | Purpose | Notes |
|---|---|---|---|
FLASK_ENV | development | Flask environment mode | Set for convention, but not read by app.py — see hot-reload note below |
FLASK_DEBUG | "1" | Flask debug flag | Set for convention, but not read by app.py — see hot-reload note below |
FALKORDB_PASSWORD | ${FALKORDB_PASSWORD:-} | FalkorDB authentication | Empty by default (no auth) |
QDRANT_URL | http://qdrant:6333 | Qdrant endpoint | Docker internal URL |
QDRANT_API_KEY | ${QDRANT_API_KEY:-} | Qdrant authentication | Not required for local Qdrant |
OPENAI_API_KEY | ${OPENAI_API_KEY:-} | OpenAI API access | Optional. 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 access | Used when EMBEDDING_PROVIDER=voyage or auto-selected |
EMBEDDING_PROVIDER | ${EMBEDDING_PROVIDER:-auto} | Provider selection | auto|voyage|openai|ollama|local|placeholder |
VECTOR_SIZE | ${VECTOR_SIZE:-1024} | Embedding vector dimension | Reported as vector_dimensions.configured in /health |
AUTOMEM_MODELS_DIR | /root/.config/automem/models | FastEmbed model cache | Must match volume mount path |
Environment variable resolution order inside Docker Compose:
- Process environment —
export OPENAI_API_KEY=sk-...before runningdocker compose up .envfile — Create.envin project root withOPENAI_API_KEY=sk-...- Docker Compose defaults — Fallback values in
docker-compose.yml
Service Dependencies and Health Checks
Section titled “Service Dependencies and Health Checks”Docker Compose manages startup order using depends_on conditions:
- FalkorDB has an active health check (password-aware
redis-cli pingevery 10s, 5 retries) - Flask API waits for
condition: service_healthyon 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
Port Mappings
Section titled “Port Mappings”| Container Port | Default Host Port | Service | Protocol | Purpose |
|---|---|---|---|---|
| 8001 | 8001 | flask-api | HTTP | AutoMem REST API |
| 6379 | 6379 | falkordb | TCP (Redis) | FalkorDB graph queries |
| 3000 | 3000 | falkordb | HTTP | FalkorDB built-in web UI |
| 6333 | 6333 | qdrant | HTTP | Qdrant vector search API |
| 6334 | 6334 | qdrant | gRPC | Qdrant 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.
Development Workflow
Section titled “Development Workflow”Makefile commands reference:
| Command | Underlying Action | Purpose | Data Loss Risk |
|---|---|---|---|
make dev | docker compose up --build | Start all services, rebuild images if Dockerfile changed | None |
make stop | docker compose down | Stop containers, preserve volumes | None |
make logs | docker compose logs -f flask-api | Follow Flask API logs in real-time | None |
make test-integration | Start services, run pytest -rs -m integration, keep running | Run integration test suite against local Docker stack | None (uses test tokens) |
make clean | docker compose down -v | Stop containers, remove volumes | High — 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:
- Starts Docker Compose with test tokens (
AUTOMEM_API_TOKEN=test-token,ADMIN_API_TOKEN=test-admin-token) - Waits 5 seconds for service initialization
- Runs pytest with
AUTOMEM_RUN_INTEGRATION_TESTS=1 - Leaves services running for debugging
Bare Metal Python
Section titled “Bare Metal Python”Direct execution of app.py without containerization. Requires external database instances.
Prerequisites
Section titled “Prerequisites”- External FalkorDB instance on port 6379
- Optional: External Qdrant instance on port 6333
- Python 3.10+ virtual environment
Setup Steps
Section titled “Setup Steps”Step 1 — Create virtual environment and install dependencies:
git clone https://github.com/verygoodplugins/automem.gitcd automempython -m venv venvsource venv/bin/activatepip install -r requirements.txtStep 2 — Configure environment:
Create .env in project root or export variables:
FALKORDB_HOST=localhostFALKORDB_PORT=6379AUTOMEM_API_TOKEN=your-dev-tokenADMIN_API_TOKEN=your-admin-tokenPORT=8001# Optional: enable vector searchQDRANT_URL=http://localhost:6333# Optional: enable real embeddingsOPENAI_API_KEY=sk-...Step 3 — Run the application:
python app.pyExpected startup output:
2026-07-16 12:00:00,000 | INFO | automem.api | Starting Flask API on port 8001 * Running on http://[::]:8001Press CTRL+C to quitapp.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.
Verification and Health Checks
Section titled “Verification and Health Checks”After starting either method, verify AutoMem is operational:
curl http://localhost:8001/healthExpected 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:
| Field | Type | Description |
|---|---|---|
status | string | Overall health: "healthy" or "degraded" (either database disconnected, or vector/graph counts have drifted apart) |
falkordb | string | FalkorDB connection: "connected" or "disconnected" |
qdrant | string | Qdrant connection: "connected" or "disconnected" |
memory_count | integer | Total memories in the graph |
vector_count | integer or null | Total points in the Qdrant collection; null if the count couldn’t be collected (e.g. Qdrant disconnected) |
sync_status | string | "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_dimensions | object | Configured vs. effective vs. collection embedding dimension, and whether they mismatch |
enrichment.status | string | Worker thread state: "running" or "stopped" |
enrichment.queue_depth | integer | Jobs currently queued |
enrichment.pending / .inflight | integer | Jobs waiting vs. actively processing |
enrichment.processed / .failed | integer | Lifetime success/failure counters |
graph | string | FalkorDB graph name (default: memories) |
Common Health Check Failures
Section titled “Common Health Check Failures”| Problem | Cause | Solution |
|---|---|---|
| 503 Service Unavailable | FalkorDB not running | docker compose up -d falkordb |
| 503 Service Unavailable | Incorrect FALKORDB_HOST | Railway uses .railway.internal; Docker uses falkordb |
| 503 Service Unavailable | Wrong FALKORDB_PORT | Default is 6379 |
| 503 Service Unavailable | Auth failure | Check FALKORDB_PASSWORD matches both services |
First Memory Test
Section titled “First Memory Test”After the health check passes, store and retrieve a test memory:
# Store a memorycurl -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 memoriescurl "http://localhost:8001/recall?q=database+preferences" \ -H "Authorization: Bearer test-token"Successful response indicators:
- POST returns
201 Createdwithmemory_id - GET
/recallreturns non-emptyresultsarray - Memory appears with
match_type: "vector"or"keyword"
Production Considerations
Section titled “Production Considerations”Docker Compose is optimized for development. Production deployments require additional hardening:
| Aspect | Docker Compose (Dev) | Production Recommendations |
|---|---|---|
| Debug Mode | Compose exports FLASK_DEBUG=1; the server runs with debug=False | Remove the debug exports from the compose file. AutoMem has no log-level variable: the server logs at INFO |
| API Tokens | Defaults to test-token, test-admin-token | Generate cryptographically secure tokens (32+ chars) |
| Port Exposure | Ports 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 Backups | Manual exports to ./backups/ | Automated backups to S3/remote storage |
| Resource Limits | Unlimited | Set deploy.resources.limits in docker-compose.yml |
| Restart Policy | restart: unless-stopped | Use restart: always with health checks |
| SSL/TLS | HTTP only | Terminate SSL at reverse proxy or use Traefik |
| Monitoring | Docker logs | External monitoring (Prometheus, health checks) |
Restricting Network Exposure
Section titled “Restricting Network Exposure”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’sDOCKER-USERiptables 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, soufw deny 6379does 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 the6379,3000,6333, and6334host ports. - Set real secrets. Put strong
AUTOMEM_API_TOKENandADMIN_API_TOKENvalues in.env, and setFALKORDB_PASSWORD, before you expose any port.
When to use Docker Compose vs. Railway:
| Scenario | Recommended Deployment |
|---|---|
| Single developer, local-only access | Docker Compose on local machine |
| Team collaboration, remote access needed | Railway |
| Self-hosted production, existing infrastructure | Docker Compose with reverse proxy + backups |
| Prototyping, short-term experiments | Docker Compose (no cloud costs) |
| Production with minimal ops overhead | Railway (managed backups, monitoring) |
| Air-gapped environments, strict data locality | Docker Compose on self-hosted infrastructure |
Migrating from Docker Compose to Railway
Section titled “Migrating from Docker Compose to Railway”Data can be migrated using backup/restore scripts:
-
Export from Docker Compose:
Terminal window # Export FalkorDB RDBdocker compose exec falkordb redis-cli SAVEdocker compose cp falkordb:/data/dump.rdb ./backups/falkordb/# Export Qdrant snapshotcurl -X POST http://localhost:6333/collections/memories/snapshots -
Import to Railway:
Use the GitHub Actions backup workflow to populate Railway from exported snapshots.