Docker for Absolute Beginners — Part 4
Md Enayetur Rahman

Md Enayetur Rahman @md_enayeturrahman_2560e3

About: Md Enayetur Rahman MERN Stack Developer | Next Js | Typescript | Framer Motion | Graph QL | Material UI | Aceternity UI |

Location:
Sylhet, Bangladesh.
Joined:
Jun 10, 2024

Docker for Absolute Beginners — Part 4

Publish Date: Jul 9
0 0

Mastering Volumes & Production-friendly Node Images

In Part 3 we hot-reloaded an Express server with a bind mount and an anonymous node_modules volume. Now we’ll level-up: learn every volume flavour Docker offers and slim our image with npm i --omit=dev. By the end you’ll know exactly **where* to store code, dependencies, and runtime data — and why.*


Learning Aims

  1. Differentiate bind, anonymous, and named volumes — when to use each and what benefits they bring.
  2. Write a Dockerfile that installs production-only dependencies via npm i --omit=dev and caches the layer smartly.
  3. Compose a docker-compose.yml that mounts three storage types in one service.

1 Project Snapshot

volume-types-demo/
├─ Dockerfile
├─ docker-compose.yml
├─ package.json          # contains devDependencies (nodemon)
└─ index.js              # appends timestamp → /data/test_data
Enter fullscreen mode Exit fullscreen mode

A minimal index.js appends a timestamped line to /data/test_data then prints the whole file. It’s perfect for watching volume persistence.


2 Dockerfile — Line-by-Line

FROM node:20-alpine            # 1
WORKDIR /usr/src/app           # 2
COPY package*.json ./          # 3
RUN npm i --omit=dev \
    && npm cache clean --force # 4 ← spotlight line
COPY . .                       # 5
CMD ["npm", "run", "dev"]        # 6
Enter fullscreen mode Exit fullscreen mode
# Instruction What it does (plain English) Why it matters
1 FROM node:20-alpine Starts from the tiny Alpine Linux image with Node 20 pre-installed. Shaves 100 MB+ off your pulls compared to node:20 (Debian).
2 WORKDIR /usr/src/app Sets the working directory for all subsequent commands. Keeps files organised in one predictable location.
3 COPY package*.json ./ Copies package.json and package-lock.json into the image. Lets Docker cache dependency install separately from source code changes.
4 RUN npm i --omit=dev && npm cache clean --force ① Installs only production dependencies (excludes devDependencies). ② Immediately removes the npm cache to reclaim ~20 MB. • Creates a leaner runtime image — no nodemon, eslint, etc. • Faster container start‑up and lower CVE surface.
5 COPY . . Copies the remainder of your source code. Because it’s after the npm layer, editing code doesn’t trigger a fresh npm install.
6 CMD ["npm","run","dev"] Default command when the container starts; the dev script typically runs nodemon. Keeps the container interactive for local development. Compose can override this later.

Tip: swap --omit=dev for --production if you’re on older npm versions (< 7).


3 docker-compose.yml — Word-by-Word

services:
  app:
    build: .
    container_name: app_dev

    command: npm run dev        # echoes & exits; replace with nodemon for live reload
    ports:
      - "3000:3000"             # HOST:CONTAINER

    volumes:
      # 1️⃣ Bind mount → live-reload source
      - .:/usr/src/app:cached
      # 2️⃣ Named volume → dependency isolation
      - app_node_modules:/usr/src/app/node_modules
      # 3️⃣ Named volume → runtime data persistence
      - app_data:/data

volumes:
  app_node_modules:             # Docker manages location + lifecycle
  app_data:
Enter fullscreen mode Exit fullscreen mode
Key Value Detailed beginner-friendly explanation
services Root of every Compose file. Each key under it describes one container (here app).
build: . Dot = “look in this folder for a Dockerfile and context.” Compose auto‑rebuilds if missing.
container_name Gives your container a friendly fixed name (app_dev) instead of a random one.
command Overrides the Dockerfile’s CMD at run‑time. Handy for trying different start scripts without rebuilding.
ports 3000:3000 publishes container port 3000 on host port 3000 so http://localhost:3000 works. Format = HOST:CONTAINER.
volumes List of storage mounts. Order matters only to humans; Docker treats each line independently.
  – .:/usr/src/app:cached Bind mount: left side is a host path (. = current folder). Right side is container path. :cached hints macOS/Windows to favour host‑write speed.
  – app_node_modules:/usr/src/app/node_modules Named volume managed by Docker. Keeps Linux‑compiled binaries (bcrypt, sqlite, etc.) separate from your host’s node_modules.
  – app_data:/data Another named volume to persist log files & uploads. Keeps runtime data even if you delete and rebuild the image.
volumes: (root) Declares the named volumes so Compose can create/manage them. No config needed for default settings.

4 Choosing the Right Volume Type

Use-case Bind Mount (hostPath:containerPath) Named Volume (name:/path) Anonymous Volume (/path)
Live-editing source code ✅ instant reload 🚫 need restart 🚫
OS-specific binaries (node_modules, venv) ⚠️ risk host ≠ container OS ✅ isolated ✅ but unnamed
Databases / uploads / logs ⚠️ easy to delete accidentally ✅ easy backup ✅ throw‑away
Quick one-off experiments ✅ zero Docker volume clean‑up 🚫 extra steps ✅ auto-clean

Rules of thumb

  • Bind mount when you need immediate feedback in a dev loop.
  • Named volume when the data must outlive containers and you want easy CLI access (docker volume ls).
  • Anonymous volume for throw‑away state you don’t care to name.

5 Running & Observing Persistence

# 1. Start the stack
docker compose up
# 2. Append another line: stop + start again
CTRL+C
docker compose up
# 3. Inspect stored data
docker volume inspect volume-types-demo_app_data
Enter fullscreen mode Exit fullscreen mode

Every run grows test_data, proving app_data survives container recreation.

Clean up everything, including volumes:

docker compose down -v
Enter fullscreen mode Exit fullscreen mode

6 Conclusion — What We Learned

✅ Three volume types and the sweet spot for each.

✅ Building a production-ready Node image with npm i --omit=dev.

✅ Compose tricks: overriding commands, combining mounts, and why :cached speeds things up on non‑Linux hosts.

Stay tuned — your Docker toolbox is growing fast! 🚀

Comments 0 total

    Add comment