Docker provides two good options for moving your code into an image or container: bind mounts and the Dockerfile
COPY instruction. In this post, I'll explain why images should always use the
COPY instruction in production, and why it may be more convenient to use bind mounts in development.
COPY instruction in a Dockerfile is used to copy files or directories from the host machine filesystem into an image. For example, the following Dockerfile sets up a NodeJS application for running in production mode.
# Install dependencies first to take advantage of Docker layer caching.
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --no-cache --production
# Copy the application files into the image.
COPY . .
CMD [ "node", "app.js" ]
Build and run:
$ docker build -t myapp .
$ docker run -d -p 3000:3000 myapp
COPY instruction recursively copies files and directories from the host into an image, which means that sensitive files may also be copied in. Similar to Git’s
.dockerignore file allows you to prevent certain files from being copied into the image. You should always include a
.dockerignore file when using the
COPY instruction. At minimum, it should include files related to your version control system and locally installed dependencies. A typical NodeJS application, for example, might use the following.
A bind mount allows you to mount a file or directory from the host machine into the container. In the below snippet, the working directory is mounted and accessible from
/app within the container. Once the container is running, you can interact with it through a shell.
$ docker run --rm -it -p 3000:3000 -v $(pwd):/app myapp bash
root@id:/app# yarn global add nodemon
root@id:/app# nodemon app.js
nodemon is a tool that watches for file changes and automatically restarts the application when changes are detected. In the above example, when you change files on the host machine, those changes are automatically visible in the container via the bind mount, and your app will automatically by restarted by
Picking Between the
COPY Instruction and Bind Mounts
When should you use the
COPY instruction, and when is appropriate to use a bind mount?
Bind mounts are great for local development. They provide convenience through the fact that changes to the host’s development environment and code are immediately reflected within the container, and files that the application creates in the container, like build artifacts or a log file, become available from the host.
However, bind mounts do come with some security concerns. From the Docker storage documentation:
One side effect of using bind mounts, for better or for worse, is that you can change the host filesystem via processes running in a container, including creating, modifying, or deleting important system files or directories. This is a powerful ability which can have security implications, including impacting non-Docker processes on the host system.
If you use Docker for development this way, your production Dockerfile would copy the production-ready artifacts directly into the image, rather than relying on a bind mount.
A bind mount exposes the host system to the container, and reduces the security and isolation of the container. Even a read-only bind mount can expose files that would otherwise be excluded from the image by
.dockerignore. To avoid these issues in production, the
COPY command should be used to transfer code into the image, and not a bind mount.
The Docker documentation provides details on other mount types, and gives additional detail on their possible use cases. In particular, there are certain use cases where it can be advantageous to use a bind mount for configuration files.