A containerized full-stack Todo application built with Node.js, Express, and MongoDB, orchestrated using Docker Compose. This project demonstrates two distinct container deployment strategies — a production-oriented multi-service setup and a development workflow using bind mounts for live code reloading.
Published on Docker Hub: docker pull pratyusha108/welcome-to-docker
┌─────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ todo-app │──────▶│ todo-database │ │
│ │ Express.js │ │ MongoDB 6 │ │
│ │ Port 3000 │ │ Port 27017 │ │
│ └──────────────┘ └──────────────────┘ │
│ │ │
│ Named Volume │
│ (persistent DB) │
└─────────────────────────────────────────────────┘
Two configurations included:
| Config | Purpose | App Port | DB Port | Volume Strategy |
|---|---|---|---|---|
multi-container-app/ |
Production deployment | 3000 | 27017 | Named volume for DB persistence |
bindmount-apps/ |
Development workflow | 3001 | 27018 | Bind mount for live code sync |
- Runtime: Node.js 19 (Alpine)
- Backend: Express.js 4.18
- Database: MongoDB 6 (official image)
- ODM: Mongoose 7.1
- Templating: EJS
- Dev Tools: Nodemon, LiveReload
- Container Orchestration: Docker Compose
├── multi-container-app/ # Production-style deployment
│ ├── compose.yaml # Compose config with named volumes
│ └── app/
│ ├── Dockerfile # Multi-stage build with npm cache mounts
│ ├── server.js # Express app with MongoDB connection
│ ├── models/Todo.js # Mongoose schema
│ ├── routes/front.js # CRUD routes (create, list, delete)
│ └── views/todos.ejs # Bootstrap-styled UI
│
├── bindmount-apps/ # Development workflow
│ ├── compose.yaml # Compose config with bind mounts
│ └── app/
│ ├── Dockerfile # Non-root user, dev dependencies excluded
│ ├── server.js # Same app, different port config
│ └── ... # Identical application code
│
└── Dockerfile # Root: React welcome page container
- Docker Desktop installed and running
cd multi-container-app
docker compose up -d --buildOpen http://localhost:3000 to access the Todo app.
cd bindmount-apps
docker compose up -d --buildOpen http://localhost:3001. Code changes in app/ are reflected immediately via bind mount + Nodemon.
docker compose downTo also remove the persistent database volume:
docker compose down -vThe todo-app service connects to MongoDB using Docker's internal DNS resolution — the connection string references the service name (todo-database) directly, with no hardcoded IPs. Compose handles the network creation automatically.
multi-container-appuses a named volume (database:/data/db) so that todo data persists across container restarts.bindmount-appsuses a bind mount (./app:/usr/src/app) to sync local code changes into the running container, with an anonymous volume to preservenode_modulesinside the container.
- Cache mounts on
npm cito speed up repeated builds - Non-root user (
USER node) in the bind mount Dockerfile for security - Layer ordering — dependencies installed before copying source code to maximize Docker's build cache
Error: bind: address already in use
Another process is occupying the port. Find and stop it:
# Check what's using the port (Linux/macOS)
lsof -i :3000
# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /FOr remap to a different port in compose.yaml:
ports:
- "3002:3000" # Change 3002 to any available portno configuration file provided: not found
You must run docker compose from the directory containing compose.yaml:
cd multi-container-app # or cd bindmount-apps
docker compose up -d --buildIf Compose pulls a remote image instead of building locally, ensure your compose.yaml uses build: and not image: for the app service:
services:
todo-app:
build:
context: ./app # Builds from local DockerfileRun with --build to force a fresh build:
docker compose up -d --buildWorking through this project gave me hands-on experience with problems that only surface in real container environments — not in tutorials. I debugged port conflicts when multiple services competed for the same port, resolved compose file not found errors caused by running commands from the wrong directory, and worked through the distinction between pulling pre-built images and building from a local Dockerfile.
Beyond debugging, I gained practical understanding of Docker Compose service orchestration: how containers discover each other through internal DNS, how named volumes provide data persistence independently of container lifecycle, and how bind mounts enable a fast development feedback loop without rebuilding images. I also published a container image to Docker Hub, completing the full build-ship workflow.
These are the kinds of operational skills that matter when deploying ML pipelines, standing up database-backed services, or working with containerized development environments in production teams.
docker pull pratyusha108/welcome-to-dockerThis project is based on Docker's welcome-to-docker educational repository, extended with multi-container and bind mount configurations.