Initial Requirements
Make sure you have Erlang and Elixir installed on your machine. For this tutorial, I’m using the following versions:
You’ll also need Docker up and running. I’m using Docker Compose as well, so make sure that’s installed too.
Setting Up the Environment
To get started, we’ll create two separate Phoenix instances:
Before running the instances, there’s still something to do. Navigate into both directories and let’s set up some important files:
Create docker-compose.yml:
in root directory, create docker-compose.yml
:
Make sure the network name is the same across all instances.
Create Dockerfile:
in root directory, create Dockerfile
:
Create entrypoint-dev.sh:
in root directory, create entrypoint-dev.sh
:
Create .env:
in root directory, create .env
:
Edit dev.exs:
To make both instances accessible via localhost, navigate to config/dev.exs
and edit line 12.
Running the Instances
Open your terminal in the root folder of the project and create the network that will connect the containers:
Finally, start the containers with the following command:
Now, the applications should be running on the ports specified in your .env
file. In my case, they’re accessible at:
Configuring Libcluster
Now, let’s set up the cluster for the instances.
Install libcluster:
To start, we need to install libcluster as a dependency. To do this, add it to your mix.exs
file:
Setup Topology
Next, let’s configure the topology. In this example, we’ll use the Gossip strategy, which automatically discovers nodes within a network.
In the config/config.exs
file, add the following at the end, right before import_config:
If you want more control over the nodes, you can also use EPMD Strategy. Be sure to check the libcluster documentation for other available options.
Initializing Libcluster
Now we need to activate this during the application initialization.
Add the following lines to lib/*_instance/application.ex
:
Checking connection
Now, let’s rebuild the instances and watch the logs. Run the following commands in both directories:
When both are running, you should see something like this in one of them (usually the one that started first):
This means our instances have successfully connected and are ready to interact with each other via PubSub!
Configuring PubSub
Now it’s time to set up PubSub to allow interaction between the instances.
In this tutorial, I’ll separate the tasks into two instances:
- Instance 1 will send messages.
- Instance 2 will receive them.
But first, we need to ensure that both instances are using the same PubSub.
To do this, open config/config.exs
in both instances and edit line 22 (or wherever the endpoint configuration is):
Next, in lib/*_instance/application.ex
, update the start function:
Setting Up the Sender
This step is pretty straightforward; all we need to do is call the PubSub.broadcast_from/3 function from somewhere. We’ll use Phoenix LiveView for this.
Create the following files:
lib/first_instance_web/live/sender_live/index.ex
:
lib/first_instance_web/live/sender_live/index.html.heex
:
Now, add the new live route in the browser scope in lib/first_instance_web/router.ex
:
Visit localhost:4000/sender and test clicking the button. If everything is set up correctly, you should see a success message.
Configuring the Receiver
For this tutorial, I’ll initialize the PubSub subscription along with the application to avoid creating another page. With PubSub, you can send messages to any page, but only users connected to that page will receive the messages. By initializing PubSub with the application, it allows me to use it like a messaging app (e.g., RabbitMQ), enabling interactions with events on the receiver regardless of whether there are connected users.
Create the file lib/second_instance/listener.ex
with the following content:
Finally, in application.ex
, add the listener to the list of children:
Final test
Great! Now that you’ve completed the setup, the system should work as expected:
- Open the Docker logs for the receiver (like example_two in my case) using the following command:
-
Go to the web page of the first instance (e.g., example_one) in your browser: localhost:4000/sender
-
Click the button to send the signal. Every time you click the button, you should see the following message appear in the receiver’s logs:
This means the PubSub is working correctly, and the instances are communicating via LibCluster and Phoenix PubSub. Now, your two instances are ready to interact with each other, sending and receiving messages across the cluster.
You can use this method for a variety of use cases, such as:
-
Real-time notifications: Send notifications between different nodes in your cluster, like alerting users across instances about new messages, system events, or live updates.
-
Distributed job processing: Divide tasks across multiple nodes for parallel processing. For example, each instance can handle different parts of a large dataset or workload, and the nodes can coordinate via messages.
-
Multi-instance chat applications: Build a chat system where users connected to different nodes can still exchange messages in real-time using PubSub for communication between instances.
-
Live updates in dashboards: If you have multiple servers handling different data streams, you can send updates to user dashboards in real-time, even if the user is connected to a different node from the one where the data originated.
I’m using this setup with 30+ instances, none of which are aware of each other’s existence, but all receive events from a “central” instance.
Feel free to explore your own ideas and adapt this setup to fit your project’s needs!