Streaming & Messaging Azure

LavinMQ Message Broker on Ubuntu 24.04 on Azure User Guide

| Product: LavinMQ 2.9.1 Message Broker on Ubuntu 24.04 LTS on Azure

Overview

LavinMQ is an ultra fast, lightweight open source message queue and streaming server written in Crystal. It speaks AMQP 0.9.1 and is a drop in replacement for RabbitMQ, so existing AMQP clients, tooling and the familiar HTTP management API all work unchanged. LavinMQ is built for high throughput and low memory use, making it a strong fit for asynchronous messaging, task queues and event streaming.

The cloudimg image runs the official upstream cloudamqp/lavinmq:2.9.1 Docker image under a thin systemd wrapper. The broker binds AMQP on TCP :5672 and the built in HTTP management console on TCP :15672. At first boot a per VM cloudimg administrator is created with a random password and the stock guest user is deleted entirely, so the running broker has no default login.

What is included:

  • LavinMQ 2.9.1 (Apache License 2.0) via the official cloudamqp/lavinmq:2.9.1 Docker image
  • Single static Crystal binary, no Erlang runtime and no Erlang cookie to manage
  • Built in web management console enabled out of the box on :15672
  • AMQP 0.9.1 listener on :5672, compatible with any RabbitMQ AMQP client
  • Bundled lavinmqctl and lavinmqperf CLI tools inside the container
  • Docker CE with the Docker Compose v2 plugin
  • Container lavinmq with /data/lavinmq bind mounted for durable broker storage
  • Per VM cloudimg administrator password (32 hex chars) generated at first boot
  • Stock guest user deleted at first boot, so there is no default login
  • Two systemd units: lavinmq.service (oneshot wrapper around docker compose up -d) and lavinmq-firstboot.service (per VM admin bootstrap)
  • 24/7 cloudimg support

Prerequisites

Active Azure subscription, SSH key, VNet and subnet. Standard_B2s (4 GB RAM) is comfortable for production AMQP traffic. NSG inbound: allow 22/tcp from your management CIDR, 5672/tcp (AMQP) from the client CIDRs that need to publish or consume, and 15672/tcp (management console) from the management CIDR.

Step 1 to 3: Deploy and SSH

Deploy the image from the Azure Marketplace, then connect over SSH as azureuser:

ssh azureuser@<vm-ip>

Step 4: Service Status and Versions

sudo systemctl is-active docker.service lavinmq.service lavinmq-firstboot.service
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
sudo ss -tln | grep -E ':5672 |:15672 '

The first command reports active for all three units. docker ps shows the single lavinmq container running cloudamqp/lavinmq:2.9.1 with a healthy status. The listeners on 0.0.0.0:5672 (AMQP) and 0.0.0.0:15672 (management console) confirm the broker is bound and ready.

Docker and LavinMQ services active with the cloudamqp/lavinmq:2.9.1 container healthy and ports 5672 and 15672 listening

Step 5: Read Per VM Credentials

sudo cat /stage/scripts/lavinmq-credentials.log

Pick up LAVINMQ_AMQP_URL, LAVINMQ_MGMT_URL, LAVINMQ_ADMIN_USER and LAVINMQ_ADMIN_PASSWORD. This file is mode 0600 owned by root and holds the unique administrator password generated for this specific VM at first boot.

Step 6: Management API Health and Auth Enforcement

PASS=$(sudo grep '^LAVINMQ_ADMIN_PASSWORD=' /stage/scripts/lavinmq-credentials.log | cut -d= -f2-)
echo "no credentials -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:15672/api/whoami)"
echo "cloudimg creds -> HTTP $(curl -s -o /dev/null -w '%{http_code}' -u "cloudimg:$PASS" http://127.0.0.1:15672/api/whoami)"
curl -s -u "cloudimg:$PASS" http://127.0.0.1:15672/api/users | jq -r '.[].name'

An unauthenticated request to the management API returns HTTP 401, and the same request with the per VM cloudimg credentials returns HTTP 200. Listing the users shows only cloudimg because the stock guest account is deleted at first boot, so there is no default login on the network.

LavinMQ per VM credentials with the password masked, the management API returning 401 without credentials and 200 with the cloudimg credentials, and only the cloudimg user present

PASS=$(sudo grep '^LAVINMQ_ADMIN_PASSWORD=' /stage/scripts/lavinmq-credentials.log | cut -d= -f2-)
curl -s -u "cloudimg:$PASS" http://127.0.0.1:15672/api/overview | jq '{lavinmq_version, node}'
curl -s -u "cloudimg:$PASS" http://127.0.0.1:15672/api/whoami | jq '{name, tags}'

/api/overview reports the LavinMQ version and node name, and /api/whoami round trips the management auth and returns the cloudimg user with the administrator tag.

LavinMQ /api/overview reporting version 2.9.1 with no Erlang runtime, and /api/whoami returning the cloudimg administrator

Step 7: Log in to the Management Console

Browse to http://<vm-ip>:15672/ and log in as cloudimg with the password from Step 5. The console redirects to the login page, where you enter the username and password.

LavinMQ management console login page

Step 8: Overview Dashboard

After login, the Overview page shows the broker version, node uptime and live counters for connections, channels, consumers, exchanges, queues and bindings, along with queued message, data rate and message rate charts. On a freshly booted VM the connection, queue and consumer counts are all zero, which is the baseline before any client connects.

LavinMQ management overview dashboard showing broker statistics and live charts for the cloudimg user

Step 9: Queues and Streams

Click Queues to manage queues. The page lists every queue and provides an Add queue form where you set the virtual host, name, durability and arguments. LavinMQ supports both classic queues and streams.

LavinMQ queues management page with the queue list and the add queue form

Step 10: Connections

Click Connections to see every AMQP client connected to the broker, with the virtual host, user, state, TLS status and protocol for each. This is the first place to look when confirming that a producer or consumer has connected.

LavinMQ connections management page listing active AMQP client connections

Step 11: Declare a Queue and Publish a Message

The management API is RabbitMQ compatible, so you can declare a queue, publish a message and consume it end to end with curl:

PASS=$(sudo grep '^LAVINMQ_ADMIN_PASSWORD=' /stage/scripts/lavinmq-credentials.log | cut -d= -f2-)
curl -s -u "cloudimg:$PASS" -H 'content-type: application/json' \
  -X PUT http://127.0.0.1:15672/api/queues/%2F/hello-world -d '{"durable":true}'
curl -s -u "cloudimg:$PASS" -H 'content-type: application/json' \
  -X POST http://127.0.0.1:15672/api/exchanges/%2F/amq.default/publish \
  -d '{"properties":{},"routing_key":"hello-world","payload":"Hello from cloudimg","payload_encoding":"string"}'
curl -s -u "cloudimg:$PASS" -H 'content-type: application/json' \
  -X POST http://127.0.0.1:15672/api/queues/%2F/hello-world/get \
  -d '{"count":1,"ackmode":"ack_requeue_false","encoding":"auto"}' | jq '.[0].payload'

Declaring the queue returns HTTP 201, publishing returns {"routed":true}, and consuming returns the "Hello from cloudimg" payload. The same workflow is available in the management console under Queues (declare a queue) and each queue's publish and get message panels. The bundled lavinmqctl and lavinmqperf tools are also available via sudo docker exec lavinmq lavinmqctl <command>.

Step 12: Connect from a Client (Python pika example)

import pika

VM_IP = '<vm-ip>'
PASS = '<password-from-step-5>'

creds = pika.PlainCredentials('cloudimg', PASS)
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host=VM_IP, port=5672, virtual_host='/', credentials=creds)
)
ch = conn.channel()
ch.queue_declare(queue='hello', durable=True)
ch.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print('published')
conn.close()

Install the client with pip install pika. The LAVINMQ_AMQP_URL from Step 5 also works directly with pika.URLParameters, kombu, aio-pika, Spring AMQP, the .NET RabbitMQ.Client and any other AMQP 0.9.1 library, because LavinMQ speaks the same protocol as RabbitMQ.

Step 13: Manage Users

List the broker users and add or remove accounts with the bundled lavinmqctl:

sudo docker exec lavinmq lavinmqctl list_users

The list shows the per VM cloudimg administrator only. To add an application user, run sudo docker exec lavinmq lavinmqctl add_user <name> <password> followed by sudo docker exec lavinmq lavinmqctl set_permissions -p / <name> ".*" ".*" ".*", or use the Users page in the management console. Grant the administrator tag only to accounts that manage the broker.

Step 14: Add TLS with a Domain and Let's Encrypt

Put nginx in front of the management console and obtain a certificate. Replace the placeholders with your own domain and email:

sudo apt-get update && sudo apt-get install -y nginx python3-certbot-nginx
sudo tee /etc/nginx/sites-available/lavinmq <<'EOF'
server {
    listen 80;
    server_name <your-domain>;
    location / {
        proxy_pass http://127.0.0.1:15672;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF
sudo ln -sf /etc/nginx/sites-available/lavinmq /etc/nginx/sites-enabled/lavinmq
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d <your-domain> -m <your-email> --agree-tos --non-interactive --redirect

Certbot adds the SSL server block and a :80 to :443 redirect, then reloads nginx. Renewal runs automatically via the certbot.timer systemd unit. For TLS directly on the AMQP listener (5671/tcp), bind mount a certificate into the container and enable the TLS listener in the LavinMQ configuration.

Step 15: Backups

Export the broker definitions (users, vhosts, exchanges, queues, bindings and policies) with the broker running, and snapshot the data directory for message contents. Replace the placeholder with your own destination:

PASS=$(sudo grep '^LAVINMQ_ADMIN_PASSWORD=' /stage/scripts/lavinmq-credentials.log | cut -d= -f2-)
curl -s -u "cloudimg:$PASS" http://127.0.0.1:15672/api/definitions > <backup-dir>/lavinmq-defs.json
sudo tar czf <backup-dir>/lavinmq-data.tgz -C /data lavinmq

Periodically copy the backup directory to Azure Blob Storage (az storage blob upload-batch) for off VM retention. Restore is a POST of the definitions JSON to /api/definitions with the broker running, plus untarring the data directory back to /data/lavinmq with the broker stopped.

Step 16: Logs and Troubleshooting

sudo docker logs lavinmq --tail 80
sudo journalctl -u lavinmq.service --no-pager -n 50
sudo journalctl -u lavinmq-firstboot.service --no-pager -n 50
sudo docker exec lavinmq lavinmqctl status

Tail container logs with docker logs --tail for a snapshot, or docker logs lavinmq -f for a live follow. The systemd unit logs cover the wrapper service and the first boot admin bootstrap. lavinmqctl status prints the broker node state.

Security

  • Per VM cloudimg administrator password (32 hex chars from openssl rand) generated at first boot
  • Stock guest user is deleted at first boot, so there is no default login on the network
  • LavinMQ restricts its default user to loopback by default, so guest is never reachable over the network even during the brief first boot window before it is removed
  • AMQP listener 5672/tcp and management console 15672/tcp are bound on the host, restrict them via NSG to known client and management CIDRs
  • LavinMQ is a single Crystal binary with no Erlang runtime, reducing the attack surface compared with brokers that ship a full language VM
  • Optional TLS on the management console via nginx and certbot (Step 14), and optional TLS on the AMQP listener (5671/tcp)
  • Broker data persists under /data/lavinmq, kept off the running container's writable layer

Support

cloudimg provides 24/7/365 expert technical support. Guaranteed response within 24 hours, one hour average for critical issues. Contact support@cloudimg.co.uk.