ข้ามไปยังเนื้อหา

รัน Redis ด้วย Docker

ทดสอบบน Redis 8.10.2 (redis:8.10.2-alpine), Docker Desktop บน Mac (Apple silicon), ioredis 6.0.0 ตอนรันจริงใช้ชื่อ container ขึ้นต้น redis-b1- ส่วนในบทใช้ชื่อ redis เพื่อให้คัดลอกได้

คุณไม่จำเป็นต้องติดตั้ง Redis บนเครื่อง คำสั่ง docker run คำสั่งเดียวจะดึง official image และสตาร์ทเซิร์ฟเวอร์ที่พร้อมใช้ภายในไม่กี่วินาที

scripts/intro-run-with-docker-run.sh
docker run -d --name redis -p 6379:6379 redis:8-alpine
Flagทำอะไร
-dรันแบบ detached (เบื้องหลัง) เพื่อให้ terminal ยังว่างอยู่
--name redisตั้งชื่อ container ที่คงที่ เพื่ออ้างถึงในคำสั่งถัด ๆ ไป
-p 6379:6379publish port 6379 ของ container ไปยังเครื่อง host ด้วย port เดียวกัน
redis:8-alpineใช้ official Redis 8 image ที่สร้างบน Alpine Linux — เล็กและเร็ว

redis:8-alpine เป็น tag ที่ เลื่อนได้; redis:8.10.2-alpine คือ release เดียว ในวันที่ตรวจสอบบทนี้ทั้งสองชี้ไปที่ image เดียวกัน:

$ docker image ls redis
redis:8-alpine 675f7644831d
redis:8.10.2-alpine 675f7644831d

image ID เดียวกัน วันนี้จึงเหมือนกัน แต่เดือนหน้า 8-alpine จะไม่ใช่ ใช้ tag ลอยสำหรับแล็บชั่วคราว และใช้ tag เต็ม (หรือ digest) ในทุกอย่างที่ต้อง reproduce ได้ — bug report, CI job, compose file คอร์สนี้ pin 8.10.2 ในการทดสอบ

ตรวจว่าเซิร์ฟเวอร์ขึ้นจริงก่อนจะโทษ client:

scripts/intro-run-with-docker-logs.sh
docker logs redis

นี่คือบรรทัดที่สำคัญ (image ของ Redis 8 ยังพิมพ์อีกหนึ่งบล็อกต่อ module ที่ฝังมา — Bloom, search, time series, JSON — ระหว่าง config กับบรรทัด Server initialized):

1:C 30 Sep 2026 05:42:55.041 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 30 Sep 2026 05:42:55.041 * Redis version=8.10.2, bits=64, commit=00000000, modified=1, pid=1, just started
1:M 30 Sep 2026 05:42:55.042 * Running mode=standalone, port=6379.
1:M 30 Sep 2026 05:42:55.048 * Server initialized
1:M 30 Sep 2026 05:42:55.048 * Ready to accept connections tcp

Ready to accept connections tcp คือไฟเขียว และ Redis version= บอกว่าคุณได้เซิร์ฟเวอร์รุ่นไหนจริง ๆ

เมื่อ container รันอยู่ ให้เปิด CLI session แบบ interactive ภายใน container:

scripts/intro-run-with-docker-exec.sh
docker exec -it redis redis-cli

docker exec รันคำสั่งภายใน container ที่ทำงานอยู่ -it จัดสรร TTY แบบ interactive เพื่อให้ได้ prompt 127.0.0.1:6379> ที่คุ้นเคย จากนั้นรันคำสั่ง Redis ใดก็ได้

image ทางการสตาร์ทเซิร์ฟเวอร์ด้วย protected-mode ปิด (พิสูจน์บน image 8.10.2) เพราะภายใน container ทุกอย่างเข้าถึง Redis ผ่าน bridge ของ Docker ไม่ใช่ loopback และ protected mode จะปฏิเสธ นั่นทำให้ การ publish port เป็นรั้วเดียวที่คุณมี:

cli/intro-run-with-docker-1.redis
127.0.0.1:6379> CONFIG GET protected-mode
1) "protected-mode"
2) "no"

อย่าเชื่อคำว่า “ข้อมูลหายเมื่อลบ container” โดยไม่พิสูจน์ — นี่คือสิ่งที่เกิดขึ้นจริง บน container ที่ไม่มี -v ค่าเริ่มต้น save 3600 1 300 100 60 10000 ของ image จะ snapshot ลง writable layer ของ container เอง (/data/dump.rdb):

$ docker run -d --name redis-b1-nov redis:8.10.2-alpine
$ docker exec redis-b1-nov redis-cli SET greeting hello
OK
$ docker stop redis-b1-nov && docker start redis-b1-nov # same container
$ docker exec redis-b1-nov redis-cli GET greeting
hello # survived: stop/start keeps the layer
$ docker rm -f redis-b1-nov && docker run -d --name redis-b1-nov redis:8.10.2-alpine
$ docker exec redis-b1-nov redis-cli GET greeting
# (nil): remove + recreate = gone

ดังนั้น docker stop/start ปลอดภัยแม้ไม่มี volume; docker rm ต่างหากที่ทำลายข้อมูล ต่อ named volume เพื่อให้ข้อมูลอยู่นานกว่า container:

scripts/intro-run-with-docker-volume.sh
docker run -d --name redis -p 6379:6379 -v redisdata:/data redis:8-alpine

ทดลองเดิมด้วย -v redisdata:/data: SET greeting hello, docker stop, docker start → DBSIZE เป็น 1; docker exec redis ls -la /data เห็น dump.rdb ขนาด 110 byte; แล้ว docker rm -f และ container ใหม่เอี่ยม บน volume เดิมตอบ GET greeting → hello Docker สร้าง volume redisdata ให้อัตโนมัติ และ /data คือที่ที่ image ทางการเขียนไฟล์ snapshot

สังเกตว่า snapshot มีอยู่เพราะ docker stop ส่ง SIGTERM และ Redis จะ save ก่อนออกเมื่อมีกฎ save ตั้งไว้ แต่ docker kill (SIGKILL) หรือ crash อาจทำให้ข้อมูลตั้งแต่ snapshot ล่าสุดหายหมด — module Persistence วัดเรื่องนี้ให้ดูจริง

scripts/intro-run-with-docker-lifecycle.sh
# Stop the running container (Redis shuts down cleanly)
docker stop redis
# Start it again — data in the named volume is preserved
docker start redis
# Remove the container entirely (volume survives unless you also delete it)
docker rm redis
scripts/intro-run-with-docker-follow.sh
# Print all logs produced so far
docker logs redis
# Follow logs in real time (Ctrl-C to stop)
docker logs -f redis
docker run -d --name redis -p 6379:6379 redis:8-alpine
docker exec -it redis redis-cli

สตาร์ทเซิร์ฟเวอร์ด้วย --requirepass (เป็น placeholder — ใช้ secret ของคุณเอง และห้าม commit):

scripts/intro-run-with-docker-auth.sh
docker run -d --name redis -p 127.0.0.1:6379:6379 redis:8.10.2-alpine redis-server --requirepass '<your-password>'

reply จริงจาก container นั้น:

$ docker exec redis redis-cli PING
NOAUTH Authentication required.
$ docker exec redis redis-cli -a '<your-password>' PING
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG

-a บน command line รั่วไปใน ps และ shell history จึงมี warning; แนะนำใช้ environment variable REDISCLI_AUTH แทน จากโค้ดแอป error ต่างออกไป เพราะ ioredis 6 เปิด connection ด้วย HELLO ก่อน:

src/intro-run-with-docker-auth.ts
import { Redis } from 'ioredis';
const bad = new Redis('redis://127.0.0.1:6379', { maxRetriesPerRequest: 0 });
bad.on('error', () => {});
// "NOAUTH HELLO must be called with the client already authenticated, otherwise the
// HELLO <proto> AUTH <user> <pass> option can be used ..."
console.log('no password ->', await bad.get('k').catch((e: Error) => e.message));
bad.disconnect();
const ok = new Redis({ host: '127.0.0.1', port: 6379, password: process.env.REDIS_PASSWORD });
console.log('with password ->', await ok.ping()); // PONG
ok.disconnect();

นี่คือข้อความจริงที่ได้ (วัดบน ioredis 6.0.0 กับ Redis 8.10.2) ถ้าเห็น NOAUTH HELLO must be called… ใน log แปลว่าลืมใส่ password ไม่ใช่ปัญหาของ protocol

อะไรที่เก็บไว้ใช้นาน ให้เขียนลงไฟล์ ไฟล์นี้รันด้วย docker compose up -d, สถานะเป็น healthy หลัง health check แรก, สร้าง appendonlydir ใน volume (เพราะ --appendonly yes) และลบด้วย docker compose down -v:

services:
redis:
image: redis:8.10.2-alpine
command: ["redis-server", "--requirepass", "<your-password>", "--appendonly", "yes"]
ports:
- "127.0.0.1:6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "<your-password>", "--no-auth-warning", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
redisdata:

AI ถูกขอให้ทำ “Redis ที่ข้อมูลไม่หายสำหรับ staging” แล้วตอบว่า:

🐛 หา bug ในโค้ดนี้

Terminal window
docker run -d --name redis -p 6379:6379 redis:8-alpine
# data is persisted: Redis saves to /data/dump.rdb
docker rm -f redis && docker run -d --name redis -p 6379:6379 redis:8-alpine
docker exec redis redis-cli GET greeting
เฉลย

มีสอง bug ข้อแรก comment จริงแค่ครึ่งเดียว: snapshot ไปที่ /data/dump.rdb จริง แต่นั่นอยู่ ใน writable layer ของ container ดังนั้น docker rm -f ลบมันทิ้ง เรารันลำดับนี้แล้ว GET greeting สุดท้ายได้ (nil) วิธีแก้คือ volume (-v redisdata:/data) — ลำดับเดียวกันเมื่อมี volume ตอบ hello ข้อสอง -p 6379:6379 publish บน ทุก interface ของ host ให้เซิร์ฟเวอร์ที่ protected-mode no และไม่มีรหัสผ่าน บนเครื่อง staging นั่นคือ Redis ที่เปิดโล่ง publish บน loopback (-p 127.0.0.1:6379:6379) หรือใส่ --requirepass และ firewall

scripts/intro-run-with-docker-fixed.sh
docker run -d --name redis -p 127.0.0.1:6379:6379 -v redisdata:/data redis:8.10.2-alpine redis-server --requirepass '<your-password>'

container คือ process ที่ใช้แล้วทิ้งพร้อมดิสก์ชั่วคราว ดิสก์ชั่วคราวรอด stop และ start แต่ตายไปกับ rm volume คือดิสก์แยกที่เสียบเพิ่ม และ port ที่ publish คือรูบนกำแพง — เจาะตรงไหน (127.0.0.1: หรือทุก interface) เป็นตัวตัดสินว่าใครเข้าถึง Redis ได้ ต้องการเซิร์ฟเวอร์เดิมพรุ่งนี้ ให้ pin tag

ตัวเลือกข้อดีข้อเสีย
รันโดยไม่ mount volumeเริ่มเร็วที่สุด เหมาะกับการทดลองชั่วคราวข้อมูลหายทันทีที่ docker rm container (รอดแค่ stop/start)
แนบ named volume (-v redisdata:/data)ข้อมูลรอดหลัง restart/สร้าง container ใหม่ต้องดูแลวงจรชีวิตของ volume เอง (backup, cleanup) — ชิ้นส่วนเยอะขึ้น
tag ลอย (8-alpine) vs ตายตัว (8.10.2-alpine)tag ลอยได้ patch release ฟรีtag ลอยเปลี่ยนใต้เท้า; tag ตายตัว reproduce ได้แต่ต้องยกเวอร์ชันเอง
รัน container ด้วยค่าเริ่มต้นของ docker runสะดวก เหมาะกับ dev บนเครื่องไม่มีรหัสผ่าน ไม่มี TLS protected-mode no — ไม่ควร deploy ตามนี้
  • “docker stop ทำให้ข้อมูลหาย” ไม่จริง แม้ไม่มี volume: writable layer ของ container เก็บ dump.rdb ไว้จนกว่าจะ docker rm
  • “Redis มี protected mode ดังนั้น publish port ปลอดภัย” image ทางการปิด protected-mode การ map port เป็นเกราะเดียวของคุณ
  • “-a password ใช้ใน script ได้ไม่เป็นไร” มี warning ด้วยเหตุผล มันโผล่ใน process list ใช้ REDISCLI_AUTH
  • “redis:8 กับ redis:8.10.2 เหมือนกัน” เหมือนแค่ในวันที่ pull
  • ลืมแนบ volume แล้วสงสัยว่าทำไมข้อมูลหาย — ถ้าไม่ mount /data กับ named volume ข้อมูลทั้งหมดอยู่แค่ใน writable layer ของ container docker rm ทำลายมันถาวร
  • publish -p 6379:6379 สู่อินเทอร์เน็ตโดยไม่มี firewall — image ทางการไม่ได้ตั้ง authentication มาให้ ถ้า bind ที่ 0.0.0.0 โดยไม่กรองเครือข่าย ใครก็เชื่อมต่อและรันคำสั่งได้
  • รัน container โดยไม่มี --requirepass — ใช้ได้เฉพาะทดลองบนเครื่อง ต้องตั้งรหัสผ่านหรือใช้ ACL ก่อนใช้งานจริง
flag -d ใน docker run -d ทำอะไร?
flag ไหน publish port ของ Redis เพื่อให้ host เชื่อมต่อได้?
จะเปิด redis-cli session แบบ interactive ใน container ที่รันอยู่ได้อย่างไร?
รัน container โดยไม่มี -v, SET key, แล้ว docker stop และ docker start — key ยังอยู่ไหม?
image redis ทางการรันด้วย protected-mode no ผลที่ตามมาคืออะไร?
ioredis 6 เชื่อมต่อเซิร์ฟเวอร์ที่มีรหัสผ่านโดยไม่ใส่รหัส จะ reject ด้วย error ใด?