Ravindra BagaleCourses & study guides मराठी

Chapter 50: Docker Volumes and Storage: Named Volumes, Bind Mounts, tmpfs, Backup and Every Volume Command

50.3 Bind mounts for development

Friends, when a developer changes code, the change should show in the container right away. Building the image every time wastes time. That is what a bind mount is for. Today we learn it step by step, three examples each.

1. Start from a story

At Razorpay, a frontend developer changes one line of HTML. To see it, she rebuilds the image, removes the old container and runs a new one. That takes two minutes, fifty times a day.

Her senior shows her a better way. Mount the code folder from the laptop straight into the container. Save the file, refresh the browser, done. No rebuild.

Think of a window between two rooms. Whatever you put on the table in your room is visible through the window in the other room. A bind mount is that window between your laptop folder and the container folder.

We will start with a plain HTML page, then a single config file, then real Node.js and Python apps that reload on save. At the end we learn the traps: typos, file owners, and why production servers do not use this.

laptop folder./site on the hostbind mount-v $PWD/site:/usr/share/nginx/htmlcontainer foldernginx serves the same files
Picture 1: a bind mount

2. Basic bind mounts

Step 1: Mount a folder

Why: You want nginx to serve your local HTML.

What: -v /absolute/host/path:/container/path mounts a host folder. $PWD gives the absolute path of your current folder.

Example 1: Make the site

Type this:

mkdir -p site && printf '<h1>Razorpay Checkout v1</h1>\n' > site/index.html

You will see:

(no output)

What it means: A tiny web page on the host.

Example 2: Mount and run

Type this:

docker run -d --name dev -p 8080:80 -v $PWD/site:/usr/share/nginx/html nginx:1.27

You will see:

9e4b...

What it means: The container now uses our folder.

Example 3: See the page

Type this:

curl -s localhost:8080

You will see:

<h1>Razorpay Checkout v1</h1>

What it means: nginx serves the host file.

Step 2: Live changes

Why: This is the reason bind mounts exist.

What: Both sides see the same files. A change on the host appears in the container at once.

Example 4: Edit on the host

Type this:

printf '<h1>Razorpay Checkout v2</h1>\n' > site/index.html

You will see:

(no output)

What it means: Just save the file.

Example 5: Refresh

Type this:

curl -s localhost:8080

You will see:

<h1>Razorpay Checkout v2</h1>

What it means: No rebuild, no restart.

Example 6: Add a new file

Type this:

printf 'ok\n' > site/health.txt && curl -s localhost:8080/health.txt

You will see:

ok

What it means: New files appear too.

Step 3: Mount a single file

Why: You only want to replace one config file.

What: You can bind-mount one file instead of a whole folder.

Example 7: Write a config

Type this:

printf 'server { listen 80; location / { return 200 "from my conf\\n"; } }\n' > my.conf

You will see:

(no output)

What it means: A tiny nginx config.

Example 8: Mount the file

Type this:

docker run -d --name conf -p 8081:80 -v $PWD/my.conf:/etc/nginx/conf.d/default.conf:ro nginx:1.27
curl -s localhost:8081

You will see:

from my conf

What it means: Only default.conf was replaced.

Example 9: Editor gotcha

Type this:

docker exec conf cat /etc/nginx/conf.d/default.conf | head -c 30

You will see:

server { listen 80; location

What it means: Some editors save by making a new file. A single-file mount then still shows the old file; mount the folder if that happens.

Step 4: Read-only for safety

Why: The container should not change your config.

What: Add :ro. The container can read but not write.

Example 10: Try to write

Type this:

docker exec conf sh -c 'echo x >> /etc/nginx/conf.d/default.conf'

You will see:

sh: 1: cannot create /etc/nginx/conf.d/default.conf: Read-only file system

What it means: Your file is safe.

Example 11: Folder read-only

Type this:

docker run --rm -v $PWD/site:/s:ro alpine:3.20 rm /s/health.txt

You will see:

rm: can't remove '/s/health.txt': Read-only file system

What it means: Same for folders.

Example 12: Check the mode

Type this:

docker inspect conf --format '{{range .Mounts}}{{.RW}}{{end}}'

You will see:

false

What it means: RW false means read-only.

3. Bind mounts with --mount

Step 5: --mount type=bind

Why: Long form is clearer in scripts and Compose.

What: --mount type=bind,source=HOST,target=CONTAINER,readonly. It fails if the host path does not exist, which catches typos.

Example 13: Long form

Type this:

docker run --rm --mount type=bind,source=$PWD/site,target=/s alpine:3.20 ls /s

You will see:

health.txt
index.html

What it means: Same result as -v.

Example 14: Typo caught

Type this:

docker run --rm --mount type=bind,source=$PWD/sitee,target=/s alpine:3.20 ls /s

You will see:

docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist

What it means: Good error.

Example 15: -v hides the typo

Type this:

docker run --rm -v $PWD/sitee:/s alpine:3.20 ls /s; ls -d sitee

You will see:

(empty)
sitee

What it means: -v made an empty folder owned by root. Delete it with sudo rm -r sitee.

Step 6: Relative paths

Why: Beginners type -v ./site:/x and get confused.

What: docker run -v needs an absolute path for bind mounts on older versions. Use $PWD. Compose files accept ./ paths.

Example 16: Relative with -v

Type this:

docker run --rm -v site:/x alpine:3.20 ls /x

You will see:

(empty)

What it means: site without a slash is treated as a volume name, not a folder.

Example 17: With $PWD

Type this:

docker run --rm -v $PWD/site:/x alpine:3.20 ls /x

You will see:

health.txt
index.html

What it means: Correct.

Example 18: In Compose

Type this:

printf 'services:\n  web:\n    image: nginx:1.27\n    volumes:\n      - ./site:/usr/share/nginx/html\n' > compose.yaml
docker compose config | grep source

You will see:

source: /home/ravi/site

What it means: Compose turns ./ into a full path.

4. A real dev setup

Now we use the window for real work. A developer at Paytm or Zerodha does not edit plain HTML all day. She edits JavaScript or Python, and the server must restart by itself after every save. We get that with two things: a bind mount for the code, and a watcher program inside the container that notices file changes. Most languages have one: node --watch for Node.js, flask --debug for Python, and air for Go.

edit app.json the laptopbind mount ./ to /appcode seen in containernodemon restartsnew code runs in 1 second
Picture 2: Node.js dev loop

Step 7: Node.js with auto-reload

Why: A Paytm backend developer wants the server to restart on save.

What: Mount the code folder, and run a watcher like node --watch inside.

Example 19: Write the app

Type this:

mkdir -p api && printf 'require("http").createServer((q,r)=>r.end("v1\\n")).listen(3000)\n' > api/app.js

You will see:

(no output)

What it means: A tiny Node server.

Example 20: Run with watch

Type this:

docker run -d --name napi -p 3000:3000 -v $PWD/api:/app -w /app node:22-alpine node --watch app.js
sleep 2; curl -s localhost:3000

You will see:

v1

What it means: Running from the host file.

Example 21: Edit and see

Type this:

sed -i 's/v1/v2/' api/app.js; sleep 2; curl -s localhost:3000

You will see:

v2

What it means: node --watch restarted it on save.

Step 8: Keep node_modules inside

Why: Host and container may need different builds of packages.

What: Mount the code with a bind mount, and put node_modules on a separate named volume so the host folder does not cover it.

Example 22: The problem

Type this:

docker run --rm -v $PWD/api:/app -w /app node:22-alpine ls node_modules

You will see:

ls: node_modules: No such file or directory

What it means: The bind mount hid anything the image had there.

Example 23: The fix

Type this:

docker run --rm -v $PWD/api:/app -v napi_mods:/app/node_modules -w /app node:22-alpine sh -c 'npm init -y >/dev/null; npm i ms >/dev/null; ls node_modules'

You will see:

ms

What it means: Packages live in the volume.

Example 24: Host stays clean

Type this:

ls api

You will see:

app.js
node_modules
package.json

What it means: Docker made an empty mount-point folder; the packages themselves are in the volume.

Step 9: File ownership

Why: Files made by the container can be owned by root on your laptop.

What: The container user writes files with its own user ID. Run with -u $(id -u):$(id -g) so new files belong to you.

Example 25: Root-owned file

Type this:

docker run --rm -v $PWD/site:/s alpine:3.20 touch /s/root.txt; ls -l site/root.txt

You will see:

-rw-r--r-- 1 root root 0 Oct 25 05:40 site/root.txt

What it means: You cannot edit it without sudo.

Example 26: Run as yourself

Type this:

docker run --rm -u $(id -u):$(id -g) -v $PWD/site:/s alpine:3.20 touch /s/me.txt; ls -l site/me.txt

You will see:

-rw-r--r-- 1 ravi ravi 0 Oct 25 05:40 site/me.txt

What it means: Owned by you, normal 0644 mode.

Example 27: Fix the old one

Type this:

sudo chown $(id -u):$(id -g) site/root.txt

You will see:

(no output)

What it means: Change owner, never use a world-writable mode.

Step 10: Python with live reload

Why: A Zerodha developer builds a Flask API and wants the same fast loop.

What: Mount the code and run Flask in debug mode. It reloads when a .py file changes.

Example 28: Write the app

Type this:

mkdir -p py && printf 'from flask import Flask\napp=Flask(__name__)\n@app.get("/")\ndef h(): return "price v1\\n"\n' > py/app.py

You will see:

(no output)

What it means: A tiny Flask app.

Example 29: Run with reload

Type this:

docker run -d --name fl -p 5000:5000 -v $PWD/py:/app -w /app python:3.12-slim sh -c 'pip install -q flask && flask --app app run --debug --host 0.0.0.0'
sleep 15; curl -s localhost:5000

You will see:

price v1

What it means: Running from the host file.

Example 30: Edit and see

Type this:

sed -i 's/v1/v2/' py/app.py; sleep 2; curl -s localhost:5000

You will see:

price v2

What it means: Flask noticed the change and reloaded.

Step 11: Find and debug bind mounts

Why: When a page shows old content, check what is really mounted.

What: docker inspect lists each mount with Type bind, Source and Destination. docker exec lets you look from inside.

Example 31: List mounts

Type this:

docker inspect dev --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{end}}'

You will see:

bind /home/ravi/site -> /usr/share/nginx/html

What it means: The real host folder.

Example 32: Compare both sides

Type this:

ls site; docker exec dev ls /usr/share/nginx/html

You will see:

health.txt index.html me.txt root.txt
health.txt  index.html  me.txt  root.txt

What it means: Same files, so the mount is right.

Example 33: Wrong folder mounted

Type this:

docker run -d --name wrong -v $PWD/sit:/usr/share/nginx/html -p 8085:80 nginx:1.27
curl -s -o /dev/null -w '%{http_code}\n' localhost:8085

You will see:

403

What it means: An empty folder gives 403 Forbidden. inspect would show the typo sit. Remove with docker rm -f wrong fl.

Step 12: Bind mounts are for dev, not production

Why: At Swiggy, production servers do not hold source code folders.

What: In production, copy code into the image with COPY so the image is complete. Use bind mounts in production only for small config files or sockets.

Example 34: Dev run

Type this:

docker run -d -v $PWD/site:/usr/share/nginx/html nginx:1.27

You will see:

...

What it means: Fast edits.

Example 35: Prod image

Type this:

printf 'FROM nginx:1.27\nCOPY site/ /usr/share/nginx/html/\n' > Dockerfile
docker build -q -t checkout:1.0 .

You will see:

sha256:4d1f...

What it means: Code is baked in.

Example 36: Clean up

Type this:

docker rm -f dev conf napi; docker volume rm napi_mods

You will see:

dev
conf
napi
napi_mods

What it means: Done.

Check yourself

Q1. What does a bind mount connect?

A host file or folder to a path in the container.

Q2. Why use :ro?

So the container cannot change your files.

Q3. Why prefer --mount type=bind?

It errors if the host path is missing.

Q4. How do you keep files owned by you?

Run with -u $(id -u):$(id -g).

Q5. Use bind mounts for app code in production?

No, COPY code into the image.

Friends, a bind mount is a window between two rooms. Save on the laptop and it shows in the container at once. Did you understand? Let us try it once more. For production, though, use COPY.

Ravindra Bagale's Tip

For dev use -u $(id -u):$(id -g), so you will not need sudo.

Lab: try at home

  1. Serve a local folder with nginx and edit it live.
  2. Mount one config file read-only.
  3. Show the typo difference between -v and --mount.
  4. Run a Node app with --watch and edit it.
  5. Make files owned by you, not root.

Recap

  • Bind mounts share host files with the container.
  • Changes appear instantly; great for development.
  • Use :ro and --mount for safety.
  • Separate node_modules with a volume.
  • In production, COPY code into the image.

Ravi Sir's Cards for this lesson

मराठी cards