ToolsOps

Docker Compose for Redis with a password

A Redis compose.yaml with a password (requirepass), AOF persistence, a redis-cli healthcheck and a localhost-bound port, plus why a Redis with no AUTH and an open port gets compromised in minutes.

Redis is blazing fast and very easy to start, which is also why it is very easy to misconfigure. This guide sets up a Redis for development with two things many people skip: a password and persistence. Use it as a cache, queue or session store from your local app.

compose.yaml with AUTH and persistence

services:
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
    environment:
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    ports:
      - "127.0.0.1:6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ['CMD-SHELL', 'redis-cli -a "$$REDIS_PASSWORD" ping | grep -q PONG']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

volumes:
  redis_data:

The .env.example is a single line. Copy it to .env, set a real password and do not push the .env to git.

REDIS_PASSWORD=CHANGE_ME

The decisions that matter

  • --requirepass. Turns on authentication. Without it, anyone who reaches the port owns your Redis.
  • command as a list. The exec form lets Compose substitute ${REDIS_PASSWORD} from the .env before startup, so the server gets the real password. A single string without a shell would leave the variable unexpanded.
  • --appendonly yes. Enables AOF persistence: every write is logged to the redis_data:/data volume, so you do not lose everything on restart.
  • 127.0.0.1:6379:6379. The port is published on your loopback only. Other containers in the compose reach Redis by name (redis:6379) without publishing it.
  • healthcheck with redis-cli. Runs an authenticated ping and checks for PONG. It uses $$REDIS_PASSWORD so the $ is evaluated by the shell inside the container.

Check that it works

docker compose up -d
docker compose ps                       # should show healthy
docker compose exec redis \
  redis-cli -a "$REDIS_PASSWORD" ping    # -> PONG
docker compose exec redis \
  redis-cli -a "$REDIS_PASSWORD" set greeting hello
docker compose exec redis \
  redis-cli -a "$REDIS_PASSWORD" get greeting

The risk of an open Redis

The classic incident: someone publishes 6379:6379 (without 127.0.0.1) on a server and leaves Redis with no password. Within minutes a bot finds it, writes keys and, in the worst cases, gets command execution or wipes everything with FLUSHALL. Two habits prevent this: always set a password, and do not publish the port unless you truly need host access (and then, bound to loopback).

Next steps

Tune this stack in the Docker Compose generator and head back to the examples index to combine Redis with your app or your database.

Frequently asked questions

Why is command a list ["redis-server", ...] instead of a string?
Because the list (exec) form makes Compose substitute ${REDIS_PASSWORD} from your .env before the container starts, so redis-server receives the real password. If you write command as a single string, it does not go through a shell and the variable can stay literal. The trade-off is that docker compose config will show the substituted password: acceptable locally, which is exactly why the secret lives in the .env and not in the compose.
Does setting a Redis password really matter locally?
It matters the moment you publish the port. Redis without requirepass accepts any command from whoever reaches it, including FLUSHALL or writing keys for remote execution. Bots constantly scan for port 6379 open to the Internet. Locally, with the port bound to 127.0.0.1 the risk drops a lot, but get into the habit of always setting AUTH: the day that compose ends up on a server with the port open, it is already protected.
What is the difference between AOF and RDB?
They are the two ways to persist Redis. RDB takes periodic snapshots of the dataset (fast, but you can lose the most recent writes between snapshots). AOF (--appendonly yes) logs every write, so you lose less data on a crash, at the cost of larger files. For development, AOF on the /data volume is a simple, safe choice. If you use Redis purely as a throwaway cache, you can skip persistence.
I get (error) NOAUTH Authentication required, what now?
It means Redis has a password but your client is not sending it. Connect with redis-cli -a "$REDIS_PASSWORD" or, inside the container, docker compose exec redis redis-cli -a "$REDIS_PASSWORD". If you instead see (error) WRONGPASS, the password does not match: check that your .env and what your app passes are the same value, and that you did not leave an old volume with a different configuration.
Is this good for production?
It is a starting point for development. In production you would not publish the port to the host, you would treat the password as a secret managed outside git, you would decide persistence and memory policy (maxmemory, eviction policy) based on usage, and you would consider replicas or a managed Redis. The goal here is to teach you the correct pattern, not to deploy this as-is in front of users.