Redis with Docker Compose: Persistence, Security, and Production-Ready Configuration

redis persistence docker

Table of Contents

  1. Why Redis Containers Lose Data
  2. RDB vs AOF: Choosing a Persistence Strategy
  3. Basic Setup with Docker Compose
  4. Full Persistence Configuration
  5. Securing Redis in Docker
  6. Setting Resource Limits
  7. Redis in a Multi-Service Stack
  8. Backing Up and Restoring a Redis Volume
  9. Common Issues and Quick Fixes
  10. Closing Notes

Redis is one of the most common services to run in Docker — it’s fast to spin up, lightweight, and perfect for caching, session storage, and queues. But that same simplicity hides a trap: by default, Redis in Docker stores everything in memory, and the moment a container is removed, all of that data disappears.

This guide walks through setting up Redis with Docker Compose the right way — covering persistence, authentication, resource limits, and the health checks you need before putting it anywhere near production.

Why Redis Containers Lose Data

A Docker container is meant to be disposable. That’s a feature for stateless services, but it’s a liability for a database like Redis. If you start a plain Redis container without a mounted volume, here’s what happens:

  • The container writes its dataset only inside its own writable layer.
  • docker compose down or docker rm removes that layer entirely.
  • The next time the container starts, Redis initializes with an empty dataset.

This single oversight accounts for a large share of “we lost our session data” incidents in small teams running Redis in containers for the first time. The fix is straightforward once you understand the two persistence mechanisms Redis offers.

RDB vs AOF: Choosing a Persistence Strategy

Redis supports two persistence models, and production setups typically combine both:

RDB (Redis Database snapshots)
Point-in-time snapshots of the dataset, saved at intervals you define. Fast to restore, but you can lose any writes that happened after the last snapshot.

AOF (Append Only File)
Every write operation is logged to disk as it happens. Slower to restore on a large dataset, but far safer — with appendfsync everysec, you lose at most one second of writes.

For most production workloads, enable both: AOF for durability, RDB for fast snapshot-based backups.

Basic Setup with Docker Compose

Start with a minimal but persistent configuration:

version: '3.8'
services:
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes

volumes:
  redis-data:
    driver: local

The key line here is volumes: - redis-data:/data. The official Redis image is already configured to write its dataset to /data, so mounting a named volume there is enough to survive a container removal. Named volumes are preferred over bind mounts for this purpose — they’re portable across hosts and Docker manages their lifecycle for you.

Full Persistence Configuration

For more control, mount a custom redis.conf instead of relying on command-line flags:

# redis.conf

# Network
bind 0.0.0.0
port 6379
protected-mode yes

# Security
requirepass your_secure_password_here

# Memory management
maxmemory 512mb
maxmemory-policy allkeys-lru

# AOF persistence
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# RDB persistence
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

# Logging
loglevel notice

Mount it in Docker Compose:

services:
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
      - ./redis.conf:/usr/local/etc/redis/redis.conf:ro
    command: redis-server /usr/local/etc/redis/redis.conf

volumes:
  redis-data:
    driver: local

Mounting the config file as read-only (:ro) prevents Redis itself from accidentally modifying it, and keeps your configuration under version control instead of buried in command-line arguments.

Securing Redis in Docker

Redis has no authentication enabled by default, and it has been a recurring target for cryptomining botnets that scan the internet for exposed instances on port 6379. A few non-negotiable steps:

  1. Always set requirepass. Never run Redis with ALLOW_EMPTY_PASSWORD outside of a local, throwaway development environment.
  2. Never expose port 6379 directly to the internet. If remote access is genuinely needed, put it behind a VPN or SSH tunnel — not a public port mapping.
  3. Disable dangerous commands in production, such as FLUSHALL, FLUSHDB, and CONFIG, using rename-command in redis.conf.
  4. Run as a non-root user. The official and Bitnami images already drop privileges by default — don’t override this with SKIP_DROP_PRIVS unless you have a specific reason to.

This is the same defense-in-depth mindset covered in our Docker Container Security Best Practices guide — Redis just adds a database-specific layer to those general container hardening principles.

Setting Resource Limits

An unbounded Redis instance can consume all available host memory under heavy load, taking down other services on the same machine. Set explicit limits:

services:
  redis:
    image: redis:7-alpine
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 256M
    command: redis-server --maxmemory 800mb --maxmemory-policy allkeys-lru

Note the two limits work at different layers: deploy.resources.limits.memory is enforced by Docker itself (the container gets OOM-killed if it crosses this), while maxmemory inside Redis tells Redis to start evicting keys before that happens — using allkeys-lru as a sensible general-purpose eviction policy for caching workloads.

Redis in a Multi-Service Stack

When Redis is shared by an API and a background worker, startup order matters. A worker that connects before Redis is ready will crash-loop. Use a health check with condition: service_healthy:

version: '3.8'
services:
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - backend

  api:
    build: ./api
    depends_on:
      redis:
        condition: service_healthy
    environment:
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
    networks:
      - backend

  worker:
    build: ./worker
    depends_on:
      redis:
        condition: service_healthy
    environment:
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/1
    networks:
      - backend

volumes:
  redis-data:
    driver: local

networks:
  backend:
    driver: bridge

Two details worth highlighting:

  • Logical databases. Redis supports 16 logical databases (0–15) on a single instance. Assigning a different database index per service (/0 for the API, /1 for the worker) avoids key collisions without running multiple Redis containers — useful in smaller setups where running separate instances would be overkill.
  • Custom bridge network. Putting Redis on its own backend network — separate from any network that’s exposed to the outside — limits which containers can even attempt to reach it. This pairs directly with the network segmentation principles in our Docker Network Security guide.

Backing Up and Restoring a Redis Volume

Even with persistence enabled, you still want backups independent of the running container — protecting against accidental FLUSHALL, corrupted volumes, or host failure.

Backup script:

#!/bin/bash
# backup-redis-volume.sh
VOLUME_NAME="redis_redis-data"
BACKUP_DIR="/backups/redis"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"

# Trigger a snapshot before backing up
docker exec redis redis-cli BGSAVE
sleep 5

docker run --rm \
  -v "$VOLUME_NAME:/data:ro" \
  -v "$BACKUP_DIR:/backup" \
  alpine tar czf "/backup/redis-data-$DATE.tar.gz" -C /data .

echo "Backup created: $BACKUP_DIR/redis-data-$DATE.tar.gz"

Restore script:

#!/bin/bash
# restore-redis-volume.sh
BACKUP_FILE="$1"
VOLUME_NAME="redis_redis-data"

docker compose stop redis

docker run --rm \
  -v "$VOLUME_NAME:/data" \
  -v "$(dirname "$BACKUP_FILE"):/backup:ro" \
  alpine tar xzf "/backup/$(basename "$BACKUP_FILE")" -C /data

docker compose start redis
echo "Restore complete from $BACKUP_FILE"

The same backup-then-archive pattern is used in our MongoDB Backup article — if you’re running both databases in the same stack, you can adapt one scheduled cron job to cover both with minimal changes.

Common Issues and Quick Fixes

SymptomLikely CauseFix
Data gone after docker compose downNo volume mountedAdd a named volume at /data.
WRONGPASS errors from applicationPassword mismatch between redis.conf and application environment variablesVerify that REDIS_PASSWORD matches in both configurations.
Redis container OOM-killedNo memory limit configured or missing eviction policySet deploy.resources.limits.memory and configure maxmemory-policy.
Worker crash-loops on startupWorker starts before Redis is fully readyAdd a healthcheck and use condition: service_healthy.
High latency under loadSynchronous AOF fsync configurationUse appendfsync everysec instead of always.

Conclusion

Running Redis in Docker Compose is simple to get started with — and just as simple to get wrong if persistence and security are treated as an afterthought. The pattern that holds up well in production is consistent: named volumes for /data, AOF+RDB persistence enabled together, requirepass always set, explicit memory limits, and a health check gating any service that depends on Redis being ready.

From here, if your stack also includes MongoDB or another database alongside Redis, the same volume-and-backup discipline applies — see our MongoDB Backup guide for the database-specific details, and our Docker Network Security guide for locking down how these containers talk to each other.

(Visited 2 times, 2 visits today)

You may also like