Skip to content

Lesson 3.3: Bind Mounts

Welcome to Lesson 3.3! You've already mastered volumes for persistent, Docker-managed storage. Now we'll explore bind mounts – a different approach that gives you direct access to the host filesystem. Bind mounts are incredibly useful for development, injecting configuration, and sharing source code between the host and containers. By the end of this lesson, you'll understand when to use bind mounts, how they differ from volumes, and how to use them effectively.


Learning Objectives

TIP

By the end of this lesson, you will be able to:

  • Explain what a bind mount is and how it differs from a volume.
  • Mount a host directory into a container using -v or --mount.
  • Use bind mounts for development (live code reload) and configuration management.
  • Understand the implications of host filesystem permissions on bind mounts.
  • List the pros and cons of bind mounts compared to volumes.
  • Recognize security considerations when using bind mounts.

1. What Are Bind Mounts?

A bind mount is a mapping between a host directory (or file) and a location inside a container. Unlike volumes, which are managed by Docker, bind mounts give you full control over the host path. The container sees the exact same files as the host, and changes made on either side are immediately reflected on the other.

Bind mounts have been available since the early days of Docker and are still heavily used, especially in development environments.

Key characteristics:

  • The host path is absolute (e.g., /home/user/project on Linux, C:\Users\user\project on Windows).
  • If the host directory doesn't exist, Docker creates it automatically (with some caveats on different OSes).
  • Bind mounts bypass the Docker storage driver; they are direct filesystem mounts.
  • They provide no additional features like snapshots or backup commands (those are up to you).

2. Bind Mounts vs. Volumes

FeatureBind MountsVolumes
ManagementManaged by user (host paths)Managed by Docker (docker volume commands)
PortabilityHost-specific paths; not portable across machinesPortable; can be named and shared
PerformanceNative host filesystem performanceNative host filesystem performance
Backup/RestoreUse host tools (tar, rsync)Docker-specific commands (via container)
Use casesDevelopment, injecting config, sharing sourceProduction, databases, any stateful service
Host dependencyDirectly depends on host directory structureIndependent of host directory structure
PermissionsFollow host permissionsDocker manages ownership (root by default)
SecurityCan expose sensitive host directoriesMore isolated (within Docker's control)

When to use bind mounts:

  • Development: Mount source code into a container to see changes immediately without rebuilding.
  • Configuration injection: Override config files in a container with host-side versions.
  • Sharing host tools: Access host binaries or Unix sockets (e.g., Docker socket).
  • Data that must be accessed directly on host: When you need to manipulate files using host tools.

When to use volumes:

  • Production data (databases, application state).
  • Data that should be portable across different hosts.
  • When you need backup/restore features via Docker CLI.

3. Mounting a Bind Mount

You can mount a bind mount using either -v or the more explicit --mount flag. The syntax differs slightly.

3.1. Using -v (Volume flag)

Syntax:

-v /host/path:/container/path[:options]
  • Host path must be absolute.
  • If the host path contains spaces, wrap it in quotes.
  • Options: ro (read-only), rw (read-write, default), z/Z for SELinux.

Example:

bash
docker run -v /home/user/myapp:/app -w /app node:18 npm start

3.2. Using --mount

The --mount flag is more explicit and preferred in scripts or when you need additional options.

Syntax:

--mount type=bind,source=/host/path,target=/container/path[,readonly]
  • type=bind indicates a bind mount.
  • source is the absolute host path.
  • target is the container mount point.
  • readonly (optional) mounts as read-only.

Example:

bash
docker run --mount type=bind,source=/home/user/myapp,target=/app node:18 npm start

3.3. Read-Only Bind Mounts

To prevent the container from writing to the mounted directory, add readonly (with --mount) or :ro (with -v).

bash
docker run --mount type=bind,source=/home/user/config,target=/etc/app/config,readonly myapp

4. Development Workflow with Bind Mounts

Bind mounts are a developer's best friend. They allow you to edit source code on your host and immediately see changes inside the container without rebuilding the image.

4.1. Live Reload Example (Node.js)

  1. Create a simple Node.js app on your host:

    bash
    mkdir mynodeapp
    cd mynodeapp
    echo 'console.log("Hello from Node!");' > index.js
  2. Run a Node container with a bind mount to the current directory, and run the script:

    bash
    docker run --rm -v $(pwd):/app -w /app node:18 node index.js

    Output: Hello from Node!

  3. Modify index.js on your host (e.g., change the message), and re-run the same command. The change is picked up immediately because the container sees the host filesystem.

4.2. Hot Reload with Nodemon (Node.js)

For a more realistic development server, use a tool like nodemon to restart the server on file changes.

bash
docker run --rm -v $(pwd):/app -w /app node:18 sh -c "npm install && npx nodemon index.js"

Now any change to index.js triggers a restart automatically.


5. Configuration Injection with Bind Mounts

Bind mounts are perfect for overriding configuration files without modifying the image.

Example: Override Nginx default page.

  1. Create a custom index.html on your host:
    bash
    echo "<h1>Custom Nginx Page</h1>" > custom-index.html
  2. Run Nginx and mount the custom file over the default index.html:
    bash
    docker run -d --name nginx-custom -p 8080:80 -v $(pwd)/custom-index.html:/usr/share/nginx/html/index.html:ro nginx
  3. Visit http://localhost:8080 – you'll see your custom page.

6. Permissions and Ownership

When you bind mount a host directory, the files inside retain their original ownership and permissions. Inside the container, the user (often root or a specific application user) must have appropriate permissions to read/write those files.

6.1. User Mismatch

If the container runs as a non-root user (e.g., node user in the official Node image) and the mounted files are owned by your host user, the container may not be able to write to them. This can cause permission errors.

Solutions:

  • Run the container as root (not recommended for production).
  • Adjust file permissions on the host to allow the container user to write (e.g., chmod 777 – but be careful).
  • Use user namespace remapping (advanced).
  • In development, you can often ignore this if you only need read access, or you can create a Dockerfile that adds a user with the same UID as your host user.

6.2. Example Permission Issue

bash
# Create a file owned by your host user
echo "data" > data.txt

# Run a container as node user (UID 1000) trying to write to the mounted file
docker run --rm -v $(pwd):/data node:18 sh -c "echo new >> /data/data.txt"

This may fail because the node user (UID 1000) may not have write permission. Check the actual UID in the image with docker run --rm node:18 id.


7. Security Considerations

  • Avoid mounting sensitive host directories (e.g., /etc, /var/run/docker.sock) unless absolutely necessary. Doing so can give containers root-like access to your host.
  • Use readonly mounts when the container does not need to write to the mounted files.
  • Be cautious with relative paths in -v; Docker may interpret them as volume names instead of bind mounts if they don't start with / or ./. Always use absolute paths for bind mounts.

8. Bind Mounts on Different Operating Systems

  • Linux: Works natively with absolute paths (e.g., /home/user/project).
  • macOS / Windows (Docker Desktop): Bind mounts are available, but the host path is relative to the Docker Desktop VM. For example, /Users/username/project on macOS, C:\Users\username\project on Windows. Docker Desktop automatically shares these directories with the VM.
  • Windows containers: Bind mounts also work but follow Windows path conventions (e.g., C:\data).

Hands-On Tasks

Task 1: Basic Bind Mount

  1. Create a directory ~/docker-bind and create a file test.txt inside it with some content.
  2. Run an Alpine container that mounts that directory to /mnt and displays the file:
    bash
    docker run --rm -v ~/docker-bind:/mnt alpine cat /mnt/test.txt
  3. Modify the file on your host and run the command again. The change is reflected.

Task 2: Live Development with Python

  1. Create a directory flask-app.
  2. Inside, create a file app.py:
    python
    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def hello():
        return "Hello from Flask!"
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5000)
  3. Run a container with Flask installed, mounting the directory, and exposing port 5000:
    bash
    docker run --rm -p 5000:5000 -v $(pwd):/app -w /app python:3.11-slim sh -c "pip install flask && python app.py"
  4. Access http://localhost:5000. Modify app.py (e.g., change the message) and refresh. Flask's debug mode would auto-reload if enabled; here you'll need to restart the container to see changes (unless you use a development server with hot reload).

Task 3: Inject Configuration into Nginx

  1. Create a custom nginx.conf on your host that changes the default welcome page text.
  2. Run an Nginx container with a bind mount for the config file (and also a bind mount for the default HTML if you want).
  3. Verify the configuration change.

Task 4: Permission Experiment

  1. Create a directory shared and a file data.txt owned by your user.
  2. Run a container as a non-root user (e.g., --user 1000:1000) and try to write to the mounted file:
    bash
    docker run --rm -v $(pwd)/shared:/data --user 1000:1000 alpine sh -c "echo test >> /data/data.txt"
  3. Check the exit code. If it fails, adjust permissions (chmod 777 shared/data.txt) and try again.

Task 5: Read-Only Bind Mount

  1. Run a container with a read-only bind mount to your home directory (or a test directory).
  2. Attempt to write a file inside the mount. It should fail with a read-only filesystem error.

Task 6: Sharing the Docker Socket (Advanced)

  • Mount /var/run/docker.sock into a container to allow the container to control the host Docker daemon. This is powerful but risky.
    bash
    docker run -v /var/run/docker.sock:/var/run/docker.sock docker:latest docker ps
  • This gives the container administrative access to your Docker. Only do this in trusted environments.

Summary

Key Takeaways

  • Bind mounts map a host directory or file directly into a container.
  • They are ideal for development (live reload), configuration injection, and sharing host resources.
  • Use absolute paths for bind mounts; relative paths may be misinterpreted as named volumes.
  • Bind mounts bypass Docker's storage driver and rely on host filesystem permissions.
  • For production stateful workloads, volumes are preferred due to better management and portability.
  • Security: avoid mounting sensitive host directories; use read-only mounts when possible.

Check Your Understanding

  1. What is the main difference between a bind mount and a volume?
  2. Write a docker run command to mount the host directory /home/user/project to /app in the container, using --mount.
  3. When would you choose a bind mount over a volume?
  4. What permission issue might arise when mounting a host directory into a container that runs as a non-root user?
  5. How can you mount a host file as read-only inside a container?
  6. Why is mounting /var/run/docker.sock considered a security risk?
Click to see answers
  1. Bind mounts map a specific host directory into the container and are managed by the user. Volumes are managed by Docker with their own CLI commands and stored in Docker's managed directories.
  2. docker run --mount type=bind,source=/home/user/project,target=/app myimage
  3. During development (for live code reload), when injecting configuration files, or when you need access to host tools or sockets.
  4. If the container runs as a non-root user whose UID doesn't match the host file owner, the container user may not have permission to read or write the mounted files.
  5. Add readonly to --mount or :ro to -v.
  6. It gives the container access to the Docker daemon on the host, effectively giving it root-level control over Docker, including the ability to start privileged containers or access the host filesystem.

Additional Resources


Next Up

In the next lesson, we'll cover tmpfs mounts – an in-memory storage option for temporary, non-persistent data. See you there!