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:
+102
@@ -196,3 +196,105 @@ flowchart LR
|
|||||||
**Rotating a secret** is a host-side edit — update the file and the service picks
|
**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
|
it up on restart. No rebuild, and nothing you run: flag it and we handle the
|
||||||
restart.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user