DEV Community

Cover image for 🐳 The Complete Guide to Dockerizing a Node.js + Express App with Database & File Uploads
EneasLari
EneasLari

Posted on • Originally published at eneaslari.com

🐳 The Complete Guide to Dockerizing a Node.js + Express App with Database & File Uploads

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) 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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"] 
Enter fullscreen mode Exit fullscreen mode

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: 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 just mongo: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 . 
Enter fullscreen mode Exit fullscreen mode
  • -t myapp → tags the image name.

Run a container

docker run -d --name myapp-container -p 3000:3000 myapp 
Enter fullscreen mode Exit fullscreen mode
  • -d → detached mode.
  • -p 3000:3000 → map port 3000 host → container.

List containers

docker ps 
Enter fullscreen mode Exit fullscreen mode

Stop a container

docker stop myapp-container 
Enter fullscreen mode Exit fullscreen mode

Remove a container

docker rm myapp-container 
Enter fullscreen mode Exit fullscreen mode

List images

docker images 
Enter fullscreen mode Exit fullscreen mode

Remove an image

docker rmi myapp 
Enter fullscreen mode Exit fullscreen mode

List volumes

docker volume ls 
Enter fullscreen mode Exit fullscreen mode

Inspect a volume

docker volume inspect uploads 
Enter fullscreen mode Exit fullscreen mode

Remove a volume

docker volume rm uploads 
Enter fullscreen mode Exit fullscreen mode

12. Running with Docker Compose

Start all services

docker compose up -d --build 
Enter fullscreen mode Exit fullscreen mode

Stop all services

docker compose down 
Enter fullscreen mode Exit fullscreen mode

Stop and remove with volumes

docker compose down -v 
Enter fullscreen mode Exit fullscreen mode

Check logs

docker compose logs -f app 
Enter fullscreen mode Exit fullscreen mode

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 . 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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)