Basic Docker Compose and Build a Redis Cluster with Docker Compose

Lim Yee Jie
8 min readJan 23, 2024

--

Docker Compose

As demonstrated in the Redis Cluster example above, we need to create numerous containers and manage aspects such as networking and data volumes for these containers. Handling these tasks one by one with individual Docker commands can be cumbersome. Therefore, Docker Compose can address this issue.

Docker Compose can be used to define and run multiple containers, configuring the services of an application through a YAML file. Subsequently, a single command can be used to create and start all services based on the configuration.

Create a yaml file
Command: $ nano docker-compose.yaml

# docker-compose.yml
services:
test:
image: redis

Usage

# Under your docker compose file's path
$ docker compose up # Execute (similar with docker run -it)
$ docker compose up -d # Execute the container in background (similar with docker run -d)
# If your docker compose yaml file name is not called "docker-compose.yaml"
$ docker-compose -f /path/to/your/project/your-custom-name.yml up -d


# Check container
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4035917143c5 redis "docker-entrypoint.s…" 4 minutes ago Up 3 minutes 6379/tcp docker-compose-testing-test-1

Docker compose yaml details:

Reference: https://docs.docker.com/compose/compose-file/

Top level:

  1. services: Configuration of the container, you can think that it is similar with the docker run command.
  2. networks: Custom Network Manage.create the custom network, and the services side can reference this network configuration, it can be used with several services at the same time.
  3. volumes: Managing volumes. Can be used with several services at the same time.
  4. version: Docker compose version.
#top level:
version: "2.21"
services:
nginx-demo: # service name (create a container), it's not a container name
container_name: nginx-1
image: nginx # image name
restart: always # optional: restart strategy
networks: # reference with networks top level
- my-frontend-network # you can call/reference the custom network
volumes: # reference with volumes top level
- nginx_volume:/usr/share/nginx/html # call the specific volume
environment:
APP_ENV: dev
dns: 8.8.8.8
depends_on:
- db # Build this container (nginx) after the container in db section created
ports:
- 8000:80

# other service (create other container)
db:
image: mysql
container_name: mysql-1
volumes:
- db_volume:/var/lib/mysql
networks:
- my-frontend-network
environment:
MYSQL_ROOT_PASSWORD: 123456
ports: 33060:3306


networks: # top level
my-frontend-network: # name of network for services side reference it
name: app-network # name of customer network
driver: bridge
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
ip_range: 172.28.5.0/24
gateway: 172.28.5.254
aux_addresses:
host1: 172.28.1.5
host2: 172.28.1.6
host3: 172.28.1.7
options:
foo: bar
bax: "0"

volumes: # top level
db_volume:
name: "mysql-volume"
nginx_volume:
name: "nginx-volume"

Docker compose command:
There are many command similar with docker.

Options:
--ansi string Control when to print ANSI control characters
("never"|"always"|"auto") (default "auto")
--compatibility Run compose in backward compatibility mode
--dry-run Execute command in dry run mode
--env-file stringArray Specify an alternate environment file.
-f, --file stringArray Compose configuration files
--parallel int Control max parallelism, -1 for unlimited (default -1)
--profile stringArray Specify a profile to enable
--progress string Set type of progress output (auto, tty, plain, quiet) (default "auto")
--project-directory string Specify an alternate working directory
(default: the path of the, first specified, Compose file)
-p, --project-name string Project name

Commands:
build Build or rebuild services
config Parse, resolve and render compose file in canonical format
cp Copy files/folders between a service container and the local filesystem
create Creates containers for a service.
down Stop and remove containers, networks
events Receive real time events from containers.
exec Execute a command in a running container.
images List images used by the created containers
kill Force stop service containers.
logs View output from containers
ls List running compose projects
pause Pause services
port Print the public port for a port binding.
ps List containers
pull Pull service images
push Push service images
restart Restart service containers
rm Removes stopped service containers
run Run a one-off command on a service.
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker Compose version information
wait Block until the first service container stops
  1. docker compose config: Parse, resolve and render compose file in canonical format
# it can check your docker-compose.yml file is correct or not
$ docker compose config
ERROR: In file './docker-compose.yml', volume 'db_volume' must be a mapping not an array.

# Successful Case:
$ docker compose config # if no error, return back the details information
name: docker-compose-testing
services:
db:
...
nginx-demo:
...
volumes: # in config will show some details information
- type: volume
source: nginx_volume
target: /usr/share/nginx/html
volume: {}
networks:
...

volumes:
...

2. docker compose create <service_name>: docker-compose.yaml file can create many images at the same time, but you can using this command to create a specific image and run container.

$ docker compose create db # create mysql
[+] Creating 2/0
✔ Volume "mysql-volume" Created 0.0s
✔ Container mysql-1 Created

3. docker compose up: create and run all service (container)

$ docker compose up -d  # -d option: run in background
[+] Running 3/3
✔ Network app-network Created 0.1s
✔ Container mysql-1 Started 0.0s
✔ Container nginx-1 Started

4. docker compose ps: check the running compose container

$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mysql-1 mysql "docker-entrypoint.sh mysqld" db 8 minutes ago Up 8 minutes 33060/tcp, 0.0.0.0:33060->3306/tcp, :::33060->3306/tcp
nginx-1 nginx "/docker-entrypoint.sh nginx -g 'daemon off;'" nginx-demo 8 minutes ago Up 8 minutes 0.0.0.0:8000->80/tcp, :::8000->80/tcp

5. docker compose down: Deleted anythings created by this docker compose

$ docker compose down
[+] Running 3/3
✔ Container mysql-1 Removed 0.0s
✔ Network docker-compose-testing_default Removed 0.6s
✔ Network app-network Removed

6. docker compose images

$ docker compose images
CONTAINER REPOSITORY TAG IMAGE ID SIZE
mysql-1 mysql latest 73246731c4b0 619MB
# Successful

$ docker compose ps -a
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mysql-1 mysql "docker-entrypoint.sh mysqld" db 2 minutes ago Created

Build a Redis Cluster with Docker Compose

Create volume (mount) file first:

# run this for loop to create each redis container configuration file
for port in $(seq 1 6); do
mkdir -p /redis/node-${port}/conf
touch /redis/node-${port}/conf/redis.conf
cat << EOF >/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

Write the docker-compose.yaml file:

version: "3"
services:
redis-1:
image: redis
command: ["redis-server","/etc/redis/redis.conf"]
volumes:
- /redis/node-1/data:/data
- /redis/node-1/conf/redis.conf:/etc/redis/redis.conf
ports:
- 6371:6379
- 16371:16379
networks:
redis_network:
ipv4_address: 172.38.0.11
redis-2:
image: redis
command: ["redis-server", "/etc/redis/redis.conf"]
volumes:
- /redis/node-2/data:/data
- /redis/node-2/conf/redis.conf:/etc/redis/redis.conf
ports:
- "6372:6379"
- 16372:16379
networks:
redis_network:
ipv4_address: 172.38.0.12
redis-3:
image: redis
command: ["redis-server", "/etc/redis/redis.conf"]
volumes:
- /redis/node-3/data:/data
- /redis/node-3/conf/redis.conf:/etc/redis/redis.conf
ports:
- "6373:6379"
- 16373:16379
networks:
redis_network:
ipv4_address: 172.38.0.13
redis-4:
image: redis
command: ["redis-server", "/etc/redis/redis.conf"]
volumes:
- /redis/node-4/data:/data
- /redis/node-4/conf/redis.conf:/etc/redis/redis.conf
ports:
- "6374:6379"
- 16374:16379
networks:
redis_network:
ipv4_address: 172.38.0.14
redis-5:
image: redis
command: ["redis-server", "/etc/redis/redis.conf"]
volumes:
- /redis/node-5/data:/data
- /redis/node-5/conf/redis.conf:/etc/redis/redis.conf
ports:
- "6375:6379"
- 16375:16379
networks:
redis_network:
ipv4_address: 172.38.0.15
redis-6:
image: redis
command: ["redis-server", "/etc/redis/redis.conf"]
volumes:
- /redis/node-6/data:/data
- /redis/node-6/conf/redis.conf:/etc/redis/redis.conf
ports:
- "6376:6379"
- "16376:16379"
networks:
redis_network:
ipv4_address: 172.38.0.16
networks:
redis_network:
driver: bridge
ipam:
config:
- subnet: 172.38.0.0/16
gateway: 172.38.0.1

Command:

# check docker-compose.yaml is correct or not
$ docker compose config

# Run docker compose
root@jielim36:/home/jielim36/redis-cluster-docker-compose# docker compose up -d
[+] Running 7/7
✔ Network redis-cluster-docker-compose_redis_network Created 0.1s
✔ Container redis-cluster-docker-compose-redis-4-1 Started 0.1s
✔ Container redis-cluster-docker-compose-redis-3-1 Started 0.1s
✔ Container redis-cluster-docker-compose-redis-5-1 Started 0.1s
✔ Container redis-cluster-docker-compose-redis-1-1 Started 0.1s
✔ Container redis-cluster-docker-compose-redis-6-1 Started 0.1s
✔ Container redis-cluster-docker-compose-redis-2-1 Started 0.1s

# check running container
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
redis-cluster-docker-compose-redis-1-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-1 23 seconds ago Up 20 seconds 0.0.0.0:6371->6379/tcp, :::6371->6379/tcp, 0.0.0.0:16371->16379/tcp, :::16371->16379/tcp
redis-cluster-docker-compose-redis-2-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-2 23 seconds ago Up 20 seconds 0.0.0.0:6372->6379/tcp, :::6372->6379/tcp, 0.0.0.0:16372->16379/tcp, :::16372->16379/tcp
redis-cluster-docker-compose-redis-3-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-3 23 seconds ago Up 20 seconds 0.0.0.0:6373->6379/tcp, :::6373->6379/tcp, 0.0.0.0:16373->16379/tcp, :::16373->16379/tcp
redis-cluster-docker-compose-redis-4-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-4 23 seconds ago Up 20 seconds 0.0.0.0:6374->6379/tcp, :::6374->6379/tcp, 0.0.0.0:16374->16379/tcp, :::16374->16379/tcp
redis-cluster-docker-compose-redis-5-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-5 23 seconds ago Up 20 seconds 0.0.0.0:6375->6379/tcp, :::6375->6379/tcp, 0.0.0.0:16375->16379/tcp, :::16375->16379/tcp
redis-cluster-docker-compose-redis-6-1 redis "docker-entrypoint.sh redis-server /etc/redis/redis.conf" redis-6 23 seconds ago Up 20 seconds 0.0.0.0:6376->6379/tcp, :::6376->6379/tcp, 0.0.0.0:16376->16379/tcp, :::16376->16379/tcp

# If created successful, enter to the redis-1 to create cluster
$ redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# Check again, enter to the redis-cli with cluster
root@b59ad0535541:/data$ redis-cli -c
# check the relationship of all redis nodes
127.0.0.1:6379> cluster nodes
94cce23d515a45ca974016bebf8354c939cd686b 172.38.0.11:6379@16379 myself,master - 0 1705657020000 1 connected 0-5460
2ddaeb2cbce78eee11d02de916ef87ed9cd02a1a 172.38.0.12:6379@16379 master - 0 1705657020000 2 connected 5461-10922
c77d4c93aa9ee2abb593abacfd9cfd7a5caae0df 172.38.0.13:6379@16379 master - 0 1705657021585 3 connected 10923-16383
af8f4638ff90e4aa04797a0cb6fa6a517ed041d4 172.38.0.15:6379@16379 slave 94cce23d515a45ca974016bebf8354c939cd686b 0 1705657021585 1 connected
18c33a87f82726cbc77cf67f7a72fdd6ecc15dda 172.38.0.16:6379@16379 slave 2ddaeb2cbce78eee11d02de916ef87ed9cd02a1a 0 1705657020000 2 connected
2fb725e35588b2edc34d8d3bc09d85593f2fd963 172.38.0.14:6379@16379 slave c77d4c93aa9ee2abb593abacfd9cfd7a5caae0df 0 1705657021000 3 connected

Command: $ redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

  • redis-cli: Redis command-line client tool.
  • --cluster create: Command to create a Redis cluster.
  • 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379: Lists the various nodes in the cluster, including their IP addresses and port numbers. Here, 6 nodes are listed.
  • --cluster-replicas 1: Sets the number of replicas for each master node to 1, meaning each master node will have one corresponding replica node.

You can provide more or fewer nodes as needed, and Redis will automatically configure the cluster accordingly. In this example, Redis inferred that you intended to create a cluster with 3 master nodes, each having a corresponding replica node.

Redis Cluster Relationship Diagram:

Usage:

# Enter into redis client with cluster mode
$ redis-cli -c
127.0.0.1:6379>

# Check cluster
127.0.0.1:6379> cluster nodes
11ef9dda18ee2fed754e5ce8baf0b04c2686ce56 172.38.0.12:6379@16379 master - 0 1705549415000 2 connected 5461-10922
b795fbd6344355a3a8d8bc9eae43b09553430874 172.38.0.16:6379@16379 slave 11ef9dda18ee2fed754e5ce8baf0b04c2686ce56 0 1705549416000 2 connected
b0954d066f6119abb5cc94307d7f66f6dabeaec4 172.38.0.15:6379@16379 slave 6d3685e325d476fadb9e0bd49a39b8406d9431a9 0 1705549416526 1 connected
6d3685e325d476fadb9e0bd49a39b8406d9431a9 172.38.0.11:6379@16379 myself,master - 0 1705549415000 1 connected 0-5460
900fb4a71efc86030b42230b10de2d25942c9e9a 172.38.0.14:6379@16379 slave e66e9af15132cd2b89f55b5fb890d2f18627aeed 0 1705549415000 3 connected
e66e9af15132cd2b89f55b5fb890d2f18627aeed 172.38.0.13:6379@16379 master - 0 1705549416426 3 connected 10923-16383

# usage
127.0.0.1:6379> set a b
-> Redirected to slot [15495] located at 172.38.0.13:6379 # it is save in node 3 (master)
OK
172.38.0.13:6379> get a
"b"

172.38.0.13:6379> set key01 value01
OK
172.38.0.13:6379> get key01
"value01"

# We known the data is save in redis-3, we try to stop this container
# In linux
root@jielim36:/home/jielim36# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ccc478e3cb5f redis "docker-entrypoint.s…" 35 minutes ago Up 35 minutes 0.0.0.0:6376->6379/tcp, :::6376->6379/tcp, 0.0.0.0:16376->16379/tcp, :::16376->16379/tcp redis-6
c78636d05676 redis "docker-entrypoint.s…" 36 minutes ago Up 36 minutes 0.0.0.0:6375->6379/tcp, :::6375->6379/tcp, 0.0.0.0:16375->16379/tcp, :::16375->16379/tcp redis-5
b701b548cdf8 redis "docker-entrypoint.s…" 37 minutes ago Up 37 minutes 0.0.0.0:6374->6379/tcp, :::6374->6379/tcp, 0.0.0.0:16374->16379/tcp, :::16374->16379/tcp redis-4
7176ac3adb31 redis "docker-entrypoint.s…" 37 minutes ago Up 37 minutes 0.0.0.0:6373->6379/tcp, :::6373->6379/tcp, 0.0.0.0:16373->16379/tcp, :::16373->16379/tcp redis-3
ac8020c72f3f redis "docker-entrypoint.s…" 37 minutes ago Up 37 minutes 0.0.0.0:6372->6379/tcp, :::6372->6379/tcp, 0.0.0.0:16372->16379/tcp, :::16372->16379/tcp redis-2
3918d85a6719 redis "docker-entrypoint.s…" 40 minutes ago Up 40 minutes 0.0.0.0:6371->6379/tcp, :::6371->6379/tcp, 0.0.0.0:16371->16379/tcp, :::16371->16379/tcp redis-1

# stop the redis-3
root@jielim36:/home/jielim36: docker stop redis-3
redis-3

# And we go back to the redis, and trying to get the key "a" again
127.0.0.1:6379> get a
-> Redirected to slot [15495] located at 172.38.0.14:6379 # As you can see, you can get the data from the redis-4 now (slave node of the redis-3)
"b" # retrieve data successful

--

--