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)
| Method | On | Effect |
|---|---|---|
RabbitMqContainer::new() | builder | Pinned default image, guest/guest. |
RabbitMqContainer::with_image(image) | builder | Caller-chosen image. |
.with_username(u) / .with_password(p) | builder | Override either credential before start(). |
.start() | builder → Result<RabbitMqGuard> | Boots the container. |
.username() / .password() | guard | The configured credential pair. |
.amqp_url() | guard | amqp://user:pass@host:port — the AMQP listener. |
.management_url() | guard | The management UI/API base URI. |
.stop() | guard | Stops 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.