RabbitMQ with Docker: A Comprehensive Guide

In this tutorial, we’ll explore how to combine RabbitMQ and Docker to improve message queuing, enhance system reliability, and simplify deployment. Whether you’re an experienced developer or just starting out, this guide will give you the knowledge and practical insights to make the most of RabbitMQ with Docker.

What is Rabbit MQ?

RabbitMQ is a robust and flexible message broker that enables seamless communication between distributed systems. It acts as a mediator, facilitating the transfer of messages between applications, making it easier to build reliable, scalable, and maintainable systems. Whether you’re dealing with microservices, event-driven architectures, or any other distributed system, RabbitMQ provides a reliable foundation for asynchronous communication, ensuring that messages are delivered efficiently and reliably across various components of your application.

In terms of languages, RabbitMQ offers a variety of languages to communicate with the Broker. In this tutorial we will show how to use a plain and simple Java client. Also, in terms of Protocol, RabbitMQ primarily supports AMQP, which is its native and most commonly used protocol. It also provides plugins for MQTT and STOMP. Therefore, RabbitMQ is not compatible out of the box with JMS Standard. However, you can install a JMS plugin to ensure JMS Compatibility as well.

Finally, RabbitMQ is implemented using Erlang OTP (Open Telecom Platform), a powerful and fault-tolerant programming language and runtime environment. Erlang OTP is well-known for its concurrency, distribution, and fault-tolerance capabilities, making it an ideal choice for building distributed and fault-tolerant systems.

Starting RabbitMQ with Docker

Starting RabbitMQ with Docker is fairy simple. Make sure that the Docker daemon is up and running. Then, you can start it as follows:

docker run --rm -it -p 15672:15672 -p 5672:5672 rabbitmq:3-management

On the other hand, you can also use Docker compose to trigger RabbitMQ Docker image. Just add the following docker-compose.yml file:

version: "3.2"
services:
  rabbitmq:
    image: rabbitmq:3-management
    container_name: 'rabbitmq'
    ports:
        - 5672:5672
        - 15672:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_go_net

networks:
  rabbitmq_go_net:
    driver: bridge

Then, execute is as follows:

docker-compose up

Upon successful start, you should see the following logs from your Docker Compose (or Docker) Image:

rabbitmq    | 2024-01-31 09:13:17.543332+00:00 [info] <0.9.0> Time to start RabbitMQ: 4591754 us
rabbitmq    | 2024-01-31 09:13:18.517038+00:00 [info] <0.651.0> accepting AMQP connection <0.651.0> (172.18.0.1:53458 -> 172.18.0.2:5672)
rabbitmq    | 2024-01-31 09:13:18.524572+00:00 [info] <0.651.0> connection <0.651.0> (172.18.0.1:53458 -> 172.18.0.2:5672): user 'guest' authenticated and granted access to vhost '/'

Connecting to RabbitMQ

Firstly, let’s connect to RabbitMQ Console http://localhost:15672/ with the default username and password: (guest/guest)

rabbitmq docker tutorial

Then, you should be able to see the Administration Console of Rabbit MQ:

rabbitmq docker compose

Coding a simple RabbitMQ Producer and Consumer

Our last step will be writing a simple Sender and Receiver to verify the connectivity with RabbitMQ. To code a RabbitMQ Client we need to use RabbitMQ Client libraries in our project:

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.20.0</version>
</dependency>

To get started even faster, if you have JBang shell available ( see here fore more info JBang: Create Java scripts like a pro ), just drop in the following code:

//DEPS com.rabbitmq:amqp-client:5.20.0

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.nio.charset.StandardCharsets;

public class Send {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

The above example creates the Queue “hello” and publishes a text message to it. Next, let’s add also a Receive Class so that we can fetch messages from the Sender:

//DEPS com.rabbitmq:amqp-client:5.20.0

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;

public class Receive {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

Then, from another shell, launch first the Receive Class:

jbang Receive.java
 [*] Waiting for messages. To exit press CTRL+C

Next, start the Sender:

jbang Send.java 

 [x] Sent 'Hello World!'

You should be able to see the following message on the Receiver’s Console:

 [x] Received 'Hello World!'

Finally, you can also acknowledge on the Web Console the new Queue which we have just triggered:

rabbitmq docker

Enabling Extra Plugins with RabbitMQ and Docker

The example application in this tutorial is using the built-in features of RabbitMQ. However, you can also install additional plugins, for example to enable AMQP 1.0 compatibility. In order to do that, you can register the additional plugins in a text file:

[rabbitmq_management,rabbitmq_amqp1_0].

Save the file with the name enabled_plugins.

Then, in the same folder, create the following Docker Compose file which copies in the Container folder /etc/rabbitmq/ the file enabled_plugins:

version: '3.8'

services:
  rabbit:
    image: rabbitmq:3.12-management-alpine
    labels:
      - smallrye-dev-service-amqp=amqp
    ports:
      # AMQP protocol port
      - '5672:5672'
      # HTTP management UI
      - '15672:15672'
    environment:
      - RABBITMQ_DEFAULT_USER=guest
      - RABBITMQ_DEFAULT_PASS=guest
    volumes:
      - './enabled_plugins:/etc/rabbitmq/enabled_plugins:ro'

Conclusion

In this tutorial we’ve explored the powerful synergy between RabbitMQ and Docker. We have showed how to fire up RabbitMQ with Docker, how to connect to the broker’s console and how to send and receive messages with some simple Java examples

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/rabbitmq/basic