The dev cycle is a sacred thing. Type code, save the file, and your change occurs instantaneously.
But once you enter the world of Docker, that rapid cycle is gone. Now you're treated to a cumbersome cycle: change a line of code, stop the container, rebuild the image, and run a new container (Figure 1) . It will take minutes, completely ruining your productivity.
1. Say Hello to Bind Mount 👋
A bind mount is a powerful Docker feature that creates a direct link, or a "portal," between a directory on your host machine (your laptop) and a directory inside your container (Figure 2).
When you create a bind mount:
- Files and folders on your host are mirrored inside the container.
- Whatever changes you make to the files stored in your host machine are immediately reflected inside the container and also the other way around.
- This is not the same as a Docker volume, which Docker manages and is for keeping data around (e.g., a database). A bind mount is perfect for dev because it binds your IDE directly into the container's filesystem.
2. A Practical Example: The Slow Way vs. The Fast Way
Let's imagine a simple node.js
Express server
-
app.js: A basic web server
const express = require('express');
const app = express();
const PORT = 2000;
app.get('/', (req, res) => {
res.send('Version 1: Hello from inside my Docker container!');
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server is running on port ${PORT}`);
});
-
package.json: We'll add
nodemon
, a tool that automatically restarts the server when it detects file changes.{ "name": "live-reload-demo", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js", "dev": "nodemon app.js" }, "dependencies": { "express": "^4.18.2", "nodemon": "^3.0.1" } }
-
Dockerfile: A standard setup for a Node app.
FROM node:18-alpine WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . EXPOSE 2000 CMD [ "npm", "start" ]
2. 1 The Slow way of doing things 🐌
a. Build the image using docker build -t my-app
b. Run the container using docker run -p 3000:3000 my-app
c. Let’s say you edit the response message in app.js
to “Version 2… “
d. See the change? Nope, The old code is baked into the image.
e. You now have to: Stop the container (docker stop ..
), rebuild the image (docker build..
) and run a new container (docker run ..
). This whole cycle is painfully slow.
2.2 The Fast way with a Bind Mount 🚀
Let's fix this. A bind mount is created with the -v (or --volume) flag in the docker run command. The syntax is host/path:container/path.
- Build the image just once. This initial build is still necessary to install the npm dependencies.
docker build -t my-app .
2. Run the container with a bind mount. This command links your current working directory $(pwd)
on your host to the /usr/src/app directory
inside the container. We also override the default CMD to use our dev script with nodemon
.
docker run -p 2000:2000 -v $(pwd):/usr/src/app my-app npm run dev
Now let’s go to http://localhost:2000
. You’ll see Version 1.
-p 2000:2000
maps port 2000 on your local machine to the port 2000 in your container.-v $(pwd):/usr/src/app
mounts the current directory to the container’s working directory.npm run dev
runs nodemon script instead of npm start
Now open app.js
using nano or vi and change the message to “Version 2: Reloading Live”, save it.
Open your terminal where your docker run command is running. You will see nodemon
recompile your server. Reload your web page.
Voilà! ✨
Your changes are live instantaneously, all without a rebuild.
Key Takeaways:
By incorporating a bind mount into your Docker run command, you establish a local-development environment-direct connection to the containerized application. This useful flag removes the slower element of the Docker dev cycle and reinstates the rapid iteration that productivity needs. You don't have to rebuild anymore; you have the benefit of live reload.