How to Install Redis and Valkey in 2026: Docker, Compose and Testcontainers
The honest answer to "how do I install Redis" changed somewhere around 2020, and most guides have not caught up. You do not install it. You run a container, you point your application at it, and you throw it away when you are done. The package manager still works, and there is a section on it at the bottom of this page, but it is now the exception rather than the default.
This page covers the container path for both Redis and Valkey — the single-command start, the Docker Compose file worth keeping, the three settings that bite people in week two, and the Java test harness that replaces a shared development server entirely. Everything here has been checked against the current official images: redis 8.10.1 and valkey/valkey 9.1.1.
One Command, Two Servers
Both projects publish an official image, and both start the same way:
# Redis
docker run -d --name redis -p 6379:6379 redis:8-alpine
# Valkey — same protocol, same port, same client code
docker run -d --name valkey -p 6379:6379 valkey/valkey:9-alpine
Check that either one answers:
docker exec -it redis redis-cli ping
# PONG
docker exec -it valkey valkey-cli ping
# PONG
That is the whole installation. No apt, no make, no editing redis.conf to set supervised systemd. If you want to know what else you can do from that prompt once it is up, the redis-cli guide covers the flags that matter.
Pin the major version rather than using latest. Both publish tags at three levels of precision — 8, 8.10 and 8.10.1 on the Redis side, 9, 9.1 and 9.1.1 on the Valkey side — so redis:8 tracks patch and minor releases within a major while staying off the next one. Each also has an -alpine variant built on a much smaller base, which is worth taking in CI where you are pulling the image repeatedly. latest will move under you on a Tuesday and take your build with it.
Redis or Valkey?
Valkey is the Linux Foundation fork of Redis 7.2.4, created in March 2024 after Redis Ltd. moved off the BSD licence. For local development the practical difference is close to zero — same wire protocol, same commands, same port — so the interesting question is which one you are going to run in production, because that is what you should be developing against.
| Version | Licence |
|---|---|
| Redis 7.2.4 and earlier | 3-Clause BSD |
| Redis 7.4.x to 7.8.x | Dual RSALv2 or SSPLv1 |
| Redis 8.0 and later | Tri-licensed: RSALv2, SSPLv1 or AGPLv3 |
| Valkey (all versions) | 3-Clause BSD |
Redis 8 restored an OSI-approved option by adding AGPLv3 to the tri-licence, which removed the sharpest edge of the 2024 change for a lot of teams. It did not remove it for everyone: AGPL's network-use clause is still a blocker at organisations whose legal review treats it as viral. That is the population Valkey exists for, and the pull numbers show it landing — the Redis official image runs around 30 million pulls a week against roughly 9 million for valkey/valkey. Valkey is not a rounding error any more, and it is not displacing Redis either.
Redisson talks to both over the same redis:// scheme with no code change, so this is a deployment decision rather than a client one. Valkey vs Redis covers the fork in depth, and migrating from Redis to Valkey in Java covers what moving actually involves.
The Docker Compose File Worth Keeping
A bare docker run is fine for a five-minute experiment. Once your application needs to start alongside the server, use Compose. Two notes before the file: the top-level version: key is obsolete and current Compose warns about it, and the hyphenated docker-compose v1 binary stopped receiving updates in July 2023. The command is docker compose, and the file starts at services:.
# docker-compose.yml
services:
redis:
image: redis:8-alpine
command: redis-server --save 60 1 --appendonly yes --loglevel warning
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
redis-data:
Bring it up with docker compose up -d and tear it down with docker compose down. Add -v to that second command when you want the data gone as well as the container.
To run Valkey instead, three tokens change:
valkey:
image: valkey/valkey:9-alpine
command: valkey-server --save 60 1 --appendonly yes --loglevel warning
ports:
- "6379:6379"
volumes:
- valkey-data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
The image name, the server binary and the CLI binary. Nothing else — not the port, not the command flags, not a single line of Java. That symmetry is the whole practical argument for treating the two as interchangeable during development.
Persistence: The Flag Everyone Forgets
The default command in both images starts the server with no persistence configured. Mounting a volume at /data does nothing on its own, because nothing is being written there. Restart the container and your data is gone — which is fine for a throwaway cache and surprising if you were treating it as a database.
The --save 60 1 above writes an RDB snapshot every 60 seconds provided at least one key changed. --appendonly yes turns on the AOF log, which is the durable option: it records every write and replays it on restart. Running both is normal, and the two answer different questions — the snapshot is what you copy elsewhere for a backup, the AOF is what limits how much you lose on an unclean shutdown.
Valkey has one convenience the Redis image does not: an environment variable that appends server flags without replacing the command.
docker run -d --name valkey \
--env VALKEY_EXTRA_FLAGS='--save 60 1 --appendonly yes' \
-p 6379:6379 valkey/valkey:9-alpine
There is no REDIS_EXTRA_FLAGS on the official Redis image. You will find one on the Bitnami image and a REDIS_ARGS variable on redis/redis-stack, and copying either into a redis:8 service is a common way to end up with a server that silently ignores your settings. On the official image, flags go in command:.
Passwords: The Official Image Has No Environment Variable
This is the single most common Compose mistake with Redis, and it is worth being blunt about. There is no REDIS_PASSWORD on the official image. Setting one does nothing at all — the container starts, accepts the variable, and remains open.
It remains open in a stronger sense than most people expect. Both official images ship with protected mode turned off so that other containers on the same Docker network can reach the server without configuration. The consequence, stated plainly in the image documentation, is that publishing the port with -p exposes an unauthenticated server to anything that can route to your host.
Set the password as a server flag:
services:
redis:
image: redis:8-alpine
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?set REDIS_PASSWORD in .env}"]
environment:
REDISCLI_AUTH: "${REDIS_PASSWORD}"
ports:
- "127.0.0.1:6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
Three things are doing work there. ${REDIS_PASSWORD:?message} makes Compose fail immediately if the variable is missing from your .env, rather than quietly starting an open server. Binding to 127.0.0.1:6379:6379 instead of 6379:6379 keeps the port on the loopback interface rather than every interface on the machine. And the health check authenticates through the REDISCLI_AUTH environment variable rather than redis-cli -a, which keeps the password out of the check's argument list — -a would put it into the process table on every probe, and prints a warning each time it does.
The trade-off with a command-line password is that it is visible to anyone who can run docker inspect or read the process table. For local development that is acceptable. For anything shared, mount a config file — /usr/local/etc/redis/redis.conf for Redis, /usr/local/etc/valkey/valkey.conf for Valkey — and keep the secret out of the process arguments. If you are being handed a NOAUTH Authentication required error from the Java side, that error has its own page.
Connecting From Java: The Hostname Nobody Expects
Add your application to the same Compose file and one detail immediately trips people up: the address depends on where the client is running.
services:
redis:
image: redis:8-alpine
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
app:
build: .
depends_on:
redis:
condition: service_healthy
environment:
REDIS_ADDRESS: "redis://redis:6379"
From inside the app container the server is reachable at redis://redis:6379 — the service name is the hostname, and the port is the container port, not the published one. From your IDE on the host it is redis://127.0.0.1:6379, via the ports: mapping. The same application, run two ways, needs two addresses, which is why it belongs in an environment variable rather than hard-coded.
The second detail is condition: service_healthy. A plain depends_on: [redis] waits for the container to start, not for the server inside it to accept connections, so your application will occasionally lose the race and fail on the first connection attempt. The health check plus the condition is what actually makes the ordering mean something.
On the Java side, nothing about running in a container changes the client configuration:
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class Application {
public static void main(String[] args) {
String address = System.getenv()
.getOrDefault("REDIS_ADDRESS", "redis://127.0.0.1:6379");
Config config = new Config();
config.useSingleServer().setAddress(address);
RedissonClient redisson = Redisson.create(config);
try {
RBucket<String> bucket = redisson.getBucket("greeting");
bucket.set("hello from a container");
System.out.println(bucket.get());
} finally {
redisson.shutdown();
}
}
}
The Maven dependency, using the current release:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>4.7.0</version>
</dependency>
The redis:// scheme is correct for Valkey too — swap the image, leave the code. Use rediss:// for TLS, which connecting over TLS covers, and note that RedissonClient is thread-safe and holds its own connection pool: build one per application and reuse it. How to connect to Redis in Java goes through the rest of the client setup, and the configuration docs list every topology.
Integration Tests: Stop Sharing a Server
Compose solves local development. It does not solve testing, and the usual workaround — a shared Redis that everyone's tests point at — fails in the predictable ways: state leaks between test classes, two engineers collide on the same key, and CI needs an instance that nobody remembered to provision.
Testcontainers is the Java answer. It starts a real container per test class, on a random port, and destroys it afterwards.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
import org.junit.jupiter.api.Test;
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Testcontainers
class RedissonIntegrationTest {
@Container
static final GenericContainer<?> VALKEY =
new GenericContainer<>(DockerImageName.parse("valkey/valkey:9-alpine"))
.withExposedPorts(6379);
@Test
void bucketSurvivesARoundTrip() {
Config config = new Config();
config.useSingleServer().setAddress(
"redis://" + VALKEY.getHost() + ":" + VALKEY.getFirstMappedPort());
RedissonClient redisson = Redisson.create(config);
try {
RBucket<String> bucket = redisson.getBucket("greeting");
bucket.set("hello");
assertEquals("hello", bucket.get());
} finally {
redisson.shutdown();
}
}
}
The important line is getFirstMappedPort(). Testcontainers deliberately publishes on a random host port so that parallel test runs cannot collide, which means hard-coding 6379 defeats the point. Ask the container where it ended up.
A static container starts once for the whole class; drop the static and JUnit gives each test method a fresh server, which is slower but removes any question of state bleeding between tests. Spring Boot users can attach @ServiceConnection(name = "redis") to the container field and skip the address wiring entirely.
This matters more than it looks for anything using distributed primitives. Testing a distributed lock, a rate limiter or a queue against a shared server produces failures that depend on who else is running tests at that moment. A container per class makes those tests deterministic.
Installing Without Docker
Still the right call if you want the server running as a system service, or you are on a machine where Docker is not an option.
Ubuntu and Debian. sudo apt install redis-server works, but the distribution package lags — often by a major version or more. For a current build, add the vendor repository:
sudo apt-get install lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] \
https://packages.redis.io/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis
redis-cli ping
# PONG
Two differences from the distribution package worth knowing. The vendor package is called redis, not redis-server, and it pulls in redis-tools alongside it. And it starts on install and enables itself at boot, so there is normally no systemctl enable step — if yours did not, sudo systemctl enable --now redis-server fixes it. Recent packages also set supervised systemd for you; the manual edit to /etc/redis/redis.conf that older guides describe is no longer needed on a systemd host.
macOS. Homebrew, which is the shortest path on a Mac:
brew install redis
brew services start redis
# ...or Valkey. Pick one: the two formulae conflict,
# because both ship a server on port 6379 and overlapping binaries
brew install valkey
brew services start valkey
That conflict is the one thing worth knowing here. Homebrew declares valkey as conflicting with redis, so brew install valkey fails while the Redis formula is present. If you want to compare the two on the same machine, run at least one of them in a container instead — which is a reasonable argument for running both that way from the start.
Windows. There is still no official native build. Use WSL 2 and follow the Ubuntu instructions inside it, which is what the Redis project recommends. The unofficial native ports that circulate are pinned to Redis 5 and older and should not be used for anything you intend to ship. A container is the better answer here — Docker Desktop on Windows runs the Linux image and gives you the real server.
From source. Only when you need a build flag the packages do not give you. Clone the repository, run make, then make test to confirm the build before make install.
Do You Need to Install It at All?
Running it yourself is right for local development and for production when you want full control of the configuration. The alternative is managed Redis — AWS ElastiCache, Azure Managed Redis, Google Cloud Memorystore — where the provider runs the server.
Understand the trade before choosing. A managed service handles provisioning, patching, replication and failover, but it also disables most administrative commands, restricts configuration to an approved subset, and controls which engine versions you can run. That changes how your client connects and what your application is allowed to do. It is also why a local container is still worth having even on a fully managed team: you cannot run CONFIG SET against ElastiCache to reproduce a bug, and you can against a container you started thirty seconds ago.
Frequently Asked Questions
How Do I Install Redis?
On any machine with Docker, docker run -d --name redis -p 6379:6379 redis:8-alpine is the whole installation. Without Docker, use apt install redis-server on Ubuntu or Debian, brew install redis on macOS, and WSL 2 on Windows. There is no official native Windows build.
Can I Run Redis in Docker?
Yes, and it is the default way to run it for development. Redis Ltd. maintains the official redis image and the Valkey community maintains valkey/valkey. Both start with no arguments, expose 6379, and store data in /data when persistence is enabled.
Is Redis Docker Free?
The image is free to pull and the server is free to run. The licence depends on the version: Redis 7.2.4 and earlier are 3-Clause BSD, Redis 7.4.x through 7.8.x are dual RSALv2 or SSPLv1, and Redis 8.0 and later are tri-licensed under RSALv2, SSPLv1 or AGPLv3. Valkey is 3-Clause BSD throughout. The restrictions in RSALv2 and SSPLv1 target offering the software as a competing managed service, not ordinary application use.
How Do I Set a Redis Password in Docker Compose?
Pass --requirepass in the command: block. The official image has no REDIS_PASSWORD environment variable — setting one has no effect, and the server stays open. REDIS_PASSWORD belongs to the Bitnami image and REDIS_ARGS to redis/redis-stack; neither works on redis:8. For anything beyond local development, mount a config file so the secret does not appear in the process arguments.
How Do I Connect to Redis From Another Docker Compose Service?
Use the service name as the hostname and the container port: redis://redis:6379. The published port in ports: is only for reaching the server from the host, where the address is redis://127.0.0.1:6379. Add a health check and depends_on with condition: service_healthy, because plain depends_on waits for the container to start rather than for the server to accept connections.
Can I Install Redis Locally?
Yes. A container is a local installation — it runs on your machine and listens on your port. If you want it running as a system service that survives a reboot without Docker, use the package manager and enable the unit with systemctl enable --now redis-server.
How Do I Install Redis on Windows?
Install WSL 2, choose a Linux distribution, and follow the Ubuntu instructions inside it. Alternatively run the official Linux image under Docker Desktop, which is simpler and gives you the same server. The unofficial native Windows ports are stuck on Redis 5 and older.
Do I Need a Different Java Client for Valkey?
No. Valkey speaks the same protocol, and Redisson connects to both with the same redis:// address scheme and the same configuration. Swapping redis:8-alpine for valkey/valkey:9-alpine in a Compose file requires no change to application code.
Next Steps
Once the server is up, connecting to it from Java is the next step, and connecting to a cluster covers the multi-node case. For running the same images under an orchestrator rather than Compose, see Redisson on Kubernetes.
If you are setting this up to reproduce a production problem, the two pages that most often follow are the redis-cli guide, for finding out what the server is actually doing, and eviction policies, for the case where a container with no maxmemory set behaves nothing like the production instance you are trying to imitate. That last one catches people regularly: the default container has no memory limit at all, so a bug that only appears under eviction pressure will never reproduce locally until you configure it to.