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.
Maintenance Tier: The Basic Spin-Up
Section titled “Maintenance Tier: The Basic Spin-Up”Objective: Start a simple two-service Compose setup and verify that both services are running.
Instructions
Section titled “Instructions”- Create a
compose.yamlfile that defines:- an
api_service - a
db_service
- an
- Use the official
mongo:8.0image for the database service. - Build the API service from our project folder.
- Publish port
3000:3000for the API service. - Run the stack with:
docker compose up -dExpected Outcome
Section titled “Expected Outcome”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:
docker compose psPrompt: “Explain how docker compose up -d differs from running two
separate docker run -d commands, especially in terms of networking and
project coordination.”
Custom Job Tier: The Persisted Cargo
Section titled “Custom Job Tier: The Persisted Cargo”Objective: Add persistence to the database and move the MongoDB connection string into environment-based configuration.
Instructions
Section titled “Instructions”- Update our
compose.yamlfile to add a named volume calledmongo_data. - Mount that volume to MongoDB’s internal data directory:
volumes: - mongo_data:/data/db- Create a
.envfile beside ourcompose.yaml. - Add a
MONGO_URIvalue that points to the database service:
MONGO_URI=mongodb://db_service:27017/portfolio- Pass that value into the API service using Compose environment variable interpolation.
- Bring the stack down and back up:
docker compose downdocker compose up -dExpected Outcome
Section titled “Expected Outcome”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:
docker volume lsPrompt: “What is the Docker CLI command to verify that our named volume
mongo_data exists independently of any running container?”
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.
Instructions
Section titled “Instructions”- Pretend our API is still trying to connect to MongoDB using:
const MONGO_URI = "mongodb://localhost:27017/portfolio";- Identify why that fails inside a Compose setup.
- Fix the connection so the API uses the database service name instead.
- Rebuild and restart the stack with:
docker compose up --build -d- Check the logs to verify the API connects successfully.
- Step into the API container with:
docker compose exec api_service shExpected Outcome
Section titled “Expected Outcome”Our fixed connection string should point to:
mongodb://db_service:27017/portfolioOnce corrected, the API should be able to reach MongoDB across the internal Compose network.
Prompt: “Why does localhost fail inside a container when trying to reach
another service, and why does db_service work instead?”
⚡ Bonus Tier: Faster Local Development
Section titled “⚡ Bonus Tier: Faster Local Development”Objective: Explore a more convenient local workflow that avoids rebuilding the image after every code change.
Instructions
Section titled “Instructions”- Update
api_serviceincompose.yamlto include a bind mount for our project files. - Configure the API service to run with a file watcher such as
nodemonor Node’s--watchflag. - Start the stack and make a small change to
server.js. - Confirm that the API reloads without needing
docker compose up --build.
Expected Outcome
Section titled “Expected Outcome”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.
Prompt: “Explain the difference between a named volume and a bind mount. What problem does each one solve in a containerized development workflow?”
Extra Bits & Bytes
Section titled “Extra Bits & Bytes”Compose Quickstart Guide
⏭ Shipping Log
Section titled “⏭ Shipping Log”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.