Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RabbitMqContainer

A single-node RabbitMQ container with the management plugin enabled. Defaults to a guest/guest credential pair (the image’s own default) so amqp_url() is usable with zero configuration.

Default image: rabbitmq:4-management-alpine Guest ports: 5672 (AMQP), 15672 (management)

MethodOnEffect
RabbitMqContainer::new()builderPinned default image, guest/guest.
RabbitMqContainer::with_image(image)builderCaller-chosen image.
.with_username(u) / .with_password(p)builderOverride either credential before start().
.start()builder → Result<RabbitMqGuard>Boots the container.
.username() / .password()guardThe configured credential pair.
.amqp_url()guardamqp://user:pass@host:port — the AMQP listener.
.management_url()guardThe management UI/API base URI.
.stop()guardStops and removes the container, releases its ports.

Readiness — verified against a real 4.x boot

rabbitmq:4-management-alpine still prints the same "Server startup complete" line the 3.x series used (captured verbatim from a real boot with this module’s env):

2026-07-04 08:47:17.936423+00:00 [info] <0.1036.0> started TCP listener on [::]:5672
 completed with 4 plugins.
2026-07-04 08:47:18.001311+00:00 [info] <0.900.0> Server startup complete; 4 plugins started.
2026-07-04 08:47:18.001311+00:00 [info] <0.900.0>  * rabbitmq_prometheus
2026-07-04 08:47:18.001311+00:00 [info] <0.900.0>  * rabbitmq_management
2026-07-04 08:47:18.001311+00:00 [info] <0.900.0>  * rabbitmq_management_agent
2026-07-04 08:47:18.001311+00:00 [info] <0.900.0>  * rabbitmq_web_dispatch

The line appears exactly once, so for_log_message at times=1 is unambiguous — unlike Postgres/MySQL/MariaDB, there is no double-boot restart to race here. The management API’s own /api/health/checks/... endpoints require authenticated requests, so the log line is the simpler and equally reliable readiness signal.

No with_memory_limit override: booted clean on msb’s default ~450M microVM RAM (observed ~5.5s IT round-trip on both backends — an Erlang VM, not a JVM, so no Paketo/QuickStart-style heap demand).

A 4.x behavior change worth knowing

RabbitMQ 4.x deprecates transient_nonexcl_queues and, per the broker’s own startup warning, “this feature can still be used for now” — but a client that declares a non-durable, non-exclusive queue (durable=false, exclusive=false) may be rejected with reply-code=541 INTERNAL_ERROR depending on the deployed policy, reproduced directly against this module’s pinned image. Declare durable, non-exclusive queues (or exclusive transient ones) from client code exercising this container; this module itself declares no queues.

Complete example

This module’s own integration test round-trips over the management HTTP API rather than an AMQP client library — lapin (the natural AMQP client choice) pulls in a time-based transitive tree above this workspace’s MSRV. The management API’s /api/queues/.../publish and /api/queues/.../get endpoints exercise the same “does the broker actually work” claim at zero extra dependency cost:

use rightsize_modules::RabbitMqContainer;

#[tokio::test]
async fn declare_publish_and_get_round_trips() -> Result<(), Box<dyn std::error::Error>> {
    let guard = RabbitMqContainer::new().start().await?;

    let agent = ureq::Agent::new_with_defaults();
    let admin = guard.management_url();
    let auth = format!("Basic {}", "guest:guest"); // base64 in the real test

    // Durable, non-exclusive — RabbitMQ 4.x's transient_nonexcl_queues deprecation
    // (see above) can reject the opposite combination.
    agent
        .put(format!("{admin}/api/queues/%2f/smoke"))
        .header("Content-Type", "application/json")
        .header("Authorization", &auth)
        .send(r#"{"durable":true,"auto_delete":false}"#)?;

    agent
        .post(format!("{admin}/api/exchanges/%2f/amq.default/publish"))
        .header("Content-Type", "application/json")
        .header("Authorization", &auth)
        .send(
            r#"{"properties":{},"routing_key":"smoke","payload":"hello-rightsize",
                "payload_encoding":"string"}"#,
        )?;

    let mut get = agent
        .post(format!("{admin}/api/queues/%2f/smoke/get"))
        .header("Content-Type", "application/json")
        .header("Authorization", &auth)
        // The management API's field is `ackmode`, not `ack_mode` — verified
        // directly against a real 4.x boot.
        .send(r#"{"count":1,"ackmode":"ack_requeue_false","encoding":"auto"}"#)?;
    let body = get.body_mut().read_to_string()?;
    assert!(body.contains("hello-rightsize"));

    guard.stop().await?;
    Ok(())
}

Backend notes

No memory-limit override is needed on either backend — see Readiness above.