用 Docker 跑一个 Redis,本该是十分钟的事。可一换成 Redis Stack,特别是要固定到某个具体版本时,端口、密码、持久化、模块参数只要有一项没配对,容器就会"起来了但连不上"。
这篇以 redis/redis-stack-server:6.2.6-v19 为例,把一份可以直接抄的 Compose 部署方案拆开讲清楚,从环境准备一直讲到健康检查。
一、为什么用 Compose,而不是一行 docker run
单个 docker run 当然能把服务跑起来,但参数一多就不好维护:端口、数据卷、环境变量、健康检查全挤在一行里,改动一次就要重敲一遍,换台机器还容易漏参数。
Compose 把这些统一写进一份声明式的 YAML,配置本身就成了文档,docker-compose up -d 一条命令即可复现整套环境,也方便随代码一起做版本管理。
对 Redis Stack 来说还有一层现实原因:它需要靠 REDIS_ARGS 这类环境变量把参数传给底层 Redis,写进 Compose 文件,比塞进冗长的命令行清晰得多。
二、先认识 redis-stack-server:6.2.6-v19
Redis Stack 并不是 Redis 的替代品,而是在 Redis 内核之上打包了五个模块,让一个实例同时具备搜索、文档、图和时间序列等能力:
- RediSearch:全文搜索与二级索引;
- RedisJSON:JSON 文档的存取与查询;
- RedisGraph:图数据结构的存储与查询;
- RedisTimeSeries:时间序列数据的写入与查询;
- RedisBloom:布隆过滤器等概率型数据结构。
官方为 Redis Stack 提供了两个镜像,选错很常见:
redis/redis-stack:包含 server 和 RedisInsight,适合本地开发,会同时暴露 6379(Redis)和 8001(RedisInsight)两个端口;redis/redis-stack-server:只包含 server,适合生产部署,仅暴露 6379。本文用的就是这一个。
本次的 6.2.6-v19 是 Redis Stack Server 6.2.6 的一个维护版本,内置的 Redis 内核版本为 6.2.17,所含模块版本如下:
| 模块 | 版本 |
|---|---|
| RediSearch | 2.6.26 |
| RedisJSON | 2.4.11 |
| RedisGraph | 2.10.12 |
| RedisTimeSeries | 1.8.16 |
| RedisBloom | 2.4.13 |
官方把这次更新的紧急程度标为 HIGH ,主要修复了 Redis Query Engine 中几处可能导致 FT.SEARCH 崩溃的问题。如果线上还在更早的 6.2.6 系列上,升级到这个版本会比较稳妥。
但有一条必须提前讲清楚:这个版本内置的 Redis 内核是 6.2.17 ,而 2025 年 10 月披露的 CVE-2025-49844 是一个 CVSS 8.8 级别的高危认证后远程代码执行漏洞,影响 Redis < 6.2.20 的所有版本,通过 Lua 脚本引擎的 use-after-free 缺陷触发------只要攻击者拥有合法账号能连进来、能执行 EVAL/EVALSHA,就有机会在 redis-server 进程内执行任意代码。6.2.6-v19 的内核无法通过小版本升级来修复这个问题,因此网络隔离与攻击面缩减就是部署这个版本时最重要的两件事,下文会单独展开。
客户端的最低版本建议,官方也一并给出:Java 端 Jedis 5.2.0 及以上,Python 端 redis-py 5.2.1 及以上,Node.js 端 node-redis 4.7.0 及以上,.NET 端 NRedisStack 0.13.2 及以上,Go 端 go-redis 9.7.0 及以上。
三、环境准备与目录规划
先确认 Docker 与 Compose 已就位:
yaml
docker version
Client: Docker Engine - Community
Version: 19.03.12
API version: 1.40
Go version: go1.13.10
Git commit: 48a66213fe
Built: Mon Jun 22 15:46:54 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.12
API version: 1.40 (minimum version 1.12)
Go version: go1.13.10
Git commit: 48a66213fe
Built: Mon Jun 22 15:45:28 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
yaml
docker-compose version
1.29.1, build c34c88b2
docker-py version: 5.0.0
CPython version: 3.7.10
OpenSSL version: OpenSSL 1.1.0l 10 Sep 2019
再规划一个专门的目录,把数据和配置都收在一处,便于备份和迁移:
bash
mkdir -p /opt/redis-stack/{data,conf}
cd /opt/redis-stack
启动前顺手看一眼 6379 端口有没有被占用,避免起不来才发现冲突:
perl
ss -lntp | grep 6379
四、编写 docker-compose.yml
在 /opt/redis-stack 下新建 docker-compose.yml:
bash
services:
redis-stack:
image: redis/redis-stack-server:6.2.6-v19
container_name: redis-stack-server
restart: unless-stopped
ports:
# 默认仅绑定宿主机回环地址,不暴露到公网
- "127.0.0.1:6379:6379"
environment:
- REDIS_ARGS=--requirepass ${REDIS_PASSWORD} --appendonly yes --appendfsync everysec --maxmemory 768mb --maxmemory-policy allkeys-lru --rename-command CONFIG "1d7f729e3a5b_${REDIS_PASSWORD}_CONFIG" --rename-command FLUSHDB "" --rename-command FLUSHALL "" --rename-command EVAL "" --rename-command EVALSHA ""
volumes:
- ./data:/data
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
几个关键项逐一说明:
- image :锁定到具体版本号,不要用
latest,否则某天镜像更新可能带来不可预期的行为变化; - ports :写成
"127.0.0.1:6379:6379",只绑定宿主机回环地址 ,而不是默认的0.0.0.0(所有网卡)。Docker 端口映射的默认行为是监听全部网卡------写成"6379:6379"就等同于把 Redis 挂到了公网 IP 上,这是最容易被忽略、也最危险的写法; - environment :把 Redis 参数交给
REDIS_ARGS,这里额外通过--rename-command做了几件事:把EVAL/EVALSHA重命名为空字符串直接禁用(堵住 CVE-2025-49844 的攻击入口),把FLUSHDB/FLUSHALL同样禁用防止误删,把CONFIG重命名为带密码的复杂字符串以防被低权限连接篡改配置; - volumes :把宿主机的
./data挂到容器的/data,容器重建数据也不会丢; - healthcheck :带密码的连接需要显式传
-a,否则检查会一直失败; - deploy.resources:给容器设内存和 CPU 上限,避免单个服务耗尽宿主机资源。
五、配置与持久化:为什么必须用 REDIS_ARGS
这是 redis-stack-server 最容易踩的坑:不要用 command 去覆盖容器的启动命令。
用一个 command: redis-server --save ... 覆盖入口脚本,模块不会按预期加载,服务还可能进入 protected mode 而对外不可访问------容器看着是起来了,客户端却连不上。正确做法是保持镜像默认入口不动,把参数通过 REDIS_ARGS 环境变量传给底层 Redis。
密码等敏感值建议放到同目录的 .env 文件,Compose 会自动读取,避免把密码硬编码进 YAML:
ini
# /opt/redis-stack/.env
REDIS_PASSWORD=ChangeMe_2026
REDIS_ARGS 里常用的参数包括:
--requirepass <password>:设置访问密码;--appendonly yes:开启 AOF 持久化,比 RDB 更不容易丢数据;--appendfsync everysec:每秒同步一次,兼顾性能与安全;--maxmemory 768mb与--maxmemory-policy allkeys-lru:限制可用内存并指定淘汰策略,取值应略小于容器的内存上限;--rename-command EVAL ""/--rename-command EVALSHA "":将 Lua 脚本相关命令重命名为空字符串,等于直接禁用------这是针对 CVE-2025-49844 的关键缓解手段;--rename-command FLUSHDB ""/--rename-command FLUSHALL "":禁用清库命令,防止误操作或攻击者删数据;--rename-command CONFIG "<复杂字符串>":将CONFIG重命名为只有运维知道的名字,避免低权限连接动态改配置。
业务依赖 EVAL 的例外处理:个别框架(如某些 Lua 缓存层、限流组件的脚本模式、Spring Cache 的部分 Lua 实现)会用到 EVAL/EVALSHA。启用前先在测试环境确认应用日志里是否有 EVAL 相关调用,如确需使用,请:①删除 YAML 中对 EVAL/EVALSHA 的 rename-command 行;②确保 Redis 密码强度足够(16位以上混合字符);③禁止任何未经认证的网络可达性;④应用账号通过 ACL 限制只能执行必要的命令前缀。
除了 REDIS_ARGS,镜像还提供针对各模块的环境变量:REDISEARCH_ARGS、REDISJSON_ARGS、REDISTIMESERIES_ARGS、REDISBLOOM_ARGS。例如给时序数据设置保留策略,可以写成 REDISTIMESERIES_ARGS=RETENTION_POLICY=20。
如果你更习惯一整份配置文件,也可以把本地的 redis-stack.conf 挂载到容器内的 /redis-stack.conf,由它接管配置。
至于持久化本身,数据落在容器的 /data 目录下。配合前面挂载的 ./data,即使执行 docker-compose down 再重建,数据依然还在。
六、启动、验证与连接
启动并在后台运行:
docker-compose up -d
docker-compose ps
当状态显示为 Up (healthy),说明容器与健康检查都通过了。
接着进入容器验证服务是否正常应答:
perl
docker exec -it redis-stack-server redis-cli -a ChangeMe_2026 --no-auth-warning ping
# 返回 PONG
确认五个模块都已加载:
css
docker exec -it redis-stack-server redis-cli -a ChangeMe_2026 --no-auth-warning MODULE LIST
写入一条数据试试手:
css
docker exec -it redis-stack-server redis-cli -a ChangeMe_2026 --no-auth-warning SET hello world
如果宿主机上装有 redis-cli,也可以直接连 127.0.0.1:6379。因为我们默认只绑定了回环地址 ,外部机器是无法通过网络直接访问这个端口的------这正是我们想要的效果。需要图形化界面的话,可以在本机运行 RedisInsight 容器通过 --network host 或连 127.0.0.1:6379。
七、安全加固:本版本必须做的三件事
这个版本的内置内核停在 6.2.17,而 CVE-2025-49844 修复于 6.2.20------我们没法靠升级内核来修它,所以下面三件事不是"建议",是必须做到,否则这个实例一旦暴露到不可信网络就是高危。
1. 端口只绑定 127.0.0.1,绝不直接放公网
Docker 的端口映射 "6379:6379" 默认行为是监听 0.0.0.0:6379,也就是宿主机的所有网卡------只要你的服务器有公网 IP,任何人都能尝试连这个端口。我们在配置里已经写成 "127.0.0.1:6379:6379",这是第一道也是最重要的防线。
如果同机部署的应用(比如本机的 Node/Python/Java 服务)需要访问 Redis,127.0.0.1:6379 就足够了。
如果需要从其他机器访问(包括本地电脑远程管理服务器上的 Redis),走 SSH 隧道或 VPN,不要放开端口。最简单的 SSH 隧道用法:
perl
# 在你本地机器执行,把远程 6379 映射到本地 6379
ssh -L 6379:127.0.0.1:6379 your_user@your_server_ip -N
执行后本地程序连 127.0.0.1:6379 就相当于连到了服务器上的 Redis,整条链路走 SSH 加密,且服务器端不需要在防火墙开放 6379。
如果是多台服务器之间的内网访问,至少要做到三层限制:云安全组/防火墙只放行业务服务器内网 IP;Docker 网络使用自定义 bridge 网络而非 host 模式;Redis 侧配合 ACL 做权限分级。
2. 禁用 Lua 脚本(EVAL/EVALSHA),堵住 CVE-2025-49844 的攻击入口
CVE-2025-49844 是 Redis Lua 脚本引擎中的 use-after-free 内存破坏漏洞。已认证的攻击者通过构造恶意 Lua 脚本,可以在 redis-server 进程内执行任意代码------一旦成功,进程里所有缓存数据、会话令牌、业务状态全部泄露,Redis 通常又以较高权限运行,可能被用来横向移动。
我们在 REDIS_ARGS 中已通过 --rename-command EVAL "" 和 --rename-command EVALSHA "" 将这两个命令彻底禁用。Redis 6.2 不支持 7.0+ 的 disable-commands 指令,rename-command 设为空字符串是官方文档中该版本唯一可靠的禁用方式。
如果业务框架确实依赖 EVAL(例如某些 Lua 限流脚本、Spring Data Redis 的脚本执行模式),请先在测试环境禁用后观察应用日志是否报错。确需保留的话:①移除 YAML 中对 EVAL/EVALSHA 的重命名;②密码必须 16 位以上、包含大小写+数字+特殊字符;③通过 Redis ACL 创建仅具有必要命令权限的应用账号,不要所有服务共用默认账号;④端口仍然不可以对公网开放。
3. 密码强认证 + 危险命令重命名 + 内存上限
- 密码不要用示例中的
ChangeMe_2026,上线前替换成自己生成的强密码; FLUSHDB/FLUSHALL已经被重命名为空,防止任何连接一键清空数据;CONFIG被重命名为带有密码片段的复杂字符串,运维需要时用redis-cli执行重命名后的完整命令名;maxmemory配了硬上限,Redis 不会无限吃掉宿主机内存。
关于 Redis Functions 的补充说明 :Redis Functions 是 7.0 才引入的功能,6.2.x 内核不支持,因此我们不需要额外处理
fcall等命令,只需要禁用 EVAL/EVALSHA 即可覆盖本版本全部 Lua 执行路径。
八、其他常见坑
- 用
command覆盖入口 :模块未加载、protected mode 拦截访问,改用REDIS_ARGS; - 设了密码却没给 healthcheck 传密码 :容器会长期停在 unhealthy,记得加
-a; - 端口冲突 :启动前用
ss -lntp | grep 6379确认端口空闲,或改映射端口; - maxmemory 与容器内存不匹配 :
maxmemory要略小于容器内存上限,避免被 OOM 杀掉; - 图省事用
latest:版本漂移带来的问题最难排查,务必锁定版本号。
结语
Redis Stack 把搜索、JSON、图、时序、概率结构这几件事收进了一个镜像,而 Compose 让整套部署变成一份可复制的 YAML。真正需要留心的地方不只是"把服务跑起来":锁死版本号、始终用 REDIS_ARGS 而不是 command 传配置是基本功;对于 6.2.6-v19 这个内核停在 6.2.17 的版本,端口只绑回环、禁用 Lua 脚本、强密码认证这三件事是不可省略的安全底线。
把这些都做到,docker-compose up -d 之后,你得到的就是一个带着完整模块能力、开着持久化、配了健康检查,且在已知高危漏洞面前收缩了攻击面的 Redis 实例。