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)