docs: document Redis as a per-repo compose sidecar

New Deploy section 'Supporting services live in your compose, not the
fleet': Redis is declared per-repo, never shared fleet-wide — a shared
instance would re-couple services the per-project network deliberately
isolates.

- ephemeral vs persistent as a conscious choice, both compose snippets in
  content tabs (--save "" + no volume, vs --appendonly yes + ${MOUNTS_DIR})
- connect by compose service name (redis://redis:6379), never a host port
- caveats given weight: appendfsync everysec ~1s loss window, and mounts
  are not backed up — the dividing line between queue state and Postgres
- SQLite noted as the no-container alternative for a single drainer
- the don'ts, plus one line on why ACLs aren't the answer here
- cross-links the redis lib on the Libraries page

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-14 15:52:55 -04:00
parent b3acd57e32
commit a248879451
+102
View File
@@ -196,3 +196,105 @@ flowchart LR
**Rotating a secret** is a host-side edit — update the file and the service picks
it up on restart. No rebuild, and nothing you run: flag it and we handle the
restart.
## Supporting services live in your compose, not the fleet
Need Redis? **Declare it in your own `compose.yaml`, as a sidecar.** There is no shared
Redis — nothing fleet-wide, nothing per-workspace, nothing ops provisions for you. A repo
that needs Redis brings its own; a repo that doesn't adds nothing.
That's the whole point of the deploy model. Every service already runs in its own compose
project on its own network so that one service falling over can't touch another. A shared
Redis puts that coupling straight back: one process everything depends on, whose OOM, stray
`FLUSHALL`, single-threaded stall, or restart becomes *everyone's* outage. A sidecar shares
its owning repo's fate and nobody else's — and the isolation is free, because it rides the
per-project network you already get. No ACLs, no key-prefix discipline, no shared
credentials to manage.
Your app talks to it with the [`redis` lib](libraries.md) from the suite (async,
config-free, kv/hash/ttl/pubsub), pointed at **`redis://redis:6379`** — the compose
**service name**, not a host port. The sidecar comes up auto-namespaced on your project's
network like every other container, exactly as the naming convention above describes.
### Ephemeral or persistent — pick deliberately
A sidecar Redis is **ephemeral by default**: restart it and the data is gone. That's
correct for some workloads and quietly destructive for others, so make the call on purpose.
Ask one question — *if this data vanished on a restart, would anything be lost?*
- **No → ephemeral.** A scratch cache, a dedupe set, rate-limit counters, transient data
you can just re-fetch. Nothing to back up, nothing to grow.
- **Yes → persistent.** An outbound webhook or notification queue, a job queue, anything
that could be mid-flight when the process dies. Losing it drops real work.
=== "Ephemeral (cache / throwaway)"
Fine to lose on restart — no volume, no persistence, by design.
```yaml
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --save "" --maxmemory 256mb --maxmemory-policy allkeys-lru
# no volume: throwaway by design
```
=== "Persistent (durable queue / state)"
Survives restart, rebuild, and reboot — the append-only file lives on the host mounts
dir injected at deploy, the same `${MOUNTS_DIR}` mechanism described above. Redis just
uses it as its backing store.
```yaml
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes --appendfsync everysec
volumes:
- ${MOUNTS_DIR:-./mounts}/redis:/data # AOF persists on the host mounts dir
```
The app connects the same way in both modes — `REDIS_URL: redis://redis:6379`. Only the
durability changes.
!!! warning "Two things to know before you rely on a persistent sidecar"
**`--appendfsync everysec` can lose ~1 second of the newest entries** on a hard crash.
For a webhook queue that's an acceptable trade — just know it's there. Use
`--appendfsync always` if you genuinely cannot drop a single entry (safer, slower).
**Data under the mounts dir is not backed up.** Mounts are excluded from the backup
pipeline, and that's *right* for a queue: a lost queue means some notifications didn't
fire, not that business data is gone. **That's the dividing line.** If losing this data
would actually hurt, it isn't queue or cache state — it's a system of record, and it
belongs in Postgres (which *is* backed up), not a local mount.
!!! tip "Sometimes you don't need Redis at all"
For the dead-simple case — **one process draining its own queue** — a **SQLite file**
under the mounts dir does the job with no extra container: atomic commits, crash-safe,
zero infra. Reach for Redis when it's actually doing queue *things* (multiple workers,
blocking pops, streams / consumer groups, pub/sub) or when it's already in your compose
for something else.
### The rules
!!! danger "Don't do these"
- **Don't map Redis to a host port.** No `ports: - "6379:6379"`. Two repos both
grabbing host 6379 on the same box collide. Keep it internal to the compose network —
nothing exposed, nothing to collide.
- **Don't stand up a shared or fleet-wide Redis.** Per-repo means per-need. One Redis
per *project*, shared by that project's containers if a repo runs several — never one
per fleet.
- **Don't treat persistent Redis as a database.** Queues and caches, yes. A durable
system of record, no — that's Postgres, and unlike a mount it's backed up.
**You can't reach another repo's Redis** — different project, different network. That's not
a restriction you have to work around; it's the isolation working *for* you. Nobody else's
service can touch your cache or drain your queue either, and you never have to think about
whose keys are whose.
!!! quote "What about ACLs?"
A shared Redis *can* be secured — Redis 6+ ACLs scope users by command, key pattern, and
channel. But ACLs don't solve resource contention or the noisy-neighbour problem, and
they add real management burden, so the fleet uses per-repo sidecars instead. Reserve
ACLs for the rare case of a deliberately shared, durable, backed-up Redis run as actual
infrastructure.