Skip to content

Reinforcement Lab: The Two-Container Shuffle

Welcome to the dry dock.

We’ve now worked through the core ideas behind a two-container setup:

  • a Node/Express API service
  • a MongoDB database service
  • Docker Compose orchestration
  • internal service networking
  • environment-based configuration
  • persistent volumes

Now it is our turn to put those ideas together.

Objective: Start a simple two-service Compose setup and verify that both services are running.

  1. Create a compose.yaml file that defines:
    • an api_service
    • a db_service
  2. Use the official mongo:8.0 image for the database service.
  3. Build the API service from our project folder.
  4. Publish port 3000:3000 for the API service.
  5. Run the stack with:
Terminal window
docker compose up -d

Compose should:

  • build the API image
  • pull the MongoDB image if needed
  • create a project network
  • start both services

Running this command should show both services up:

Terminal window
docker compose ps
Student-to-AI: Concept Check

Prompt: “Explain how docker compose up -d differs from running two separate docker run -d commands, especially in terms of networking and project coordination.”


Objective: Add persistence to the database and move the MongoDB connection string into environment-based configuration.

  1. Update our compose.yaml file to add a named volume called mongo_data.
  2. Mount that volume to MongoDB’s internal data directory:
volumes:
- mongo_data:/data/db
  1. Create a .env file beside our compose.yaml.
  2. Add a MONGO_URI value that points to the database service:
MONGO_URI=mongodb://db_service:27017/portfolio
  1. Pass that value into the API service using Compose environment variable interpolation.
  2. Bring the stack down and back up:
Terminal window
docker compose down
docker compose up -d

Our database should now store its data in a named volume rather than only inside the container.

Our API should read its MongoDB connection string from process.env.MONGO_URI instead of a hardcoded value.

We should also be able to confirm that the named volume exists:

Terminal window
docker volume ls
Student-to-AI: Verification Prompt

Prompt: “What is the Docker CLI command to verify that our named volume mongo_data exists independently of any running container?”

Remember What `down -v` Does

A normal docker compose down removes containers and the project network.

A docker compose down -v also removes named volumes, which means our Mongo data will be deleted too.


Solo’s Special: The Sabotaged Connection

Section titled “Solo’s Special: The Sabotaged Connection”

Objective: Diagnose and fix a broken database connection caused by using the wrong hostname.

  1. Pretend our API is still trying to connect to MongoDB using:
const MONGO_URI = "mongodb://localhost:27017/portfolio";
  1. Identify why that fails inside a Compose setup.
  2. Fix the connection so the API uses the database service name instead.
  3. Rebuild and restart the stack with:
Terminal window
docker compose up --build -d
  1. Check the logs to verify the API connects successfully.
  2. Step into the API container with:
Terminal window
docker compose exec api_service sh

Our fixed connection string should point to:

mongodb://db_service:27017/portfolio

Once corrected, the API should be able to reach MongoDB across the internal Compose network.

Student-to-AI: Debug Prompt

Prompt: “Why does localhost fail inside a container when trying to reach another service, and why does db_service work instead?”


Objective: Explore a more convenient local workflow that avoids rebuilding the image after every code change.

  1. Update api_service in compose.yaml to include a bind mount for our project files.
  2. Configure the API service to run with a file watcher such as nodemon or Node’s --watch flag.
  3. Start the stack and make a small change to server.js.
  4. Confirm that the API reloads without needing docker compose up --build.

Our API container should reflect code changes much more quickly during development.

This is a common local-development pattern, even though it is a different idea from named volumes.

Student-to-AI: Bind Mount Prompt

Prompt: “Explain the difference between a named volume and a bind mount. What problem does each one solve in a containerized development workflow?”


Compose Quickstart Guide

We’ve built the stack, wired the services, and kept the data alive. Next, we’ll lock the key ideas into the reference page for quick future lookup.