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

Created by eneaslari 13/8/2025

docker

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 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 .
  • -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).

More to read


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

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.

My New Plan: Making a Mini-Game Every Week
21/2/2025

This week, I realized that working on big projects can sometimes feel slow, and it’s easy to lose motivation. So, I came up with a new plan—I’ll create and release a small game every week!

Making Tic-Tac-Toe and Sudoku in Just a Few Hours
20/2/2025

This week, I decided to take a break from my main game project and create something simple: **a Tic-Tac-Toe and a Sudoku game**.

How I Made My Game Development Workflow Stress-Free
19/2/2025

Game development can be overwhelming, especially when working on multiple tasks without a clear plan. For a while, I was struggling to organize my thoughts and decide what to focus on next