Docker provides two good options for moving your code into an image or container: bind mounts and the Dockerfile [COPY](https://docs.docker.com/engine/reference/builder/#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
InstructionThe 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.
# DockerfileFROM node:carbon
WORKDIR /appENV NODE_ENV=production
# 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 . .
EXPOSE 3000CMD [ "node", "app.js" ]
Build and run:
$ docker build -t myapp .$ docker run -d -p 3000:3000 myapp
The 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 .gitignore
, Docker’s [.dockerignore](https://docs.docker.com/engine/reference/builder/#dockerignore-file)
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.
# .dockerignore.gitDockerfile.dockerignorenode_modulesnpm-debug.log
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 bashroot@id:/app# yarn global add nodemonroot@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 nodemon
.
COPY
Instruction and Bind MountsWhen 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.
And really, do not ignore [.dockerignore](https://codefresh.io/docker-tutorial/not-ignore-dockerignore/)
.