Service Redis - thekaveh/atlas GitHub Wiki
Shared cache, queue and pub/sub broker. Redis is the most cross-cutting service in the stack: one instance serves every consumer in ยง12.2. It has one container, one source variant (container), no GPU path and no init container.
Consumers are separated by database index, not by service. The ยง3 table lists which consumer uses which index.
Image: redis:7.2.14-alpine. Persistence: AOF (--appendonly yes). Auth: one shared password (REDIS_PASSWORD), with no ACL users. Every consumer connects to redis:6379 by Docker DNS on backend-network. The host port (default 63025) is for debugging.
Volume: ${PROJECT_NAME}-redis-data (AOF append log). ./stop.sh --cold removes it.
| Path | URL | Notes |
|---|---|---|
| Host (debug) |
localhost:${REDIS_PORT} (default 63025) |
Use with redis-cli -h 127.0.0.1 -p 63025 -a "$REDIS_PASSWORD". |
| Internal | redis://:${REDIS_PASSWORD}@redis:6379/<db> |
What sibling containers use. |
| Kong | โ | Redis is infrastructure; no Kong route. |
Canonical port table: Ports and Routes.
REDIS_SOURCE=container # only value
REDIS_PORT=63025 # host port; container port is always 6379
REDIS_PASSWORD=redis_password # placeholder; ./start.sh replaces it with a random value on first run
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 # default; consumers override db index
REDIS_MAXMEMORY=0 # no cap; see ยง9
REDIS_MAXMEMORY_POLICY=volatile-lruTo change REDIS_PASSWORD, edit .env and rerun ./start.sh. Redis and every consumer read the password only when their containers start, so all of them must be recreated.
Database-index convention (consumer-built URLs):
| DB | Consumer | Notes |
|---|---|---|
| 0 | n8n, litellm, langfuse, backend | n8n queue (QUEUE_BULL_REDIS_DB: 0). LiteLLM cache and Langfuse BullMQ set no index, so they use 0. Backend state through REDIS_URL (ยง4). |
| 2 | open-webui, lightrag | WebSocket store (OPEN_WEB_UI_REDIS_DB) and LightRAG KV/doc-status. The key shapes do not overlap; isolate one of them if you reuse db 2. |
| 3 | jupyterhub | notebook REDIS_URL
|
| 4 | celery | broker + result backend (CELERY_BROKER_URL / CELERY_RESULT_BACKEND) |
| 5 | trueforge | REDIS_URL |
A consumer that needs its own namespace builds a URL from ${REDIS_PASSWORD} and redis:6379/<db>.
Startup ordering. The manifest lists depends_on.required: supabase only to pin topology port slots; removing it would renumber later services' ports. Redis has no functional Postgres dependency. The only compose-level depends_on in this fragment is redis-exporter waiting for a healthy redis.
Consumers. ยง12.2 lists every service that reaches Redis at runtime. Notes:
- The Backend and Celery use db 0 through
REDIS_URLfor hosted-media operations, RAG ingestion state and memory consolidation leases. The Backend readiness probe checks it too. - Kong does not use Redis. Its only rate limiter runs with
policy: local, and itsdepends_on: redisis start ordering only. - Airflow receives
REDIS_PASSWORDfor DAGs that useRedisHook. - Local Deep Researcher is not wired (ยง12.4).
Failure mode. No consumer has a fallback. A Redis outage stops n8n queue execution and Open WebUI live updates. It also breaks LiteLLM caching and stalls LightRAG's KV layer.
Eviction policy. See ยง9.
Observability sidecar (redis-exporter). The family also ships redis-exporter (oliver006/redis_exporter:v1.86.0) on host port ${REDIS_EXPORTER_PORT} and container port 9121.
- It scales 1โ0 with
PROMETHEUS_SOURCE: the bootstrapper's_generate_prometheus_config()hook writesREDIS_EXPORTER_SCALE. - Prometheus scrapes
redis-exporter:9121/metrics. ThePostgres + RedisGrafana dashboard shows memory usage, ops/sec and hit ratio. - Its
/scrape?target=endpoint is disabled (REDIS_EXPORTER_DISABLE_SCRAPE_ENDPOINT=true), because it would dial any target withREDIS_PASSWORD.
When LIGHTRAG_SOURCE != disabled AND REDIS_SOURCE != disabled, LightRAG uses Redis db=2 as its KV and doc-status backend (via RedisKVStorage). Use redis-cli -a "$REDIS_PASSWORD" -n 2 --scan --pattern '*' to inspect.
Support tier: experimental โ Capability contract declared; no cited cold-start, workflow, or upgrade qualification run yet (evidence at v0.1.0).
| Capability | Status | Verification | Notes |
|---|---|---|---|
| Authenticated cache and queue substrate | supported | tested | Atlas runs password-protected Redis as the shared cache, queue, coordination, and transient-state substrate for multiple service families. |
| AOF persistence and bounded eviction | partial | tested | Append-only persistence and volatile-LRU are configured, but the default zero maxmemory is unbounded and only expiring keys become eviction candidates after a cap is set. |
| Per-service ACL and transport isolation | not-supported | documented | Consumers share one password and logical database convention over plaintext Redis; Atlas provisions neither per-service ACL users nor TLS. |
NOAUTH Authentication required. Consumer's REDIS_URL is missing the password segment. Inspect with docker exec <project>-backend env | grep REDIS_URL. Expected shape: redis://:${REDIS_PASSWORD}@redis:6379/<db> โ note the leading colon before the password (no username).
n8n EXECUTIONS_MODE=queue workflows hang. Check docker logs <project>-redis for connection errors from n8n. n8n's queue mode uses Redis db /0 (QUEUE_BULL_REDIS_DB: 0). If the password changed and n8n was not recreated, its workers retry forever (ยง3).
Memory pressure. Monitor with docker exec <project>-redis redis-cli INFO memory (the container sets REDISCLI_AUTH). Set a cap as ยง9 describes.
Redis crash-loops with Bad file format reading the append only file. An unclean write, such as a host power loss, left bytes in the append-only file (AOF) that Redis cannot parse. Redis refuses to load it, Compose --wait fails, and every service that waits on it stays at Created.
-
./start.sh doctorreports a failedredis-aofcheck. It names the volume and prints the checker lines with the offset where valid data ends. The check reads a copy of the volume and never changes it. - A last command that was only cut off is not a failure. Redis drops it and starts (
aof-load-truncatedis on by default), so the check passes. - Back up first.
--fixtruncates the file at the first bad byte and drops every write after it, so keep the backup until the stack is verified.
Run this Bash sequence from the Atlas repository root with uv and the bootstrapper dependencies installed.
Keep ATLAS_ENV_FILE set to the deployment's active file, if configured.
Relative paths start at the repository root; absolute paths are used directly.
The default active file is .env.
The selected file must exist and contain a nonblank REDIS_IMAGE.
Replace <project> with the deployment's project name before running the sequence.
The sequence stops if configuration loading or the backup fails.
REDIS_IMAGE=$(PYTHONPATH=bootstrapper uv run --project bootstrapper python - <<'PY'
from core.config_parser import ConfigParser
parser = ConfigParser(root_dir=".")
if not parser.env_file_path.is_file():
raise SystemExit("The active env file is missing or is not a file.")
try:
image = parser.parse_env_file().get("REDIS_IMAGE", "")
except (OSError, ValueError) as error:
raise SystemExit(str(error)) from None
if not image.strip():
raise SystemExit("Set REDIS_IMAGE in the active env file before recovery.")
print(image, end="")
PY
) || exit 1
./stop.sh
VOLUME=<project>-redis-data
docker run --rm -v "$VOLUME":/data -v "$PWD":/backup "$REDIS_IMAGE" \
tar czf /backup/redis-aof-backup.tgz -C /data appendonlydir || exit 1
docker run --rm -it -v "$VOLUME":/data -w /data/appendonlydir "$REDIS_IMAGE" \
redis-check-aof --fix appendonly.aof.manifest # answer y to truncate
./start.shIf the doctor reports that the base snapshot is not sane, --fix cannot repair it. Restore an earlier backup, or remove the volume (docker volume rm "$VOLUME") to start Redis empty and lose its queue, sessions and cache. To restore a backup, extract it through the same container, because the archive is root-owned on Linux:
docker run --rm -v "$VOLUME":/data -v "$PWD":/backup "$REDIS_IMAGE" \
sh -c 'rm -rf /data/appendonlydir && tar xzf /backup/redis-aof-backup.tgz -C /data'Data loss after ./stop.sh --cold. Expected โ --cold deletes the ${PROJECT_NAME}-redis-data volume, taking the AOF log with it. Use ./stop.sh (no --cold) to preserve queue/session state across restarts.
docker compose ps redis
docker compose logs -f redis
docker exec <project>-redis redis-cli INFO serverFor general startup and routing issues, see Troubleshooting.
Inspect keys by namespace. Useful prefixes are bull: (n8n queue), litellm.cache: (LiteLLM response cache), and LightRAG's {workspace}_{namespace}: keys on db /2. Scan with redis-cli --scan --pattern 'bull:*'. Never use KEYS on a busy instance.
Watch traffic live. redis-cli MONITOR prints every command the server receives. Use it to check that a new consumer connects to the right db index, then stop it.
Force AOF rewrite. BGREWRITEAOF compacts the AOF after heavy churn. It is safe to run at any time.
Cold-start vs warm-start. ./stop.sh (no flags) keeps the AOF, and Redis replays it on the next start, so the n8n queue and sessions survive. ./stop.sh --cold deletes the volume.
Memory cap and eviction.
-
REDIS_MAXMEMORYdefaults to0(no cap). Redis then grows until Docker's memory limit kills it, which loses in-flight queue state. - Set a cap of about 75% of the container's memory budget, for example
REDIS_MAXMEMORY=512mb. -
REDIS_MAXMEMORY_POLICYdefaults tovolatile-lru, which evicts only keys with a TTL. These include LiteLLM's response cache and ordinary Backend media records. - Media retention defaults to seven days, but a memory cap can evict TTL-bearing records earlier, including operations without budget tracking.
- Budget-tracked in-flight records, unresolved
submission_unknownrecords and pending ledger intents remain persistent until settlement. See Backend media retention. - Keys without a TTL survive
volatile-lru. - If no TTL key is left, writes fail with an OOM error.
Stack-relevant knobs (the first two are .env settings; the rest need a Compose change):
| Knob | Default | Recommended for stack |
|---|---|---|
maxmemory |
unbounded | 75% of container memory budget |
maxmemory-policy |
volatile-lru |
Keep for the mixed stock workload; use a dedicated cache instance before selecting allkeys-lru. |
appendfsync |
everysec |
leave as-is; always is overkill, no loses queue state on crash |
save (RDB) |
disabled | enable for faster cold-start replay |
Set REDIS_MAXMEMORY and REDIS_MAXMEMORY_POLICY in .env. The remaining low-level knobs require a targeted Compose change.
-
Shared password. Every consumer uses the same
REDIS_PASSWORD. A compromised n8n container can read LiteLLM's budget counters and backend sessions. Redis 7 ACLs would fix this โ see Future โ Unused features. -
No TLS in-cluster. Traffic on
backend-networkis unencrypted. This is acceptable on one host, not across hosts. Usestunnelor a Redis build with native TLS if traffic crosses a trust boundary. -
Host port.
REDIS_PORT(default 63025) binds to127.0.0.1unless you widenHOST_BIND_IP. Every consumer shares one password, so do not widen the bind without a firewall. -
AOF includes commands, not just data.
appendonly.aofis a literal command log; anyone with read access to the volume can reconstruct every key. Treat the volume as confidential.
- Redis commands reference โ the canonical command index, organized by data type.
- Redis persistence โ AOF vs RDB trade-offs, useful when tuning the stack's defaults.
-
BullMQ on Redis โ n8n's queue layer; explains the
bull:*key shape. - LiteLLM caching โ Redis cache integration LiteLLM uses (already enabled in this stack).
No upstream calls.
| Service | Category | Status |
|---|---|---|
| langfuse | infra | current |
| prometheus | infra | current |
| litellm | llm | current |
| airflow | agents | optional: an operator-authored DAG; airflow-init only seeds the Connection |
| celery | agents | current |
| lightrag | agents | current |
| n8n | agents | current |
| trueforge | agents | current |
| backend | apps | current |
| jupyterhub | apps | current |
| open-webui | apps | current |

Open the full-size diagram for a full-screen view.
-
redis โ comfyui โ Why: ComfyUI's compose declares
depends_on: redis(startup ordering only) but the container receives noREDIS_URL. A real link would let n8n or the Backend queue generation jobs in a Redis list or stream. A sidecar publisher would returnprogress/executedevents and replace the per-caller websocket pattern. Mechanism: ComfyUI custom node +redis-pywritingXADD comfyui:eventson progress; producersBLPOP comfyui:jobsfrom a tiny worker that calls/prompt. Effort: medium. Confidence: medium. -
redis โ local-deep-researcher โ Why: LDR's compose has no
REDIS_URL. Dbs/3,/4and/5belong to JupyterHub, Celery and TrueForge, so an LDR checkpointer would take a new index such as/6. LangGraph's Redis checkpointer would let long-running research runs survive container restarts and let backend stream node-by-node progress. Mechanism:redis://:${REDIS_PASSWORD}@redis:6379/6consumed bylanggraph.checkpoint.redis.RedisSaver;PUBSUBchannelldr:run:<id>for progress. Effort: small. Confidence: high. -
redis โ hermes โ Why: Hermes has no shared state between requests; conversation memory, tool-call rate-limits, and per-user budget counters live in process. Mechanism: Hermes custom skill reads/writes
hermes:session:<id>hashes andhermes:ratelimit:<user>counters viaredis-py. Effort: small. Confidence: medium. -
redis โ doc-processor โ Why: document parsing is expensive and idempotent on file SHA. A Redis cache keyed on
sha256(file)lets repeat ingests (common during n8n flow iteration) short-circuit; a Redis stream broadcastsdoc:parsedevents to backend + weaviate ingest. Mechanism: cache:SETEX doc:parsed:<sha> 86400 <json>; event bus:XADD doc:events. Effort: small. Confidence: medium. -
redis โ weaviate โ Why: embedding generation dominates ingest latency; a content-hash โ vector cache cuts repeat-ingest cost dramatically and de-duplicates concurrent embeddings across n8n/backend. Mechanism:
GET emb:<model>:<sha>before calling Weaviate's vectorizer;SETEXon miss. Lives behind a tiny helper in backend. Effort: medium. Confidence: medium.
- RedisInsight โ Headline: official Redis GUI for browsing keys, profiling commands, and inspecting streams across all stack consumers. Wires into: backend, n8n, kong, litellm, open-webui, jupyterhub.
-
Redis Stack (
redis-stack-server) โ Headline: drop-in Redis image bundling RediSearch, RedisJSON, RedisBloom, and RedisTimeSeries โ unlocks vector + JSON queries without a second datastore. Wires into: backend, weaviate (overlap), n8n, hermes.
-
Redis Streams (
XADD/XREAD/consumer groups) โ Why pursue: one durable event bus, already in the image, could replace ad-hoc HTTP fan-out between backend, n8n, ComfyUI and doc-processor. Effort: medium. - Pub/Sub channels โ Why pursue: live progress streaming for ComfyUI and LDR to the Open WebUI chat surface without polling. Effort: small.
-
Redis ACL users โ Why pursue: replace the single shared
REDIS_PASSWORDwith per-service users so a compromised n8n container cannot read LiteLLM's budget counters. Effort: small. -
Workload-specific memory classes โ Why pursue: separate cache and queue instances could each have their own cap and policy, not one shared budget and
volatile-lru. Effort: medium. -
RDB snapshots alongside AOF โ Why pursue: faster cold-start restore; current
--appendonly yesis durable but slow to replay on large datasets. Effort: small.