NodeJS
Docker
MongoDB
RabbitMQ
Software Development

How can I run NodeJS in Docker with MongoDB and RabbitMQ?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Running a Node.js service with MongoDB and RabbitMQ in Docker is a common local-development setup for API and worker applications. The key is to let Docker Compose provide service discovery, persistent storage, and startup coordination so your app connects to mongo and rabbitmq by container name instead of hard-coded host addresses.

Use Compose To Define The Whole Stack

A small compose.yaml file is the simplest way to run the three services together:

yaml
1services:
2  app:
3    build: .
4    ports:
5      - "3000:3000"
6    environment:
7      MONGODB_URI: mongodb://mongo:27017/appdb
8      AMQP_URL: amqp://guest:guest@rabbitmq:5672/
9    depends_on:
10      mongo:
11        condition: service_started
12      rabbitmq:
13        condition: service_started
14
15  mongo:
16    image: mongo:7
17    ports:
18      - "27017:27017"
19    volumes:
20      - mongo_data:/data/db
21
22  rabbitmq:
23    image: rabbitmq:3-management
24    ports:
25      - "5672:5672"
26      - "15672:15672"
27
28volumes:
29  mongo_data:

This gives you:

  • 'app for the Node.js service'
  • 'mongo for the database'
  • 'rabbitmq for the message broker'

The hostnames inside the Docker network are the service names, so the Node app should connect to mongo and rabbitmq, not localhost.

Build A Node Image That Starts Cleanly

The Node image can stay minimal:

dockerfile
1FROM node:20-alpine
2
3WORKDIR /app
4
5COPY package*.json ./
6RUN npm ci
7
8COPY . .
9
10EXPOSE 3000
11CMD ["node", "server.js"]

Using npm ci instead of npm install is generally preferable in container builds when a lockfile exists, because it produces a more reproducible dependency tree.

Connect To MongoDB And RabbitMQ From Node

Inside the application, use the container hostnames from the environment variables:

javascript
1const express = require("express");
2const mongoose = require("mongoose");
3const amqp = require("amqplib");
4
5const app = express();
6
7async function start() {
8  await mongoose.connect(process.env.MONGODB_URI);
9
10  const connection = await amqp.connect(process.env.AMQP_URL);
11  const channel = await connection.createChannel();
12  await channel.assertQueue("jobs", { durable: true });
13
14  app.get("/", async (_req, res) => {
15    await channel.sendToQueue("jobs", Buffer.from("hello"), { persistent: true });
16    res.json({ ok: true });
17  });
18
19  app.listen(3000, () => {
20    console.log("Server listening on port 3000");
21  });
22}
23
24start().catch((err) => {
25  console.error(err);
26  process.exit(1);
27});

This example connects once at startup, creates a durable queue, and publishes a simple message on each request.

Expect Startup Timing Issues

depends_on controls container startup order, but it does not guarantee MongoDB or RabbitMQ are fully ready for connections by the time Node starts. In real stacks, add retry logic:

javascript
1async function wait(ms) {
2  return new Promise((resolve) => setTimeout(resolve, ms));
3}
4
5async function connectWithRetry(fn, label) {
6  for (let attempt = 1; attempt <= 10; attempt++) {
7    try {
8      return await fn();
9    } catch (err) {
10      console.log(`${label} not ready yet, attempt ${attempt}`);
11      await wait(2000);
12    }
13  }
14  throw new Error(`Could not connect to ${label}`);
15}

Use that wrapper around database and broker initialization so the app survives short warm-up delays.

Running The Stack

From the project directory:

bash
docker compose up --build

Once it is running:

  • the Node app is reachable on port 3000
  • MongoDB is reachable on port 27017
  • RabbitMQ's management UI is reachable on port 15672

That is enough for local development, simple demos, and integration testing.

Common Pitfalls

One common mistake is using localhost inside the Node container. From one container, localhost means that same container, not MongoDB or RabbitMQ.

Another issue is assuming depends_on means "ready for queries." It only controls startup ordering unless you add readiness handling yourself.

A third problem is forgetting to persist MongoDB data in a volume, which makes the database look empty after the stack is recreated.

Finally, teams often hard-code secrets in the compose file and then copy the same pattern to production. For real deployments, move credentials into environment management or secret storage.

Summary

  • Use Docker Compose to run Node.js, MongoDB, and RabbitMQ as one networked stack.
  • Connect to mongo and rabbitmq by service name, not localhost.
  • Keep the Node image simple and reproducible with npm ci.
  • Add retry logic because startup order is not the same as service readiness.
  • Use volumes for MongoDB data and treat local-development credentials separately from production.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.