This guide explains how to build, run, and persist a Node.js + Express application inside Docker — with support for MongoDB, PostgreSQL, and file uploads — and also clarifies the relationship between images, containers, and volumes.
1. Big Picture: Images → Containers → Volumes
If Docker was a kitchen:
Docker Concept | Kitchen Analogy | Purpose |
---|---|---|
Image | Recipe | Instructions + ingredients for your app |
Container | Prepared Meal | A running instance of the recipe |
Volume | Fridge/Pantry | Where persistent ingredients are kept |
Key rules:
- Images are read-only.
- Containers are ephemeral — delete them, data is gone unless stored in a volume.
- Volumes survive container restarts and recreate.
Diagram: How They Work Together
Image: my-node-app Image: mongo:6 Image: postgres:16 +----------------------+ +---------------------+ +---------------------+ | code + deps | | mongo binary + init | | postgres binary + init| +----------------------+ +---------------------+ +---------------------+ | | | v v v Container: app Container: mongo Container: postgres (ephemeral FS) (/data/db) (/var/lib/postgresql) | | | v v v Volume: uploads Volume: mongo_data Volume: pg_data (user files) (MongoDB storage) (Postgres storage)
2. Why Use Docker for Node.js Apps
- Consistent environment for development, testing, and production.
- No "works on my machine" headaches.
- Easy to ship and run anywhere.
- Built-in isolation between services (app, DB, admin tools).
3. Project Structure Example
myapp/ src/ index.js routes/ package.json Dockerfile docker-compose.yml .env uploads/ # Will be mounted to a volume in production
4. Dockerfile for Node.js + Express
FROM node:20-alpine # Create app directory WORKDIR /app # Install dependencies first (layer caching) COPY package*.json ./ RUN npm ci --omit=dev # Copy app code COPY . . # Environment variables ENV NODE_ENV=production ENV PORT=3000 # Create uploads folder and fix permissions RUN mkdir -p /app/uploads && chown -R node:node /app USER node EXPOSE 3000 CMD ["node", "src/index.js"]
5. docker-compose.yml with MongoDB, PostgreSQL & File Uploads
services: app: build: . env_file: .env ports: - "3000:3000" volumes: - uploads:/app/uploads depends_on: - mongo - postgres restart: unless-stopped mongo: image: mongo:6 volumes: - mongo_data:/data/db restart: unless-stopped mongo-express: image: mongo-express:1 ports: - "8081:8081" environment: - ME_CONFIG_MONGODB_SERVER=mongo - ME_CONFIG_BASICAUTH=false depends_on: - mongo postgres: image: postgres:16 environment: - POSTGRES_DB=mydb - POSTGRES_USER=app - POSTGRES_PASSWORD=app volumes: - pg_data:/var/lib/postgresql/data restart: unless-stopped pgadmin: image: dpage/pgadmin4:8 ports: - "8082:80" environment: - PGADMIN_DEFAULT_EMAIL=admin@example.com - PGADMIN_DEFAULT_PASSWORD=secret depends_on: - postgres volumes: uploads: mongo_data: pg_data:
6. Environment File Example (.env)
NODE_ENV=production PORT=3000 DB_CLIENT=mongo MONGO_URI=mongodb://mongo:27017/mydb # PostgreSQL alternative POSTGRES_URI=postgres://app:app@postgres:5432/mydb
7. How to Run
# Build and start all services docker compose up -d --build # Stop everything docker compose down # View logs docker compose logs -f app # Enter app container docker compose exec app sh
8. Volumes in Practice
- uploads → holds all user-uploaded files.
- mongo_data → stores MongoDB’s database files.
- pg_data → stores PostgreSQL’s database files.
Even if you run:
docker compose down docker compose up -d
Your DB data and uploaded files remain intact because they’re in named volumes.
9. Security Tips
- Never commit
.env
files with passwords to Git. - Do not expose DB ports to the internet in production.
- Use specific image versions (
mongo:6.0.13
, not justmongo:latest
). - Run app as a non-root user in Dockerfile.
10. Final Takeaways
- Image = blueprint for your app.
- Container = running copy of that blueprint.
- Volume = persistent storage for data that must survive restarts.
- Use docker-compose to orchestrate app + DB + admin tools.
- Always separate dev and prod configs.
121 Docker Commands (with Explanations)
Build the image
docker build -t myapp .
-
-t myapp
→ tags the image name.
Run a container
docker run -d --name myapp-container -p 3000:3000 myapp
-
-d
→ detached mode. -
-p 3000:3000
→ map port 3000 host → container.
List containers
docker ps
Stop a container
docker stop myapp-container
Remove a container
docker rm myapp-container
List images
docker images
Remove an image
docker rmi myapp
List volumes
docker volume ls
Inspect a volume
docker volume inspect uploads
Remove a volume
docker volume rm uploads
12. Running with Docker Compose
Start all services
docker compose up -d --build
Stop all services
docker compose down
Stop and remove with volumes
docker compose down -v
Check logs
docker compose logs -f app
13. Migrating to Another Server
Backup volumes
docker run --rm -v uploads:/data -v $PWD:/backup alpine \ tar -czf /backup/uploads.tar.gz -C /data . docker run --rm -v mongo_data:/data -v $PWD:/backup alpine \ tar -czf /backup/mongo_data.tar.gz -C /data .
Restore volumes
docker run --rm -v uploads:/data -v $PWD:/backup alpine \ tar -xzf /backup/uploads.tar.gz -C /data docker run --rm -v mongo_data:/data -v $PWD:/backup alpine \ tar -xzf /backup/mongo_data.tar.gz -C /data
14. Summary of Best Practices
- Use named volumes for persistence (DB, uploads).
- Separate app, DB, and admin into their own containers.
- Keep sensitive data in
.env
, not in code. - Use
docker-compose.yml
for multi-service orchestration. - Always
.dockerignore
node_modules
if installing inside the container. - Avoid running containers as
root
in production. - Use lightweight base images (
node:alpine
).
Top comments (0)