Module 1: Redis Fundamentals

Installing Redis and redis-cli

Overview

This capsule is where Redis goes from abstract concept to concrete tool running on your machine. In 30 seconds you'll have Redis working with Docker, connect with redis-cli, and run your first commands. It's deliberately hands-on: every concept is experienced in the CLI before moving on.

There are three ways to install Redis: Docker (recommended, works identically on any OS), Homebrew (macOS), and apt (Linux). We'll cover all three, but the rest of the guide assumes Docker because it eliminates all operating-system variability. If you get stuck for 20 minutes with native installation problems, Docker solves everything in 10 seconds.

After installing, we'll spend most of the capsule on redis-cli — Redis's most important tool for interaction and debugging. It isn't just a shell for running commands: it's the sandbox where you'll learn every data type in the module, where you'll debug production problems, and where you'll explore someone else's Redis databases. Mastering it now pays dividends in every capsule that follows.


Installing with Docker (recommended)

Bringing up Redis in a container

If you have Docker installed and running (verify with docker ps — it should respond without an error), a single command brings up Redis:

docker run -d --name redis-dev -p 6379:6379 redis:7

Breaking the command down:

  • docker run — creates and starts a container
  • -d — detached mode (runs in the background, doesn't block your terminal)
  • --name redis-dev — names the container redis-dev (easier than the hash ID)
  • -p 6379:6379 — maps port 6379 in the container to 6379 on your machine (Redis uses 6379 by default)
  • redis:7 — the official Redis image, version 7 (the latest stable)

Expected output:

Unable to find image 'redis:7' locally
7: Pulling from library/redis
...
Status: Downloaded newer image for redis:7
a1b2c3d4e5f6...

The first time it takes 10-30 seconds (downloading the image). After that it's instant (the image is already cached).

Verifying that Redis responds

docker ps

Expected output:

CONTAINER ID   IMAGE      COMMAND                  STATUS         PORTS                    NAMES
a1b2c3d4e5f6   redis:7    "docker-entrypoint.s…"   Up 5 seconds   0.0.0.0:6379->6379/tcp   redis-dev

If you see redis-dev in the Up state, Redis is running. Port 6379 is accessible from your machine.

Useful container commands

# Stop Redis (the data is lost if you didn't configure persistence)
docker stop redis-dev

# Restart Redis
docker start redis-dev

# View logs (useful for debugging)
docker logs redis-dev

# Delete the container entirely (frees up the redis-dev name)
docker rm redis-dev

# View resource usage
docker stats redis-dev

Basic persistence (optional for this module)

By default, Redis in Docker loses the data when you stop the container. For this module it doesn't matter — you'll be experimenting, not storing critical data. If you want persistence now:

docker run -d --name redis-dev \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7 redis-server --appendonly yes

-v redis-data:/data creates a Docker volume that persists the data. --appendonly yes enables AOF (Append-Only File), a persistence strategy. That's enough for development.


Native installation (alternative)

If you'd rather not use Docker, here are the alternatives. They work the same in the end, but they require more care with versions and configuration.

macOS with Homebrew

# Install
brew install redis

# Start as a service (runs in the background)
brew services start redis

# Or start it manually
redis-server

# Stop the service
brew services stop redis

Ubuntu / Debian

# Install
sudo apt update
sudo apt install redis-server

# Start as a service
sudo systemctl start redis-server
sudo systemctl enable redis-server  # Auto-start on boot

# Stop
sudo systemctl stop redis-server

Windows

Native Redis doesn't officially support Windows. The real options:

  1. WSL2 (Windows Subsystem for Linux): Install Ubuntu in WSL2, then follow the Ubuntu instructions above
  2. Docker Desktop: The cleanest way, identical to macOS/Linux
  3. Memurai (https://www.memurai.com/): an unofficial Redis port for native Windows

Recommendation: Use Docker Desktop. It saves you problems and replicates production behavior (where Redis almost always runs on Linux).


Connecting with redis-cli

redis-cli is the client tool that ships with Redis. It connects you to the server so you can run commands interactively.

If you installed Redis with Docker

redis-cli lives inside the container. Three ways to use it:

Option A: Run redis-cli inside the container

docker exec -it redis-dev redis-cli

This opens a redis-cli shell connected to the container's Redis.

Option B: Install redis-cli on your machine (more convenient long-term)

# macOS
brew install redis  # This installs the client without starting the server

# Ubuntu/Debian
sudo apt install redis-tools

Then you connect to the container's Redis from your machine:

redis-cli
# or explicitly:
redis-cli -h localhost -p 6379

Option C: Use the container's client without entering a shell

docker exec -it redis-dev redis-cli ping
# PONG

Useful for quick commands without entering an interactive session.

If you installed Redis natively

redis-cli is already available in your PATH:

redis-cli

The first command: PING

Once inside redis-cli, the prompt changes to 127.0.0.1:6379>. Type:

127.0.0.1:6379> PING
PONG

If you see PONG, Redis is working and connected. It's the standard way to verify that a Redis server responds — you'll use it in your API's health checks in later modules.


Fundamental commands

Let's walk through the essential commands you need to manipulate any data in Redis. The general syntax is:

COMMAND key [arguments]

Keys are strings. They can be any text: user:1, cache:product:42, rate_limit:ip:192.168.1.1. By convention, a colon : is used as a separator to emulate hierarchy (Redis has no real namespaces — user:1 is just a string that looks hierarchical).

SET and GET — Redis's "Hello World"

127.0.0.1:6379> SET greeting "Hello Redis"
OK

127.0.0.1:6379> GET greeting
"Hello Redis"

SET stores a value. GET reads it. As simple as it gets.

Overwriting a value

127.0.0.1:6379> SET greeting "Hello World"
OK

127.0.0.1:6379> GET greeting
"Hello World"

SET always overwrites the previous value. There's no error and no warning — the previous value is replaced.

If the key doesn't exist

127.0.0.1:6379> GET nonexistent
(nil)

(nil) means "it doesn't exist." It isn't an error — it's Redis's way of saying "I have nothing for that key."

DEL — deleting keys

127.0.0.1:6379> SET temp "this value is going away"
OK

127.0.0.1:6379> DEL temp
(integer) 1

127.0.0.1:6379> GET temp
(nil)

DEL returns (integer) 1 if it deleted the key, 0 if the key didn't exist. You can delete multiple keys at once:

127.0.0.1:6379> SET key1 "a"
OK
127.0.0.1:6379> SET key2 "b"
OK
127.0.0.1:6379> DEL key1 key2 key3
(integer) 2

It deleted key1 and key2 (they existed), but not key3 (it didn't). That's why it returned 2.

EXISTS — checking existence without reading

127.0.0.1:6379> SET user:42 "Alex"
OK

127.0.0.1:6379> EXISTS user:42
(integer) 1

127.0.0.1:6379> EXISTS user:99
(integer) 0

1 = it exists, 0 = it doesn't. More efficient than GET when all you want to know is whether the key exists (you don't need the value).

KEYS — listing keys (carefully)

127.0.0.1:6379> SET user:1 "Alex"
OK
127.0.0.1:6379> SET user:2 "Maria"
OK
127.0.0.1:6379> SET product:1 "Laptop"
OK

127.0.0.1:6379> KEYS *
1) "user:1"
2) "user:2"
3) "product:1"

127.0.0.1:6379> KEYS user:*
1) "user:1"
2) "user:2"

KEYS * lists ALL the keys. KEYS user:* lists the ones starting with user:.

⚠️ CRITICAL: KEYS blocks Redis while it scans the database. In production with millions of keys, it can freeze Redis for seconds. Never use KEYS in production — development only. The professional alternative is SCAN (we don't cover it here, but it's in the docs).

TYPE — what data type a key holds

127.0.0.1:6379> SET name "Alex"
OK
127.0.0.1:6379> TYPE name
string

127.0.0.1:6379> HSET user:1 name "Alex" age 30
(integer) 2
127.0.0.1:6379> TYPE user:1
hash

Useful when you're exploring someone else's Redis database and you don't know what type each key is.


Expiration: TTL and EXPIRE

One of Redis's most useful features is that keys can expire automatically. This is the foundation of caching: you store a value with a TTL (Time To Live), and Redis deletes it on its own when it expires.

EXPIRE — adding a TTL to an existing key

127.0.0.1:6379> SET session:abc123 "user_id=42"
OK

127.0.0.1:6379> EXPIRE session:abc123 60
(integer) 1

EXPIRE key seconds — the key expires in 60 seconds. It returns 1 if the operation worked, 0 if the key doesn't exist.

TTL — how much time a key has left

127.0.0.1:6379> TTL session:abc123
(integer) 58

127.0.0.1:6379> TTL session:abc123
(integer) 45

It returns the remaining seconds. If the key has no TTL: -1. If the key doesn't exist: -2.

127.0.0.1:6379> SET permanent "doesn't expire"
OK
127.0.0.1:6379> TTL permanent
(integer) -1

127.0.0.1:6379> TTL nonexistent
(integer) -2

SET with a TTL in a single command

127.0.0.1:6379> SET cache:product:42 "Laptop" EX 300
OK

127.0.0.1:6379> TTL cache:product:42
(integer) 298

EX 300 adds a 300-second TTL to the SET. It's the idiomatic way to cache with expiration. More efficient than a separate SET + EXPIRE (one atomic operation instead of two).

PERSIST — removing the TTL

127.0.0.1:6379> SET cache:item "value" EX 300
OK

127.0.0.1:6379> PERSIST cache:item
(integer) 1

127.0.0.1:6379> TTL cache:item
(integer) -1

The key becomes permanent again (no expiration).

Waiting for it to expire

127.0.0.1:6379> SET temp "I'm going to disappear" EX 5
OK
127.0.0.1:6379> GET temp
"I'm going to disappear"

# Wait 5 seconds...

127.0.0.1:6379> GET temp
(nil)

Redis deletes the key automatically when it expires.


Professional exploration commands

redis-cli isn't just for SET/GET. It's Redis's debugging and administration tool. You'll use these commands in production all the time.

DBSIZE — how many keys there are

127.0.0.1:6379> DBSIZE
(integer) 4

It returns the total number of keys in the current database. Ultra fast (it doesn't scan, it reads an internal counter).

INFO — server metrics

127.0.0.1:6379> INFO
# Server
redis_version:7.2.4
redis_git_sha1:00000000
process_id:1
...

# Clients
connected_clients:1
...

# Memory
used_memory:921616
used_memory_human:900.02K
...

# Stats
total_connections_received:5
total_commands_processed:25
keyspace_hits:8
keyspace_misses:2
...

INFO returns a massive dump of server information. The most useful sections:

  • Memory: used_memory_human tells you how much RAM Redis is using
  • Stats: keyspace_hits vs keyspace_misses tells you your hit rate (the basis of cache monitoring)
  • Clients: connected_clients — how many clients are connected

You can filter by section:

127.0.0.1:6379> INFO memory
# Memory
used_memory:921616
used_memory_human:900.02K
used_memory_rss:8388608
used_memory_rss_human:8.00M
...

127.0.0.1:6379> INFO stats
# Stats
total_connections_received:5
total_commands_processed:25
...

MONITOR — seeing every command in real time

127.0.0.1:6379> MONITOR
OK
1714069200.123456 [0 127.0.0.1:54321] "SET" "key1" "value1"
1714069201.234567 [0 127.0.0.1:54321] "GET" "key1"
1714069202.345678 [0 127.0.0.1:54322] "INCR" "counter"

MONITOR shows you EVERY command Redis executes, in real time. It includes the timestamp, the client's IP, and the exact command.

⚠️ CRITICAL: MONITOR has high overhead. Don't use it in production with real traffic — it can degrade performance. In development it's invaluable for debugging. To exit, Ctrl+C.

CLIENT LIST — seeing connected clients

127.0.0.1:6379> CLIENT LIST
id=3 addr=127.0.0.1:54321 name= age=120 idle=5 ...
id=5 addr=127.0.0.1:54322 name= age=60 idle=2 ...

Useful for spotting zombie connections or knowing how many workers are connected.

FLUSHDB and FLUSHALL — deleting everything

127.0.0.1:6379> DBSIZE
(integer) 4

127.0.0.1:6379> FLUSHDB
OK

127.0.0.1:6379> DBSIZE
(integer) 0

FLUSHDB deletes every key in the current database. FLUSHALL deletes every key in ALL the databases.

⚠️ CRITICAL: In production, this is catastrophic. Some teams disable these commands in production precisely for that reason. In development, use them without fear whenever you need to start from scratch.

SELECT — multiple databases

Redis has 16 numbered databases (0-15) by default. You switch with SELECT:

127.0.0.1:6379> SET test "db 0"
OK

127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> GET test
(nil)

127.0.0.1:6379[1]> SET test "db 1"
OK

127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> GET test
"db 0"

Notice how the prompt changes to 127.0.0.1:6379[1]> when you're in DB 1. The databases are isolated — data in DB 0 isn't visible from DB 1.

⚠️ NOTE: Using multiple DBs is debated. Redis Cluster doesn't support them. Modern practice is to use key prefixes (app1:user:1, app2:user:1) instead of separate databases. For this guide we'll stick with DB 0 (the default).


Troubleshooting

Problem 1: redis-cli: command not found

Cause: redis-cli isn't installed on your machine (you're using Docker but without a local client).

Solution:

Option A — use the container's client:

docker exec -it redis-dev redis-cli

Option B — install redis-cli locally:

# macOS
brew install redis

# Ubuntu/Debian
sudo apt install redis-tools

Problem 2: Could not connect to Redis at 127.0.0.1:6379: Connection refused

Cause: Redis isn't running, or it's running on another port.

Solution:

# Check whether Redis is running (Docker)
docker ps | grep redis

# If it doesn't show up, bring it up:
docker run -d --name redis-dev -p 6379:6379 redis:7

# If it says "name redis-dev already in use":
docker start redis-dev   # If it's only stopped
docker rm redis-dev && docker run -d --name redis-dev -p 6379:6379 redis:7  # If you have to recreate it

If you installed Redis natively:

# macOS
brew services list | grep redis  # Check the status
brew services start redis        # Start it

# Linux
sudo systemctl status redis-server
sudo systemctl start redis-server

Problem 3: Port 6379 is already in use

Cause: You have another Redis running (maybe one native and one in Docker).

Solution:

# See which process is using port 6379
lsof -i :6379          # macOS/Linux
netstat -ano | findstr :6379   # Windows

# If it's another native Redis, stop it:
brew services stop redis        # macOS
sudo systemctl stop redis-server # Linux

# Or use another port in Docker:
docker run -d --name redis-dev -p 6380:6379 redis:7
# And connect with: redis-cli -p 6380

Problem 4: (error) ERR wrong number of arguments

Cause: Incorrect syntax in the command.

Solution: Check the command's documentation with HELP:

127.0.0.1:6379> HELP SET
SET key value [EX seconds|PX milliseconds|...]

Or in the official docs: https://redis.io/commands/

Problem 5: redis-cli shows strange characters with strings containing special characters

Cause: The default encoding. The binary is fine, it just doesn't render well.

Solution: Start redis-cli with the raw output flag:

redis-cli --no-raw  # Formatted output (the default in interactive mode)
redis-cli           # For scripts, raw output

Or use decode_responses=True when you use Python (next capsule).


Exercises

Exercise 1: Complete setup (Easy)

Bring up Redis with Docker, connect with redis-cli, verify with PING, and run SET hello "world" followed by GET hello. The final output should be "world".

See solution
# Bring up Redis
docker run -d --name redis-dev -p 6379:6379 redis:7

# Verify
docker ps

# Connect
redis-cli  # or: docker exec -it redis-dev redis-cli

# Inside redis-cli:
PING
# PONG

SET hello "world"
# OK

GET hello
# "world"

Explanation: This is the minimum viable flow: bring up the server, connect the client, verify the response, and operate. If those 4 steps work, Redis is ready for everything else.

Exercise 2: TTL in practice (Easy)

Create a key notification:42 with the value "Temporary message" and a 10-second TTL. Check the TTL with TTL. Read the value with GET. Wait 12 seconds. Read it again with GET — it should return (nil).

See solution
127.0.0.1:6379> SET notification:42 "Temporary message" EX 10
OK

127.0.0.1:6379> TTL notification:42
(integer) 10

127.0.0.1:6379> GET notification:42
"Temporary message"

# (wait 12 seconds)

127.0.0.1:6379> GET notification:42
(nil)

127.0.0.1:6379> TTL notification:42
(integer) -2

Explanation: TTL returns -2 because the key no longer exists (Redis deleted it when it expired). This is the foundation of caching: store a value with a TTL, and Redis cleans it up by itself. No cron jobs and no cleanup code needed.

Exercise 3: Multiple keys with a prefix (Easy-Medium)

Create 5 keys following the pattern product:1, product:2, ..., product:5 with the values "Product N". Create 3 more keys with the pattern category:1, category:2, category:3. Use KEYS with a pattern to list only the products.

See solution
127.0.0.1:6379> SET product:1 "Product 1"
OK
127.0.0.1:6379> SET product:2 "Product 2"
OK
127.0.0.1:6379> SET product:3 "Product 3"
OK
127.0.0.1:6379> SET product:4 "Product 4"
OK
127.0.0.1:6379> SET product:5 "Product 5"
OK
127.0.0.1:6379> SET category:1 "Cat 1"
OK
127.0.0.1:6379> SET category:2 "Cat 2"
OK
127.0.0.1:6379> SET category:3 "Cat 3"
OK

127.0.0.1:6379> KEYS product:*
1) "product:1"
2) "product:2"
3) "product:3"
4) "product:4"
5) "product:5"

127.0.0.1:6379> KEYS *
1) "product:1"
... (8 keys total)

Explanation: The : is only a visual convention — Redis treats product:1 as a normal string. But that convention lets you use KEYS product:* to filter by prefix. Remember: never use KEYS in production (it blocks Redis when there are many keys).

Exercise 4: Deleting all the categories (Medium)

With the keys from exercise 3 still in Redis, delete all the ones starting with category: in a single DEL command.

See solution
127.0.0.1:6379> DEL category:1 category:2 category:3
(integer) 3

127.0.0.1:6379> KEYS category:*
(empty array)

127.0.0.1:6379> KEYS product:*
1) "product:1"
2) "product:2"
3) "product:3"
4) "product:4"
5) "product:5"

Explanation: DEL accepts multiple keys as arguments. It returns how many keys were deleted (3 = they all existed). A professional trick: to delete many keys matching a pattern, use a combination of bash + redis-cli:

redis-cli KEYS "category:*" | xargs redis-cli DEL

(In production, use SCAN instead of KEYS so you don't block Redis.)

Exercise 5: Basic hit rate (Medium)

Use INFO stats to look at keyspace_hits and keyspace_misses. Run 10 GETs against a key that exists and 10 against a key that does NOT exist. Look at INFO stats again. Calculate your hit rate: hits / (hits + misses).

See solution
127.0.0.1:6379> SET hot_key "exists"
OK

127.0.0.1:6379> INFO stats
# Stats
keyspace_hits:5     # an example, it can vary
keyspace_misses:2

# Run 10 GETs against the key that exists
127.0.0.1:6379> GET hot_key
"exists"
# (repeat 10 times, or use a redis-cli loop)

# Run 10 GETs against a key that does NOT exist
127.0.0.1:6379> GET cold_key
(nil)
# (repeat 10 times)

127.0.0.1:6379> INFO stats
# Stats
keyspace_hits:15    # 5 initial + 10 hits
keyspace_misses:12  # 2 initial + 10 misses

Hit rate calculation:

The difference: 10 hits and 10 misses added.

Hit rate = hits / (hits + misses) = 10 / (10 + 10) = 0.5 = 50%

Explanation: Hit rate is the fundamental cache performance metric. 50% is low (only half the requests are served from cache). In production, the typical target is 80%+ for an effective cache. If your hit rate is consistently below 50%, the cache isn't helping — it's worth auditing your strategy (module 2 covers this).

A trick for the GET loop in bash:

for i in {1..10}; do redis-cli GET hot_key; done
for i in {1..10}; do redis-cli GET cold_key; done

Exercise 6: Debugging with MONITOR (Medium-Hard)

Open two terminals with redis-cli. In the first one, run MONITOR. In the second, run several commands: SET test 1, INCR test, EXPIRE test 60, GET test, DEL test. Watch the output in the MONITOR terminal.

See solution

Terminal 1 (MONITOR):

127.0.0.1:6379> MONITOR
OK
1714069300.123 [0 127.0.0.1:54321] "SET" "test" "1"
1714069302.456 [0 127.0.0.1:54321] "INCR" "test"
1714069304.789 [0 127.0.0.1:54321] "EXPIRE" "test" "60"
1714069306.012 [0 127.0.0.1:54321] "GET" "test"
1714069308.345 [0 127.0.0.1:54321] "DEL" "test"

Terminal 2 (the operations):

127.0.0.1:6379> SET test 1
OK
127.0.0.1:6379> INCR test
(integer) 2
127.0.0.1:6379> EXPIRE test 60
(integer) 1
127.0.0.1:6379> GET test
"2"
127.0.0.1:6379> DEL test
(integer) 1

Explanation: MONITOR is Redis's most powerful debugging tool for understanding what's happening in real time. In development, you use it to see which commands your application sends (is it caching what you think it is? are there duplicate queries?). In production, do NOT use it — it adds ~50% overhead on throughput. For production there are alternatives: redis-cli --latency to measure latency, slow logs (SLOWLOG GET) for slow queries.


Summary

In this capsule you learned:

  • Installing Redis with Docker in 30 seconds: docker run -d --name redis-dev -p 6379:6379 redis:7
  • Installation alternatives (Homebrew, apt) and why Docker is preferable
  • Connecting redis-cli inside the container or from your machine with redis-cli -h localhost -p 6379
  • Fundamental commands: PING, SET, GET, DEL, EXISTS, KEYS, TYPE
  • Expiration: EXPIRE, TTL, PERSIST, SET ... EX seconds (the short syntax)
  • Exploration commands: DBSIZE, INFO, MONITOR, CLIENT LIST, SELECT, FLUSHDB
  • The key convention: prefixes with : (e.g., user:1, cache:product:42) — visual, not functional
  • Basic cache metrics: keyspace_hits vs keyspace_misses to calculate hit rate

Critical commands not to use in production: KEYS *, MONITOR, FLUSHDB, FLUSHALL. Useful in development, dangerous in production.


Additional resources

  1. Redis Commands Reference — The complete list of commands with syntax and examples
  2. Redis CLI documentation — Official redis-cli docs with flags and advanced modes
  3. Docker Hub: redis image — Official documentation for the Docker image, all versions and configurations
  4. Redis Persistence — Depth on RDB vs AOF for when you want to configure serious persistence
  5. Redis Latency Monitoring — How to use redis-cli --latency and slow logs
  6. redis-cli Cheat Sheet — A quick printable reference

What's next?

In Capsule 03 you leave simple SET/GET behind and get into the two most-used data types in the backend: strings with advanced operations (atomic counters with INCR, locks with SETNX, batch ops) and hashes (structured objects with individually updatable fields). This is where Redis starts to feel powerful, not just fast.

Keep Redis running in your Docker between capsules. If you stopped it:

docker start redis-dev

Let's go.