LavinMQ Message Broker on Ubuntu 24.04 on Azure User Guide
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.1Docker 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
lavinmqctlandlavinmqperfCLI tools inside the container - Docker CE with the Docker Compose v2 plugin
- Container
lavinmqwith/data/lavinmqbind mounted for durable broker storage - Per VM
cloudimgadministrator password (32 hex chars) generated at first boot - Stock
guestuser deleted at first boot, so there is no default login - Two systemd units:
lavinmq.service(oneshot wrapper arounddocker compose up -d) andlavinmq-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.

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.

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.

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.

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.

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.

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.

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
cloudimgadministrator password (32 hex chars fromopenssl rand) generated at first boot - Stock
guestuser is deleted at first boot, so there is no default login on the network - LavinMQ restricts its default user to loopback by default, so
guestis never reachable over the network even during the brief first boot window before it is removed - AMQP listener
5672/tcpand management console15672/tcpare 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.