Run Redis with Docker
Verified on Redis 8.10.2 (
redis:8.10.2-alpine), Docker Desktop on an Apple-silicon Mac, ioredis 6.0.0. Container names below use aredis-b1-prefix in the runs; the lesson text usesredisso you can copy it.
Docker is the fastest path to a local Redis
Section titled “Docker is the fastest path to a local Redis”You do not need to install Redis on your machine. A single docker run command pulls the official image and starts a ready-to-use server in seconds.
The default command explained
Section titled “The default command explained”docker run -d --name redis -p 6379:6379 redis:8-alpine| Flag | What it does |
|---|---|
-d | Run in detached (background) mode so your terminal stays free |
--name redis | Give the container a stable name you can reference in later commands |
-p 6379:6379 | Publish port 6379 from the container to your host on the same port |
redis:8-alpine | Use the official Redis 8 image built on Alpine Linux — small and fast |
Floating tag or pinned tag?
Section titled “Floating tag or pinned tag?”redis:8-alpine is a moving tag; redis:8.10.2-alpine is one release. On the day this lesson was verified both resolved to the very same image:
$ docker image ls redisredis:8-alpine 675f7644831dredis:8.10.2-alpine 675f7644831dSame image ID, so today they are identical. Next month 8-alpine will not be. Use the floating tag for a throwaway lab, and the exact tag (or a digest) in anything you want to reproduce — a bug report, a CI job, a compose file. This course pins 8.10.2 in its tests.
Reading the startup banner
Section titled “Reading the startup banner”Check that the server actually came up before you blame your client:
docker logs redisThese are the lines that matter (the Redis 8 image also prints one block per built-in module — Bloom, search, time series, JSON — between the config and the Server initialized line):
1:C 30 Sep 2026 05:42:55.041 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1:C 30 Sep 2026 05:42:55.041 * Redis version=8.10.2, bits=64, commit=00000000, modified=1, pid=1, just started1:M 30 Sep 2026 05:42:55.042 * Running mode=standalone, port=6379.1:M 30 Sep 2026 05:42:55.048 * Server initialized1:M 30 Sep 2026 05:42:55.048 * Ready to accept connections tcpReady to accept connections tcp is your green light, and Redis version= tells you which server you really got.
Connecting with redis-cli
Section titled “Connecting with redis-cli”Once the container is running, open an interactive CLI session inside it:
docker exec -it redis redis-clidocker exec runs a command inside a running container. -it allocates an interactive TTY so you get the familiar 127.0.0.1:6379> prompt. From there you can run any Redis command.
The official image starts the server with protected-mode off (proven on the 8.10.2 image), because inside a container everything reaches Redis through Docker’s bridge, not loopback, and protected mode would refuse it. That makes the port publishing the only fence you have:
127.0.0.1:6379> CONFIG GET protected-mode1) "protected-mode"2) "no"Adding persistence with a named volume
Section titled “Adding persistence with a named volume”Do not take “the data disappears when you remove the container” on faith — here is what actually happens. On a container without -v, the image’s default save 3600 1 300 100 60 10000 snapshots into the container’s own writable layer (/data/dump.rdb):
$ docker run -d --name redis-b1-nov redis:8.10.2-alpine$ docker exec redis-b1-nov redis-cli SET greeting helloOK$ docker stop redis-b1-nov && docker start redis-b1-nov # same container$ docker exec redis-b1-nov redis-cli GET greetinghello # survived: stop/start keeps the layer$ docker rm -f redis-b1-nov && docker run -d --name redis-b1-nov redis:8.10.2-alpine$ docker exec redis-b1-nov redis-cli GET greeting # (nil): remove + recreate = goneSo docker stop/start is safe even without a volume; docker rm is what destroys the data. Attach a named volume to make the data outlive the container:
docker run -d --name redis -p 6379:6379 -v redisdata:/data redis:8-alpineSame experiment with -v redisdata:/data: SET greeting hello, docker stop, docker start → DBSIZE is 1; docker exec redis ls -la /data shows a 110-byte dump.rdb; then docker rm -f and a brand-new container on the same volume answers GET greeting → hello. Docker creates the redisdata volume automatically, and /data is where the official image writes its snapshot file.
Note the snapshot only exists because docker stop sends SIGTERM and Redis saves before exit when a save rule is configured. A docker kill (SIGKILL) or a crash can lose everything since the last snapshot — the Persistence module measures exactly that.
Container lifecycle
Section titled “Container lifecycle”# Stop the running container (Redis shuts down cleanly)docker stop redis
# Start it again — data in the named volume is preserveddocker start redis
# Remove the container entirely (volume survives unless you also delete it)docker rm redisViewing logs
Section titled “Viewing logs”# Print all logs produced so fardocker logs redis
# Follow logs in real time (Ctrl-C to stop)docker logs -f redisdocker run -d --name redis -p 6379:6379 redis:8-alpine
docker exec -it redis redis-cliA password, and what a client sees without one
Section titled “A password, and what a client sees without one”Start the server with --requirepass (placeholder — use your own secret, never commit it):
docker run -d --name redis -p 127.0.0.1:6379:6379 redis:8.10.2-alpine redis-server --requirepass '<your-password>'Real replies from that container:
$ docker exec redis redis-cli PINGNOAUTH Authentication required.$ docker exec redis redis-cli -a '<your-password>' PINGWarning: Using a password with '-a' or '-u' option on the command line interface may not be safe.PONG-a on the command line leaks into ps and shell history, hence the warning; prefer the REDISCLI_AUTH environment variable. From application code the error is different, because ioredis 6 opens the connection with HELLO first:
import { Redis } from 'ioredis';
const bad = new Redis('redis://127.0.0.1:6379', { maxRetriesPerRequest: 0 });bad.on('error', () => {});// "NOAUTH HELLO must be called with the client already authenticated, otherwise the// HELLO <proto> AUTH <user> <pass> option can be used ..."console.log('no password ->', await bad.get('k').catch((e: Error) => e.message));bad.disconnect();
const ok = new Redis({ host: '127.0.0.1', port: 6379, password: process.env.REDIS_PASSWORD });console.log('with password ->', await ok.ping()); // PONGok.disconnect();That is the exact message we got (measured on ioredis 6.0.0 against Redis 8.10.2). If you ever see NOAUTH HELLO must be called… in your logs, you forgot password, not a protocol problem.
The same thing as a compose file
Section titled “The same thing as a compose file”For anything you keep around, write it down. This file was run with docker compose up -d, went healthy after the first health check, created appendonlydir inside the volume (because of --appendonly yes), and was torn down with docker compose down -v:
services: redis: image: redis:8.10.2-alpine command: ["redis-server", "--requirepass", "<your-password>", "--appendonly", "yes"] ports: - "127.0.0.1:6379:6379" volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "-a", "<your-password>", "--no-auth-warning", "ping"] interval: 5s timeout: 3s retries: 5volumes: redisdata:Reading AI-generated code
Section titled “Reading AI-generated code”An assistant is asked for “a Redis with data that survives, for staging” and answers:
🐛 Spot the bug
docker run -d --name redis -p 6379:6379 redis:8-alpine# data is persisted: Redis saves to /data/dump.rdbdocker rm -f redis && docker run -d --name redis -p 6379:6379 redis:8-alpinedocker exec redis redis-cli GET greetingShow the answer
Two bugs. First, the comment is only half true: the snapshot goes to /data/dump.rdb, but that is inside the container’s writable layer, so docker rm -f deletes it. We ran this sequence and the final GET greeting returned (nil). The fix is a volume (-v redisdata:/data) — with it the same sequence answered hello. Second, -p 6379:6379 publishes on all host interfaces of a server with protected-mode no and no password; on a staging box that is an open Redis. Publish on loopback (-p 127.0.0.1:6379:6379) or add --requirepass and a firewall.
docker run -d --name redis -p 127.0.0.1:6379:6379 -v redisdata:/data redis:8.10.2-alpine redis-server --requirepass '<your-password>'Mental Model
Section titled “Mental Model”A container is a disposable process with a scratch disk. The scratch disk survives stop and start but dies with rm. A volume is a separate disk you plug in, and the published port is a hole in the wall — where you drill it (127.0.0.1: or all interfaces) decides who can reach Redis. Pin the tag when you need the same server tomorrow.
Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
| Run without mounting a volume | Fastest to start, good for throwaway experiments | Data disappears the moment you docker rm the container (it survives only stop/start) |
Attach a named volume (-v redisdata:/data) | Data survives container restart/recreate | You own the volume lifecycle yourself (backup, cleanup) — more moving parts |
Floating tag (8-alpine) vs pinned (8.10.2-alpine) | Floating gets patch releases for free | Floating changes under you; pinned is reproducible but you must bump it |
Run the container with docker run defaults | Convenient, fine for local dev | No password, no TLS, protected-mode no — not something you deploy as-is |
Common Misconceptions
Section titled “Common Misconceptions”- “
docker stoploses my data.” Not without a volume either: the container layer keepsdump.rdbuntil youdocker rm. - “Redis has protected mode, so publishing the port is safe.” The official image turns
protected-modeoff. The port mapping is your only guard. - “
-a passwordis fine in scripts.” It prints a warning for a reason; it shows up in the process list. UseREDISCLI_AUTH. - “
redis:8andredis:8.10.2are the same.” Only on the day you pulled.
Common mistakes
Section titled “Common mistakes”- Forgetting to attach a volume, then wondering why data disappeared — without mounting
/datato a named volume, all data lives only in the container’s writable layer.docker rmdestroys it permanently. - Publishing
-p 6379:6379to the internet with no firewall — the official image ships with no authentication configured. If it is bound to0.0.0.0with no network filtering, anyone can connect and run commands. - Running the container without
--requirepass— fine for local experimentation only. Set a password or use ACLs before anything resembling real use.