Skip to content

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 a redis-b1- prefix in the runs; the lesson text uses redis so 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.

scripts/intro-run-with-docker-run.sh
docker run -d --name redis -p 6379:6379 redis:8-alpine
FlagWhat it does
-dRun in detached (background) mode so your terminal stays free
--name redisGive the container a stable name you can reference in later commands
-p 6379:6379Publish port 6379 from the container to your host on the same port
redis:8-alpineUse the official Redis 8 image built on Alpine Linux — small and fast

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 redis
redis:8-alpine 675f7644831d
redis:8.10.2-alpine 675f7644831d

Same 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.

Check that the server actually came up before you blame your client:

scripts/intro-run-with-docker-logs.sh
docker logs redis

These 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 oO0OoO0OoO0Oo
1:C 30 Sep 2026 05:42:55.041 * Redis version=8.10.2, bits=64, commit=00000000, modified=1, pid=1, just started
1:M 30 Sep 2026 05:42:55.042 * Running mode=standalone, port=6379.
1:M 30 Sep 2026 05:42:55.048 * Server initialized
1:M 30 Sep 2026 05:42:55.048 * Ready to accept connections tcp

Ready to accept connections tcp is your green light, and Redis version= tells you which server you really got.

Once the container is running, open an interactive CLI session inside it:

scripts/intro-run-with-docker-exec.sh
docker exec -it redis redis-cli

docker 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:

cli/intro-run-with-docker-1.redis
127.0.0.1:6379> CONFIG GET protected-mode
1) "protected-mode"
2) "no"

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 hello
OK
$ docker stop redis-b1-nov && docker start redis-b1-nov # same container
$ docker exec redis-b1-nov redis-cli GET greeting
hello # 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 = gone

So 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:

scripts/intro-run-with-docker-volume.sh
docker run -d --name redis -p 6379:6379 -v redisdata:/data redis:8-alpine

Same 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.

scripts/intro-run-with-docker-lifecycle.sh
# Stop the running container (Redis shuts down cleanly)
docker stop redis
# Start it again — data in the named volume is preserved
docker start redis
# Remove the container entirely (volume survives unless you also delete it)
docker rm redis
scripts/intro-run-with-docker-follow.sh
# Print all logs produced so far
docker logs redis
# Follow logs in real time (Ctrl-C to stop)
docker logs -f redis
docker run -d --name redis -p 6379:6379 redis:8-alpine
docker exec -it redis redis-cli

A 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):

scripts/intro-run-with-docker-auth.sh
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 PING
NOAUTH Authentication required.
$ docker exec redis redis-cli -a '<your-password>' PING
Warning: 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:

src/intro-run-with-docker-auth.ts
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()); // PONG
ok.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.

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: 5
volumes:
redisdata:

An assistant is asked for “a Redis with data that survives, for staging” and answers:

🐛 Spot the bug

Terminal window
docker run -d --name redis -p 6379:6379 redis:8-alpine
# data is persisted: Redis saves to /data/dump.rdb
docker rm -f redis && docker run -d --name redis -p 6379:6379 redis:8-alpine
docker exec redis redis-cli GET greeting
Show 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.

scripts/intro-run-with-docker-fixed.sh
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>'

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.

ChoiceBenefitCost
Run without mounting a volumeFastest to start, good for throwaway experimentsData disappears the moment you docker rm the container (it survives only stop/start)
Attach a named volume (-v redisdata:/data)Data survives container restart/recreateYou 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 freeFloating changes under you; pinned is reproducible but you must bump it
Run the container with docker run defaultsConvenient, fine for local devNo password, no TLS, protected-mode no — not something you deploy as-is
  • “docker stop loses my data.” Not without a volume either: the container layer keeps dump.rdb until you docker rm.
  • “Redis has protected mode, so publishing the port is safe.” The official image turns protected-mode off. The port mapping is your only guard.
  • “-a password is fine in scripts.” It prints a warning for a reason; it shows up in the process list. Use REDISCLI_AUTH.
  • “redis:8 and redis:8.10.2 are the same.” Only on the day you pulled.
  • Forgetting to attach a volume, then wondering why data disappeared — without mounting /data to a named volume, all data lives only in the container’s writable layer. docker rm destroys it permanently.
  • Publishing -p 6379:6379 to the internet with no firewall — the official image ships with no authentication configured. If it is bound to 0.0.0.0 with 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.
What does the -d flag do in docker run -d?
Which flag publishes the Redis port so your host can connect to it?
How do you open an interactive redis-cli session inside the running container?
You run a container without -v, SET a key, then docker stop and docker start it. Is the key still there?
The official redis image runs with protected-mode no. What follows?
ioredis 6 against a password-protected server, without a password, rejects with which error?