Redis CI/CD automation with practical examples: practical implementation guide
Redis often sits on the critical path for latency and availability. Automating Redis workflows with CI/CD reduces manual error, shortens feedback cycles, and makes risky operations repeatable. In this guide you will:
- Inventory Redis versions, topology, and environments so every change is targeted and testable.
- Build a narrow, inspectable pilot that validates config, runs smoke tests, and proves a safe rollout and rollback.
- Add verification and diagnostics so every change is observable and reversible.
- Learn common failure modes and concrete recovery steps.
The goal is not to automate everything at once. Start with a small slice you can inspect locally and in staging, then expand once you trust the path.
Version and Environment Inventory
Before writing any pipeline code, capture what you run and where. This inventory drives compatibility checks and the shape of your rollout.
- Redis version: 6.x, 7.x, or managed service variant.
- Topology: single instance, Sentinel-managed HA, or Cluster.
- Persistence: RDB snapshots, AOF, or both.
- Auth: ACL users/passwords, TLS, network policies.
- Endpoints: hostnames/ports per environment.
- Client libraries and versions: Node.js, Java, Go, etc.
- Data risks: keyspaces that are write-heavy, eviction policies, maxmemory.
Keep this inventory under version control near your infrastructure-as-code or operations docs. A minimal, practical table you can adapt:
| Field | Example |
|---|---|
| redis_version | 7.2.x |
| topology | single primary with 1 replica |
| persistence | RDB disabled in dev, AOF in prod |
| auth | ACL user "app" with allcommands -dangerous |
| endpoints | dev: localhost:6380, stage: redis-stage:6379, prod: redis-prod:6379 |
| client_libs | Node.js [email protected] |
| maxmemory_policy | allkeys-lru |
Safe Configuration Path
Automation succeeds when scope is small, measurable, and easy to observe.
Recommended path:
- Start with a single-node dev instance for fast iteration.
- Use a throwaway Redis for config validation and smoke tests.
- Keep persistence off for speed in dev: --save "" --appendonly no.
- Stage mirrors production auth and persistence.
- Use the same ACLs and persistence mode you plan for prod.
- Run realistic smoke tests and a light benchmark to ensure headroom.
- Production changes use a replica cutover.
- Add a replica of the current primary.
- Validate replication health and promote only when replica is in sync.
- Switch application connections via a DNS record or configuration flag.
- Keep the change surface narrow.
- One variable per rollout: config tweak, Redis version, or client library update, but not all at once.
- Explicit verification gates between stages.
A minimal stage/gate view:
| Stage | Gate (must be true) | Example command/check |
|---|---|---|
| Config parse | Redis starts with target config | redis-server /path/redis.conf --daemonize no |
| Auth check | App user can auth and run expected ops | redis-cli -a "$REDIS_PASS" PING |
| Functional | Set/Get match expectations | redis-cli SET ci: key val; redis-cli GET ci: key |
| Persistence | AOF or RDB works as intended | redis-cli BGREWRITEAOF; check AOF size > 0 |
| Replication | replica is in sync, lag < threshold | redis-cli INFO replication |
Practical CI/CD Examples
Below are examples you can copy and adapt. They avoid containers by default and rely on redis-server and redis-cli being available on the runner or host.
Prerequisites
- Install Redis tools on the runner or agent.
- Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y redis-tools redis-server
- macOS: brew install redis
- Store secrets (passwords) in your CI system secret store.
- Provide environment variables for endpoints and credentials:
- REDIS_HOST, REDIS_PORT, REDIS_PASS
Example 1: Local config parse and smoke test
This script bootstraps an ephemeral Redis, validates basic commands, then cleans up.
#!/usr/bin/env bash
set -euo pipefail
PORT="6380"
TMP_DIR="$(mktemp -d)"
CONF="$TMP_DIR/redis.conf"
cat > "$CONF" <<'EOF'
port 6380
save ""
appendonly no
protected-mode no
EOF
# Start Redis in the foreground for fast failure, then background it.
redis-server "$CONF" --daemonize yes
# Wait for port to open
for i in {1..20}; do
if redis-cli -p "$PORT" PING >/dev/null 2>&1; then break; fi
sleep 0.2
done
# Functional smoke test
redis-cli -p "$PORT" SET ci: hello world >/dev/null
VAL=$(redis-cli -p "$PORT" GET ci: hello)
if [[ "$VAL" != "world" ]]; then
echo "Unexpected value: $VAL" >&2
exit 1
fi
# Latency sanity
redis-cli -p "$PORT" --latency-history -i 0.1 -n 1 -c 5 >/dev/null || true
# Cleanup
redis-cli -p "$PORT" SHUTDOWN NOSAVE || true
rm -rf "$TMP_DIR"
echo "OK: local config parse and smoke test passed"
Expected result
- Script exits 0 and prints OK.
- No leftover Redis process.
Example 2: App smoke test with Node.js
Use your application client library to exercise auth and a couple of commands. This detects ACL and protocol mismatches earlier than full integration tests.
// file: smoke-redis.js
const { createClient } = require('redis');
(async () => {
const client = createClient({
socket: { host: process.env.REDIS_HOST, port: Number(process.env.REDIS_PORT || 6379) },
password: process.env.REDIS_PASS
});
client.on('error', (err) => {
console.error('Client error', err);
process.exit(2);
});
await client.connect();
const who = await client.sendCommand(['ACL', 'WHOAMI']);
if (!who) throw new Error('ACL WHOAMI empty');
await client.set('ci: app: probe', '42', { EX: 30 });
const v = await client.get('ci: app: probe');
if (v !== '42') throw new Error('Unexpected value: ' + v);
await client.quit();
console.log('OK app smoke');
})().catch((e) => {
console.error(e);
process.exit(1);
});
Run it:
export REDIS_HOST=redis-stage.example
export REDIS_PORT=6379
export REDIS_PASS='******'
node smoke-redis.js
Expected result
- Outputs: OK app smoke
- Exit code 0.
Example 3: Minimal CI YAML with gates (GitHub Actions)
This pipeline validates local config, then runs an app smoke test against stage, and finally performs a guarded production replica check before any manual cutover.
name: redis-ci
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
config-parse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Redis
run: sudo apt-get update && sudo apt-get install -y redis-tools redis-server
- name: Local config parse and smoke
run: |
bash ./scripts/redis-local-smoke.sh
stage-smoke:
needs: config-parse
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Redis CLI
run: sudo apt-get update && sudo apt-get install -y redis-tools
- name: Install deps
run: npm ci
- name: Stage smoke
env:
REDIS_HOST: ${{ secrets.STAGE_REDIS_HOST }}
REDIS_PORT: ${{ secrets.STAGE_REDIS_PORT }}
REDIS_PASS: ${{ secrets.STAGE_REDIS_PASS }}
run: node smoke-redis.js
prod-replica-verify:
needs: stage-smoke
runs-on: ubuntu-latest
steps:
- name: Install Redis CLI
run: sudo apt-get update && sudo apt-get install -y redis-tools
- name: Check replication health
env:
REDIS_HOST: ${{ secrets.PROD_REPLICA_HOST }}
REDIS_PORT: ${{ secrets.PROD_REPLICA_PORT }}
REDIS_PASS: ${{ secrets.PROD_REDIS_PASS }}
run: |
role=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASS" INFO replication | awk -F: '/role:/{print $2}')
echo "role=$role"
[[ "$role" == slave || "$role" == replica || "$role" == replica\r ]] || { echo "Not a replica"; exit 1; }
lag=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASS" INFO replication | awk -F: '/master_link_status:/{print $2}')
echo "master_link_status=$lag"
[[ "$lag" =~ up ]] || { echo "Replication link down"; exit 1; }
Expected result
- The job fails if the replica is not healthy, preventing any further rollout action.
- You perform the final cutover separately with a controlled change (described below).
Example 4: Safe production cutover with a replica
Prerequisites
- You have a current primary P and a synced replica R.
- Applications connect via a DNS record (for example, redis-prod.example) or a central configuration flag you can update atomically.
Steps
- Freeze write-heavy operations briefly (optional, based on risk tolerance).
- Validate replica is in sync.
redis-cli -h R -a "$REDIS_PASS" INFO replication | egrep 'role|master_sync_in_progress|master_link_status|slave|replica'
Expected: role: slave or role: replica, master_link_status: up, master_sync_in_progress:0.
- Promote the replica.
- For managed services, use the provider command to promote.
- For self-managed with Sentinel, trigger a failover via Sentinel.
- For manual promotion on single primary/replica without Sentinel, stop writes to P, then:
# On replica R
a) redis-cli -h R -a "$REDIS_PASS" SLAVEOF NO ONE
# or for newer versions
a) redis-cli -h R -a "$REDIS_PASS" REPLICAOF NO ONE
- Switch application traffic to R.
- Update DNS or your configuration to point to R.
- Flush client connection pools so they reconnect.
- Keep P as a replica of the new primary for quick rollback.
redis-cli -h P -a "$REDIS_PASS" REPLICAOF R <port>
- Observe errors, latency, and replication for at least one key business cycle.
Rollback
- If issues appear, reverse the DNS/config change to point back to P.
- Switch roles back after investigation.
Verification and Diagnostics
Before and after each change, verify health with fast, focused checks.
Connectivity and identity
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS PING
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS ACL WHOAMI
Expected: PONG and the expected ACL username.
Functional read/write
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS SET ci: probe 1 EX 60
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS GET ci: probe
Expected: 1
Replication state
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS INFO replication | egrep 'role|slave|replica|master_link_status|master_sync_in_progress|slave_repl_offset|master_repl_offset'
Expected: role: master on primary, role: replica on replica, link up, in_progress:0, and offsets close.
Latency and slow operations
# Quick latency probe
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS --latency -i 0.1 -n 1 -c 5
# Slowlog snapshot
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS SLOWLOG LEN
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS SLOWLOG GET 10
Memory and eviction posture
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS INFO memory | egrep 'used_memory_human|maxmemory_human|maxmemory_policy'
Persistence sanity (if enabled)
# AOF check: force rewrite and ensure file is non-empty
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS BGREWRITEAOF
sleep 2
ls -lh $(redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS CONFIG GET appendfilename | tail -n1) || true
Application-level verification
- Exercise a representative request path that involves the cache.
- Check cache hit ratio in application logs or metrics if available.
Failure Modes and Recovery
Design your pipeline to stop when these occur and provide deterministic rollback.
| Failure mode | Detection signal | Recovery step |
|---|---|---|
| Bad config parse | Redis refuses to start; stderr shows unknown directive | Fail early on local parse step; fix or revert config; do not proceed |
| Auth or ACL mismatch | PING fails or ERR unknown command due to ACL | Adjust ACLs or app user permissions; re-run smoke test before rollout |
| Replication lag or broken link | master_link_status: down or large offset gap | Pause rollout; investigate network or load; wait until in sync before cutover |
| Memory pressure and eviction | used_memory near max; evictions spike | Increase memory, reduce dataset, adjust maxmemory-policy, or reduce TTLs; roll back change if caused by growth |
| AOF/RDB persistence misconfigured | BGREWRITEAOF fails or no persistence file present | Restore correct persistence config; verify on stage; only then retry deployment |
| Latency regression post-cutover | --latency spikes; app timeouts | Cut back to previous primary via DNS/config; inspect slowlog and client timeouts |
| Unsafe command introduced | SLOWLOG shows KEYS/SCAN misuse | Revert the change; replace with safe patterns (indexes, limited SCAN) |
Rollback and Recovery Playbooks
Change landed but signals are bad? Roll back first, then investigate offline. Keep these playbooks as scripts in your repo.
Rollback 1: Connection switch
- If you just changed DNS or a config flag to a new primary and see errors:
# Revert DNS or config to previous endpoint
# Flush app connections so pools reconnect
Rollback 2: Role reversal after failed promotion
# Make the old primary P primary again if you promoted R
redis-cli -h P -a "$REDIS_PASS" REPLICAOF NO ONE
# Point apps back to P
# Make R replica of P again
redis-cli -h R -a "$REDIS_PASS" REPLICAOF P <port>
Rollback 3: Bad config pushed to prod
# Replace config file with last known good version
# Attempt a safe restart window
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS CONFIG REWRITE || true
# If restart is required, coordinate a brief failover or maintenance window
Rollback validation
- After any rollback, run the Verification and Diagnostics checks again.
- Confirm app error rate and latency return to baseline.
Building Confidence: Expand the Pilot
Once the pilot is green across several releases, expand coverage in small steps:
- Add keyspace-pattern smoke tests for critical prefixes.
- Validate backup restore by restoring a small dump into a disposable instance and running read-only checks.
- Gate risky config directives behind staging proofs (for example, eviction policy changes, client-output-buffer-limits).
- Integrate a lightweight performance gate using redis-benchmark on stage with low counts to protect against regressions:
redis-benchmark -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASS -n 5000 -t get, set -q
- Add alerts that mirror your verification checks so CI findings match runtime monitoring.
Operations Checklist
Use this as a repeatable runbook for each Redis change.
Planning
- Identify the single change variable (config, version, or client lib).
- Update the inventory table if anything environment-specific changes.
- Write or update smoke tests to cover the change.
Pre-deploy gates
- Local config parse: throwaway Redis boots and replies to PING.
- Stage smoke: auth, SET/GET, and any app-level probe pass.
- Persistence: AOF/RDB setting behaves as expected on stage.
- Replica health: production replica shows link up and near-zero lag.
Cutover (if applicable)
- Freeze high-risk writes if needed.
- Promote replica via managed command or REPLICAOF NO ONE.
- Switch DNS or config to new primary.
Post-deploy verification
- Connectivity: PONG and ACL WHOAMI correct.
- Functional: probe keys set and retrieved.
- Replication: remaining replicas healthy and in sync.
- Latency and error rates at or better than baseline for at least one key cycle.
Rollback readiness
- Previous primary still available and can accept traffic.
- DNS/config change is reversible in minutes.
- Scripts for role switch and verification are present and tested.
Review
- Document what changed and the signals observed.
- Queue the next small automation improvement.
Conclusion
Automating Redis with CI/CD pays off when you keep scope narrow, enforce clear gates, and make every step observable and reversible. Start with a pilot that anyone on the team can run locally and in staging. Validate configuration by actually starting Redis, exercise critical commands with both redis-cli and your app client, and deploy to production through a safe replica cutover with a fast rollback path. Over time, grow the automation by adding targeted checks and performance guards, always keeping one small, verifiable change per rollout. This approach gives you predictable Redis changes without slowing delivery.