Redis
Redis is the in-memory key-value store most applications
reach for when they need a cache, a session backend, leaderboards (sorted sets)
or atomic counters. pgcli runs it as a standalone, top-level addon with the
same CLI surface as the object stores (install / start / stop / logs /
autostart / remove), managed entirely through pg.yaml.
It is deliberately the simplest addon in the fleet:
- One container, one port, one password.
requirepassis generated on the first install and stored inpg.yaml— an unauthenticated Redis is not a reachable state. - Version selection. It is the first version-selectable addon:
--version 7|8resolves through a built-in table to a pinneddocker.io/library/redistag (7 →7.4.11, 8 →8.10.2, the default).--imagestill bypasses the table with any tag you like. - Persistence you can dial in. By default the dataset is an RDB snapshot in
a bind-mounted host directory — it survives restarts, and survives
pg addon remove(without--clean-data) so reinstalling the same name revives the data.--aofadds the write-ahead log on top for crash-safe, no-eviction stores, and--save noturns snapshots off entirely (see Parameters).
Pick Redis for cache/session/ranking/counter workloads next to your PostgreSQL instances; it is independent of the PG stack, coexists with any other addon, and several instances (even across majors) can run on one host.
Out of scope: Sentinel (automatic failover) is not managed by pgcli. Redis’s native cluster is supported —
--clusterturns instances into cluster-enabled members that you assemble once yourself withpg redis-cli -- --cluster create(masters-only by default; add per-master followers with--cluster-replicas N; see Native cluster). Read replicas are too —--replica-ofturns an instance into a read-only follower of another (see Read replicas and Known limitations).
Parameters
pgcli keeps Redis’s parameter surface deliberately small: one flag per knob,
each mapping to a native redis-server option — argv overrides through the
image’s own entrypoint, no redis.conf file is generated, and one container
per instance runs the plain upstream image (no pgcli wrapper, unlike
rustfs). The table lists every knob and its default:
Flag / pg.yaml key |
Default | Redis option | Meaning |
|---|---|---|---|
--version / version |
8 |
— (picks the image tag) | major to install (see Version selection) |
--image / image_tag |
resolved from the version table | — | override the tag verbatim |
--port / port |
auto from redis_start_port (base 6379) |
--port |
host port |
--listen / listen |
0.0.0.0 |
--bind |
bind address (127.0.0.1 to tighten) |
--password / password |
generated, 20 chars | --requirepass |
auth; always required (a replica’s must equal the master’s) |
--maxmemory / maxmemory |
unset (no cap) | --maxmemory |
dataset cap that turns eviction on |
--maxmemory-policy / maxmemory_policy |
allkeys-lru |
--maxmemory-policy |
eviction policy, only under a cap |
--aof / aof |
off | --appendonly yes |
write-ahead log on top of the snapshot |
--appendfsync / appendfsync |
everysec (Redis’s own) |
--appendfsync |
AOF flush strength, only with --aof |
--save / save |
Redis’s default schedule | --save |
RDB snapshot plan; no disables snapshots |
--replica-of / replica_host+replica_port |
unset (master) | --replicaof + --masterauth |
read-only replica of a local master addon (its host/port/password are resolved from the config) |
--replica-of-host / replica_host |
— | --replicaof |
master address for a cross-host replica (pair with --replica-of-port + --password) |
--replica-of-port / replica_port |
— | --replicaof |
master port, paired with --replica-of-host |
--cluster / cluster |
unset (standalone) | --cluster-enabled yes + --masterauth |
native-cluster member: non-empty is the group name — same value means same cluster, and every member of a group shares the first member’s password (--cluster create authenticates all operands with one password, exactly like a replica’s password must equal its master’s). Every member also carries --masterauth so any node Redis later promotes to a follower can authenticate to its master. Mutually exclusive with --replica-of/--replica-of-host — an instance is either a cluster member or a read replica, never both. pgcli installs the node; you assemble the cluster yourself — see Native cluster |
--cluster-replicas / cluster_replicas |
0 (masters-only) |
— (echoed into the suggested --cluster create only) |
per-master followers for the assemble step: the summary counts 3*(1+N) nodes and the printed command ends with --cluster-replicas N. Which members become followers is Redis’s decision at --cluster create, not at install. A group property like the password — the first member sets it, later members inherit it, an explicit mismatch is rejected |
--advertise-host / advertise_host |
unset (127.0.0.1, single-host) |
--cluster-announce-ip/-port/-bus-port |
the address this member announces to peers — a LAN IP, required for a cross-host cluster (Redis’s cluster bus has no NAT/port remapping) |
--data-dir / data_dir |
<base-dir>/addon/redis/<name>/data |
--dir /data (host dir bind-mounted at /data) |
where the data lives |
| — | autostart: false |
— | start on boot (pg autostart enable --redis) |
How the knobs combine:
requirepassis always on — an unauthenticated Redis is not a reachable state, even if--passwordis left empty by hand inpg.yaml.- Persistence defaults to RDB snapshots — Redis’s own
saveschedule, plus a final snapshot on shutdown (--stop-timeout 30gives Redis the time to write it) and a replay fromdump.rdbat start. --aofadds the write-ahead log (--appendonly yes) alongside the snapshot — the durable, no-eviction shape.--appendfsynctunes its flush:always(durability) /everysec(the default) /no(throughput). The AOF lives in anappendonlydir/under the data dir and is replayed before the snapshot, so it is the source of truth when both are on.--save nodisables snapshots — pair it with--aoffor an AOF-only store (the summary then readsaof only); on its own it means a purely in-memory instance that loses its dataset on restart (the install warns).--maxmemoryimplies eviction, defaulting toallkeys-lru— the cap means “cache, evict to stay under”. Pass--maxmemory-policy noevictionfor a hard ceiling (writes fail past the cap instead of evicting), or omit--maxmemoryentirely for no cap. The policy flag requires--maxmemory;--appendfsyncrequires--aof— the CLI rejects those combinations before it pulls or creates anything.--replica-ofmakes a read replica — see Read replicas. The persistence/eviction knobs stay orthogonal: a replica may cap memory or enable AOF exactly like a master.--clustermakes a cluster member, and is mutually exclusive with--replica-of/--replica-of-host— an instance is one role or the other, never both. Members of the same--clustergroup share the first member’s password automatically (a later--clustermember with a different--passwordis rejected before anything is pulled or created), and the per-master follower count set by--cluster-replicas Nis inherited the same way. See Native cluster.
Not every Redis option is a flag, by design: the multi-node control plane
(Sentinel, automatic failover) is out of scope, and so is cluster orchestration
— pgcli installs cluster-enabled members but never runs --cluster create,
--cluster add-node, resharding, or scale-in for you; see Native
cluster and Known limitations. Read
replicas are supported
(Read replicas); it is the orchestration (promoting a
replica automatically when the master dies) that pgcli leaves to you.
And --image is the escape hatch for the
engine itself: pg addon install redis --image docker.io/valkey/valkey:8
runs Valkey (the Redis fork), its major reverse-parsing
as 8.
Version selection
--version picks a major; the table maps each major to a pinned patch tag:
| Major | Default image tag | Select with |
|---|---|---|
| 7 | docker.io/library/redis:7.4.11 |
pg addon install redis --name cache --version 7 |
| 8 | docker.io/library/redis:8.10.2 |
pg addon install redis --name cache (8 is the default) |
Resolution rules:
--version 7(or8) → the table’s tag is stored inimage_tag, the major inversion;--image <tag>→ your tag wins outright; the major is reverse-parsed from it for display (redis:7.2.4→7), or left empty if unparseable (the tag is then shown verbatim);- neither → major
8; - an unknown
--version→ error listing the available majors.
Both majors can run side by side on one host — different tags, different ports
from the same pool. The tags track the latest patch of each major and are
bumped together with the docs/images.md rows.
Install
The install summary prints everything a client needs:
Security model.
listendefaults to0.0.0.0— on Linux (host networking) the port is published on every interface, andrequirepassis the only gate. That is convenient for app-on-another-host setups but means the password is load-bearing: pass--listen 127.0.0.1to keep an instance loopback-only.pg addon listand the logs never print the password; reveal it on demand withpg addon password redis --name cacheorpg addon list --show-password(or readpg.yaml).
Re-running install is idempotent: a running container is left alone, a
stopped one is started. To apply changed ports/listen/password/image, add
--force (the container is recreated from the config; the data dir is
untouched). The persistence knobs (--aof/--appendfsync/--save/
--maxmemory-policy) take effect the same way.
Using Redis
pg redis-cli
pg redis-cli runs Redis’s own client from a throwaway container of the
addon’s image — no host install, no password typing:
The local target is --name <addon> or, when omitted, the first redis addon by
name (with several installed, the chosen target is announced on stderr).
pgcli’s own flags are parsed only before the command word; everything after is
forwarded to redis-cli verbatim — negative indexes and option flags (via --)
work that way.
Reaching a remote endpoint — --host/--port override where the client
connects, without registering the remote as an addon:
Use these instead of redis-cli’s own -h/-p: those sit after the command
word and would be read as command arguments (ping -h 10.0.0.7 errors), while
pgcli injects the connection -h/-p itself. REDISCLI_AUTH (redis-cli’s
native env var), if set, overrides the stored password in every mode:
Application connections
Any Redis client works; the DSN printed at install has the shape
redis://:<password>@<host>:<port>/0. For TLS you would need a stunnel or
redis’ own TLS build in front — see Known limitations.
Read replicas
--replica-of turns a new instance into a read-only replica of an existing
master addon: it streams the master’s writes continuously and serves reads,
while writes to it are rejected with READONLY. This is read scaling and data
redundancy, not high availability — pgcli does not run Sentinel, so a dead
master is not promoted automatically (see Known
limitations).
A local --replica-of resolves everything from the config: the replica adopts
the master’s version (cross-major replication is unsupported — a mismatch is a
fast error), its password, and the endpoint 127.0.0.1:<master-port>. That one
password then serves both requirepass (the replica’s own clients auth with it)
and --masterauth (the replica authenticating to the master), so
--password/--maxmemory/--aof etc. stay exactly as on a master. The stored
replica_host/replica_port mean pg addon start rebuilds the same
--replicaof argv even if the master is gone from the config.
Cross-host masters use the explicit form — pgcli cannot look up a remote master’s password, so you supply it (it must equal the master’s):
pg addon list and the install summary show a Role: line — master, or
replica of <host>:<port> (read-only) — and pg redis-cli --name cache-ro info replication reports role:replica with master_link_status:up once the
(initial, RDB-carrying) sync completes. Send reads to the replica and writes to
the master, e.g. with the DSNs:
To promote a replica to an independent master at runtime (the manual failover pgcli deliberately leaves to you), clear its replication and then, if you want it to persist across restarts, drop its role from the config:
Native cluster
--cluster turns instances into members of a Redis native cluster —
sharding by slot (16384 of them), not a read replica of one master. This is the
only multi-node topology pgcli supports; Sentinel is not.
pgcli’s role is deliberately narrow: it installs cluster-enabled nodes, it does
not assemble them. That is Redis’s own two-step flow, not a pgcli invention —
a cluster is cluster-enabled yes servers plus one redis-cli --cluster create
command run against all of them at once. Splitting those two steps means pgcli
never has to guess “is this the last member I’m waiting for, should I form the
group now” (an etcd-style auto-bootstrap state machine); assembling is always
your decision, made once, the same way you’d do it against bare redis-server.
Members of one group share the first member’s password automatically:
--cluster create authenticates every operand node with a single -a (here,
via the one REDISCLI_AUTH pg redis-cli injects), so one password has to reach
the whole group — the same rule that makes a read
replica’s password equal its master’s. A later --cluster
member installed with a different --password is rejected up front, before
anything is pulled or created; omit --password and the group’s own value is
inherited. Every member also emits --masterauth (the same shared password) in
its argv: at --cluster create time Redis may promote any node to a follower,
and a follower needs --masterauth to authenticate to its master — without it
it spins forever in a reconnect storm (master_link_status:down, CPU pinned).
--cluster and --replica-of/--replica-of-host are mutually exclusive on one
install — an instance is either a cluster member or a read replica, never both.
The three addr:port operands are every member’s PeerAddr:port — that’s
127.0.0.1:<port> for a single-host cluster, or each member’s
--advertise-host:<port> for a cross-host one (see below). The install summary
already prints this exact command, built from whatever --cluster app members
are configured at that moment (pg redis-cli --name n3 -- --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 --cluster-replicas 0), so it is
copy-pasteable rather than something to reconstruct by hand:
--cluster-replicas 0 is masters-only — every member holds its own slice of
the slots and none of them has a follower. To give each master a follower (so
one master’s slot range survives that node dying), pass --cluster-replicas N
on the first install and add enough members: each master needs itself plus its
N followers, so the total is 3*(1+N) — for N=1 that’s 6 members:
Which members Redis elects as masters vs followers is its decision at
--cluster create (it balances followers across hosts where it can), so the
pgcli instance names are deliberately role-neutral, and a follower re-links
from its own nodes.conf after a restart — role and masterauth pairing
survive, no re-create. The install summary switches from “3 masters
configured” to counting nodes (“6 nodes configured”) once followers are in
play, and echoes the group’s --cluster-replicas N into the suggested command;
it never calls a follower-set node a master.
After assembling
A pg redis-cli pointed at a cluster member is automatically a
cluster-aware client: pgcli injects redis-cli’s own -c flag so a get/set
that lands on the “wrong” node follows its MOVED redirect to the node that
actually owns the slot — no -c to remember, and it doesn’t matter which
--name you happen to point at:
The last line is the exception, not a rule to remember: when the command is
redis-cli’s own --cluster <subcommand> (which dials the nodes named in its
operands itself), pgcli suppresses the -c injection — it’s the assembly/admin
surface from step 2, not a data command.
The auto--c above depends on pgcli reading a local cluster member from
pg.yaml (the --name path). When you reach the cluster remotely with
--host/--port from a machine that has no such addon configured, pgcli has no
way to know the endpoint is a cluster node, so it injects nothing — and an
unredirected get/set that lands on the wrong node just returns MOVED ....
Forward redis-cli’s -c yourself (it is redis-cli’s own flag, so it goes after
the -- passthrough, before the command word):
-c matters for writes as much as reads: without it a set aimed at a
non-owner returns MOVED and is not applied, so the key never exists. Put
-c on the write, and any single node serves the whole keyspace.
You can also skip the env var and pass the password with redis-cli’s own -a
— it is a connection option too, so it goes in the same passthrough slot,
before the command word:
Two caveats distinguish it from REDISCLI_AUTH: redis-cli prints a security
warning on every invocation (-a/-u on a command line is visible to other
users via ps), and placing the flag after the command word (get foo -a …)
fails with “wrong number of arguments” — it would be passed as a command
argument. For anything beyond a one-off command, prefer the env var; they are
otherwise equivalent for authentication.
If your clients cannot be made cluster-aware at all, put a
Predixy proxy in front of the cluster: it follows the redirects
itself and exposes one plain redis:// endpoint.
The cluster bus is a second port per member: always client port + 10000
(Redis’s own fixed rule, e.g. 6379→16379) — it is not drawn from
redis_start_port, not stored in pg.yaml, and pg addon list shows it
alongside the token:
Topology is self-healing: each node’s --cluster-config-file nodes.conf lives
in its own --dir /data bind mount, so pg addon stop/start (or a host
reboot) brings the whole cluster back with no re-create step — same
start-only model as autostart for everything else here.
Two commands check the cluster’s health directly, both through the passthrough
(yes, the -c one is a data-path client flag, so it goes after the --):
Cross-host members: --advertise-host
A single-host cluster dials peers at 127.0.0.1 and works with no extra flags.
Redis’s cluster bus does not support NAT or port remapping, so members on
different hosts must announce the address peers can actually reach:
--advertise-host adds --cluster-announce-ip/-port/-bus-port to the node’s
argv, and PeerAddr (used to build the --cluster create operands above)
switches from 127.0.0.1 to that address. Two things to get right on your side:
open each member’s bus port (client+10000 — 16379 at the default 6379)
on the firewall between hosts — not just the client port — and every member of
the group must carry the flag, not only the ones being joined later.
The --password "$PW" on every host after the first is not decoration: the
shared-password inheritance only works within one pg.yaml — on a single
host, later members automatically adopt the group’s password; across hosts, each
host has its own config file and generates its own secret. A cross-host cluster
must therefore pin one password explicitly on every host, read from the first
member with pg addon password.
(--cluster create authenticates every operand with that one password, so a
mismatch surfaces as a per-node auth error.)
Status: cross-host clusters are verified end-to-end across three hosts (
--advertise-host→--cluster-announce-*, cross-host--cluster create, MOVED redirects between hosts, and self-heal after a stop/start on each host). The single-host flow is additionally covered by the automated e2e (test/addon/test_redis_cluster.sh), which tests both shapes there: a three-master group and a 3-master × 1-follower group (which is how the missing---masterauthreconnect storm was caught). The multi-host case is so far a manual procedure, not yet in the suite.
Ports
Each instance takes one port from Redis’s own pool, redis_start_port
(default 6379) — a separate cursor from the object stores’ shared
minio_start_port pool, so a redis, a minio and a rustfs instance never
collide:
--port fixes an instance to a specific port; auto-assignment skips anything
already explicit or live on the host. A native-cluster
member is no different here — the cluster bus port is always Redis’s own
client+10000, derived, never drawn from the pool.
Configuration
Instances live under the top-level addons.redis map in pg.yaml:
Edits to listen, port, password, maxmemory/maxmemory_policy,
aof/appendfsync/save, replica_host/replica_port,
cluster/cluster_replicas/advertise_host, image_tag/version, or
data_dir take effect after the next pg addon install redis --name cache --force (or via the matching flags). The replica and cluster roles are normally
set by the --replica-of* / --cluster (incl. --cluster-replicas) /
--advertise-host flags rather than hand-edited — see Read
replicas and Native cluster.
List
pg addon list never prints the password. To reveal it deliberately, add
--show-password (it appends a Password: and a ready-to-paste Raw DSN:
line to each Redis instance — and a Root password: line to each object store),
or print just one instance’s value bare on stdout for scripting:
pg addon password takes the same --name as the other subcommands (defaulting
to the addon’s own name) and a --file that writes the value mode 0600 instead
of to the terminal — prefer --file so the secret stays out of shell history
and scrollback.
Start and stop
stop sends a graceful SIGTERM — with --stop-timeout 30 Redis writes its
final RDB before exiting. start only starts an existing container (and
self-heals an improper state by recreating it from the config).
Auto-start on Boot
Containers carry a --restart unless-stopped policy (crashes, not reboots). To
bring instances up after a host reboot:
Redis autostart is start-only (install the instance first) and replays the
RDB snapshot from its data dir, so the dataset survives the reboot. Redis is
independent of the PostgreSQL stack, so boot-time pg start --autostart starts
it last.
Remove
Without --clean-data the host data dir (and its dump.rdb) stays in place —
reinstalling the same name revives the dataset. --clean-data deletes it,
falling back through podman unshare when rootless podman’s subordinate uid
makes Redis’s files undeletable by the host user. An empty per-instance parent
directory is pruned; a custom data_dir parent is never touched.
Logs
Troubleshooting
NOAUTH Authentication requiredfrom a raw client — expected: every instance has arequirepass. Get it withpg addon password redis --name cache, or usepg redis-cli, which injects it.maxmemoryevicting more than you wanted — under a--maxmemorycap the default policy isallkeys-lru(evict any key to stay under: a cache, not a hard store). Pass--maxmemory-policy noevictionto make the cap a hard ceiling instead (writes fail past it rather than evicting), or omit--maxmemoryfor no cap at all.- AOF not replaying the newest writes —
--appendfsync everysec(the default) can lose the last second of writes on a hard crash; use--appendfsync alwayswhen you cannot afford that. A gracefulpg stopalways flushes, so this only bites on an unclean kill. - Image not found at install — both majors are plain
docker.io/library/redisimages pulled on demand; on an air-gapped host,podman loadthe tar first (the catalog and export steps live indocs/images.mdin the repo) and the pull is skipped. - A hand-edited
versionthat is not in the table —installerrors and suggests--imagerather than silently rewriting your value. - Port already live — auto-assignment scans host listeners and skips
them; pin
--portwhen you need a stable number. - A replica rejects writes (
READONLY) — that is the role, not a fault: send writes to the master. To make it an independent master, see Read replicas. - A freshly-created replica briefly shows
master_link_status:down— normal during the first (RDB-carrying) full sync, and redis 8 reloads the link once more when the snapshot transfers over its rdbchannel. Watchinfo replication; it settles atupandrole:replica.
Known limitations
- No automatic failover — read replicas are supported
(Read replicas) and native-cluster sharding is supported
(Native cluster), but the HA control plane is not:
Sentinel is out of scope, and so is cluster orchestration — pgcli
installs cluster members but never assembles, rescales, or rebalances them.
A replica gives read scaling and a redundant copy of the data, not a
self-healing pair — when the master dies you promote a replica yourself
(
replicaof no one, above). A single instance with no replica is a single point: it can be made durable on disk with--aof, but it cannot fail over to another node. - Native cluster: assembly and reshaping stay manual —
--clusterinstalls cluster-enabled nodes and--cluster-replicas N(echoed into the suggested command) lets each master have followers, so a follower auto-promotes when its master dies. What pgcli does NOT do is run--cluster create/add-node/ resharding/del-nodefor you, and removing a member (pg addon remove) is a plain delete with no slot reassignment — shrink or reshape the cluster at the Redis layer first (move its slots off, forget the node), then remove it. Also note a masters-only group (--cluster-replicas 0) has no follower to promote: there a dead master takes its slot range down with it until you intervene. - Cross-host cluster (
--advertise-host) is manual, not in the e2e suite — the flag, the--cluster-announce-*argv it produces, and the firewall/bus-port rules are implemented, unit-tested, and verified by hand across three hosts (assembly, cross-host MOVED redirects, and self-heal all pass). The single-host flow (masters-only and 3-master × 1-follower) has an automated e2e; cross-host-with-followers has no automated e2e but relies on the same per-member--masterauththat makes followers link up on one host. See the status note in Native cluster. - No TLS — Redis 7’s native TLS exists in some builds but is not wired
here; treat the network path as trusted and rely on
requirepass, or keep the instance loopback-only (--listen 127.0.0.1). For a cross-host cluster that means the cluster bus traffic between members is unencrypted as well. - macOS is code-complete, untested — the bridge path (published ports,
host.containers.internalforpg redis-cli) mirrors the object stores; it has not yet been exercised on a Mac. - Default persistence is RDB granularity — a crash loses writes since the
last snapshot. Enable
--aofwhen that gap matters (ateverysecyou still lose at most one second;alwayscloses it); accept snapshots-only for cache/session data, or move durable data to PostgreSQL.