Skip to Content

Production Guide: Deploy RabbitMQ with Docker Compose + Caddy on Ubuntu

A practical, production-oriented RabbitMQ deployment playbook for secure, resilient event-driven systems.

Introduction: real-world use case

Many teams adopt event-driven architecture in phases. At first, message queues are introduced for one workflow (for example, payment callbacks, order processing, or asynchronous report generation). Soon after, more services depend on reliable message delivery, retries, and decoupled processing. If the broker is not deployed with production guardrails, small issues become major outages: disk fills unexpectedly, queues grow without visibility, TLS is misconfigured, or credentials leak into shell history and backups.

This guide shows a production-oriented RabbitMQ deployment on Ubuntu using Docker Compose with Caddy as a reverse proxy. You will get an operational setup with TLS at the edge, durable storage, predictable restart behavior, least-privilege service accounts, and a practical day-2 checklist for monitoring, backups, and upgrades.

The approach is intentionally pragmatic: use battle-tested components, keep defaults secure, and avoid over-engineering while still meeting production expectations for reliability and recoverability.

Architecture and flow overview

We will deploy RabbitMQ as a dedicated service on a Linux host using Docker Compose. RabbitMQ stores durable data on a named volume. Caddy terminates TLS for the management UI and forwards traffic internally to RabbitMQ management. AMQP client traffic can stay private on internal networks or be exposed later with additional TCP reverse proxy patterns if your architecture requires it.

  • RabbitMQ container: message broker + management plugin.
  • Persistent volume: queue metadata and durable message state.
  • Caddy container: HTTPS termination, automatic certificate management, and secure headers.
  • Dedicated Docker network: controlled service communication.
  • System-level controls: firewall, file permissions, and backup automation.

This pattern gives teams a clean separation between app producers/consumers and platform operations. Developers focus on exchanges, routing keys, and consumer logic; operators keep the broker healthy and secure.

Prerequisites

  • Ubuntu 22.04/24.04 server with sudo access.
  • DNS record pointing mq.sysbrix.local (replace with your domain) to your server public IP.
  • Ports 80/443 open for Caddy TLS and web access.
  • Docker Engine + Docker Compose plugin installed.
  • A non-root Linux user for operations.
  • Basic understanding of RabbitMQ users, vhosts, and permissions.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg ufw

# Optional firewall baseline
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

If the copy button does not work in your browser, select the code block and copy manually.

Step-by-step deployment

1) Create project layout and environment file

Keep secrets out of Compose files. Use a restricted .env owned by your deployment user and never commit it to Git. Create a directory structure that separates config from runtime data.

mkdir -p ~/rabbitmq-prod/{caddy,backups}
cd ~/rabbitmq-prod

cat > .env <<'EOF'
RABBITMQ_DEFAULT_USER=broker_admin
RABBITMQ_DEFAULT_PASS=replace-with-strong-password
RABBITMQ_ERLANG_COOKIE=replace-with-long-random-cookie
RABBITMQ_HOST=mq.example.com
EOF

chmod 600 .env

If the copy button does not work in your browser, select the code block and copy manually.

2) Write Docker Compose for RabbitMQ + Caddy

This Compose file runs RabbitMQ with management UI and persists state in Docker volumes. Caddy handles HTTPS automatically. The management interface is proxied from Caddy to RabbitMQ over the private Docker network.

cat > docker-compose.yml <<'EOF'
services:
  rabbitmq:
    image: rabbitmq:3.13-management
    container_name: rabbitmq
    restart: unless-stopped
    env_file:
      - .env
    environment:
      RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
      RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE}
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    networks:
      - edge
    expose:
      - "5672"
      - "15672"
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5

  caddy:
    image: caddy:2.8
    container_name: rabbitmq-caddy
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      rabbitmq:
        condition: service_healthy
    networks:
      - edge

volumes:
  rabbitmq_data:
  caddy_data:
  caddy_config:

networks:
  edge:
    driver: bridge
EOF

If the copy button does not work in your browser, select the code block and copy manually.

3) Configure Caddy reverse proxy

Set your actual domain and email. Keep admin endpoints behind TLS and add conservative security headers. If your org requires IP allowlisting for the management UI, add matcher rules in Caddy.

cat > caddy/Caddyfile <<'EOF'
{$RABBITMQ_HOST} {
  encode zstd gzip

  header {
    X-Content-Type-Options "nosniff"
    X-Frame-Options "DENY"
    Referrer-Policy "strict-origin-when-cross-origin"
  }

  reverse_proxy rabbitmq:15672
}
EOF

If the copy button does not work in your browser, select the code block and copy manually.

4) Start the stack and validate container health

Bring up services and immediately verify health before onboarding applications. Health checks catch startup issues such as incorrect Erlang cookie, invalid volume permissions, or DNS resolution errors.

docker compose pull
docker compose up -d
docker compose ps
docker logs rabbitmq --tail=80
docker logs rabbitmq-caddy --tail=80

If the copy button does not work in your browser, select the code block and copy manually.

5) Apply least-privilege RabbitMQ access model

Do not let applications share the initial admin account. Create one virtual host per environment or workload boundary, then assign app-specific users with scoped permissions. This dramatically simplifies incident response and auditability.

# Create vhost and app user
docker exec rabbitmq rabbitmqctl add_vhost /orders
docker exec rabbitmq rabbitmqctl add_user orders_app 'replace-with-app-password'

# Set scoped permissions: configure/write/read
# Regex can be tightened per exchange/queue naming standards
docker exec rabbitmq rabbitmqctl set_permissions -p /orders orders_app ".*" ".*" ".*"

# Optional: tag only true administrators
docker exec rabbitmq rabbitmqctl set_user_tags broker_admin administrator

If the copy button does not work in your browser, select the code block and copy manually.

6) Configure resource alarms and queue hygiene

Production incidents often start with uncontrolled queue growth. Use TTL policies, dead-letter exchanges, and length limits to contain failures. Pair this with alerting on memory/disk alarms and consumer lag.

# Example policy: apply queue max-length and message TTL to a naming pattern
docker exec rabbitmq rabbitmqctl set_policy -p /orders \
  orders-safety "^orders\\." \
  '{"message-ttl":86400000,"max-length":50000,"overflow":"reject-publish"}' \
  --apply-to queues

# Check alarms and runtime status
docker exec rabbitmq rabbitmq-diagnostics alarms
docker exec rabbitmq rabbitmq-diagnostics status

If the copy button does not work in your browser, select the code block and copy manually.

Configuration and secrets handling best practices

Message brokers become central infrastructure quickly, so secrets discipline matters. Use a secret manager if available; if not, lock down local files and rotate credentials regularly. Avoid exporting broker passwords in shell profiles or CI logs.

  • Store administrator and app credentials separately.
  • Rotate app credentials quarterly or after team changes.
  • Use unique credentials per service; no shared β€œintegration” user.
  • Keep .env file permissions at 600 and limit sudo access.
  • Document vhost ownership in your platform runbook.

For backup strategy, snapshot RabbitMQ definitions and persist data volume backups. Definitions (users, vhosts, policies, exchanges, queues) help rebuild quickly in disaster recovery scenarios.

# Export broker definitions (metadata) for DR
docker exec rabbitmq rabbitmqctl export_definitions /tmp/definitions.json
docker cp rabbitmq:/tmp/definitions.json ./backups/definitions-$(date +%F).json

# Example volume backup (stop briefly for consistency in strict environments)
docker compose stop rabbitmq
docker run --rm -v rabbitmq-prod_rabbitmq_data:/data -v $(pwd)/backups:/backup alpine \
  tar czf /backup/rabbitmq-data-$(date +%F).tgz -C /data .
docker compose start rabbitmq

If the copy button does not work in your browser, select the code block and copy manually.

Verification checklist

  • Management UI reachable over HTTPS at your configured domain.
  • RabbitMQ health check is healthy and alarms report clear.
  • App vhost exists and app user authentication succeeds.
  • Policies are present for TTL/max-length/dead-letter behavior.
  • Definitions export and volume backup complete successfully.
  • Restart test confirms service recovery after host reboot.
# Quick validation commands
docker exec rabbitmq rabbitmqctl list_vhosts
docker exec rabbitmq rabbitmqctl list_users
docker exec rabbitmq rabbitmqctl list_permissions -p /orders
docker exec rabbitmq rabbitmqctl list_policies -p /orders
docker exec rabbitmq rabbitmq-diagnostics check_port_connectivity

If the copy button does not work in your browser, select the code block and copy manually.

Common issues and fixes

Issue 1: Management UI is unreachable over HTTPS

Symptoms: Browser timeout or certificate errors. Fix: Confirm DNS points to the host, ports 80/443 are open, and Caddy logs show successful certificate issuance. If behind cloud firewall/NAT, verify forwarding and security-group rules.

Issue 2: RabbitMQ restarts repeatedly after deploy

Symptoms: Container exits on boot. Fix: Check environment variable formatting and volume ownership. Invalid Erlang cookie or corrupted data permissions are common causes. Review docker logs rabbitmq before changing versions.

Issue 3: Producers can connect but consumers fail to read

Symptoms: Authentication works, but queues remain unconsumed. Fix: Verify per-vhost permissions include read rights and that clients are using the expected vhost path (for example, /orders not default /).

Issue 4: Queue depth grows continuously during incidents

Symptoms: Messages pile up with no recovery. Fix: Enable dead-lettering, queue limits, and message TTL. Scale consumers, then inspect poison messages and idempotency behavior in downstream services.

Issue 5: Backup exists but restore runbook is untested

Symptoms: Recovery uncertainty. Fix: Run quarterly restore drills in staging: restore volume snapshot, import definitions, replay producer tests, and verify consumer lag clears within expected SLO.

FAQ

Do I need to expose AMQP port 5672 publicly?

Usually no. Keep AMQP private and reachable only from trusted app networks/VPN. Exposing management UI and AMQP broadly increases attack surface and complicates access control.

Should I run one RabbitMQ node or a cluster?

Start with one node for simpler operations if your workload and availability requirements permit. Move to clustered/quorum queue architecture when SLOs, failure domains, or throughput require it.

How often should I rotate RabbitMQ credentials?

At least quarterly for app credentials and immediately after security incidents or team changes. Rotate admin credentials on a stricter cadence and update automation pipelines accordingly.

What is the safest way to handle retry storms?

Use bounded retries, dead-letter queues, and delayed requeue patterns. Never allow unbounded instant retries; they amplify outages and can overwhelm dependent systems.

Can Caddy and RabbitMQ run on separate hosts?

Yes. Many teams place Caddy on an ingress node and keep RabbitMQ on private subnets. Ensure secure network paths, strict firewall rules, and observability for both layers.

How do I verify my deployment is truly production-ready?

Treat readiness as a checklist: secure defaults, TLS, least-privilege users, tested backups/restores, alerting for queue depth and alarms, and documented incident runbooks with owner accountability.

Related guides

Talk to us

Need help deploying a production-ready messaging platform, designing resilient queue policies, or creating secure backup and upgrade runbooks for your team? We can help with architecture, hardening, migration, and operational readiness.

Contact Us

Production Guide: Deploy OpenObserve with Docker Compose + Caddy + ClickHouse on Ubuntu
A practical, production-oriented OpenObserve deployment playbook for platform and DevOps teams.