Generally speaking, Docker containers should have everything they need access to baked into the image. There are times, however, that it may be necessary to have additional files or directories provided to the container to persist information. These can include, but are not limited to:
- Configuration files
- Data persistence (usually only for local databases for development)
- Application package hotswap during development
- Saving artifacts generated by the application
Docker has two ways to provide such storage: bind mounts and volumes.
Bind Mounts
Bind mounts are used to provide access to a directory on the host machine. On a Linux host, Docker allows you to bind a user defined directory into the root filesystem of the container, effectively allowing you to do the equivalent of mount –bind for your directly to link it directly to the container’s filesystem. This is ideal for providing custom configuration files or saving off build artifacts to your host directory. To mount a directory into a container, execute the following on an example container:
mkdir -p testsite echo "Hello, world!" > testsite/index.html docker run -d --rm --name test --mount type=bind,source=$(pwd)/testsite,target=/usr/share/nginx/html -p 80:80 nginx
This creates a simple dummy site and then pulls down the nginx image, running it with the content of our simple site. If you open your web browser to http://localhost, you will see the “Hello, world!” message that we left in our sample directory. Alternatively, instead of the –mount option, you can use the older style -v syntax:
docker run -d --rm --name test -v $(pwd)/testsite:/usr/share/nginx/html -p 80:80 nginx
It is recommended that you use the –mount option as it is more precise in its definition. The -v option is only still available for legacy purposes.
We can inspect the container and see that the mount is defined for our container by using docker inspect test :
... "Mounts": [ { "Type": "bind", "Source": "/tmp/testsite", "Destination": "/usr/share/nginx/html", "Mode": "", "RW": true, "Propagation": "rprivate" } ], ...
We can specify bind mounts in a compose file as such:
version: "3.2" services: web: image: nginx:alpine volumes: - type: bind source: ./testsite target: /usr/share/nginx/html
You can also map an individual file onto a container, but it is rare to do so. If using the -v syntax, note that if the file is missing, a directory will be created with the name of the file that you specify. This can be confounding if you use this in a Compose file. More can be found on the Docker website:
https://docs.docker.com/storage/bind-mounts/
Bind mounts on Docker for Mac do not use native bind mounting, but instead uses osxfs to attempt to provide a near-native experience for bind mounts. It is still slower than a native bind mount running on Linux, but should still work seamlessly with local HFS+ filesystems. By default, it only has access to the /Users, /Volumes, /private, and /tmp directories. See official documentation details on Docker’s official website:
https://docs.docker.com/docker-for-mac/osxfs/
Docker Volumes
Docker volumes are file system mounts that are managed completely by the Docker engine. Historically, these have been called “named volumes,” just in case you see a reference to it in literature or in command line help or error messages. When a Docker volume is created, the directory is stored in the /var/lib/docker/volumes/ directory. The typical use case for a named volume would be for something like data persistence or sharing data between containers. Let’s dig out the Pastr app from the first tutorial. We’ll add the mount in the docker-compose.yml file:
database: image: redis:latest volumes: - type: volume source: pastrdatastore target: /data ports: - "6379:6379" ... volumes: pastrdatastore:
The top-level volumes directive (at the bottom of the snippet) denotes that a datastore shall be created via this compose file. After starting the container with docker-compose up -d the Docker engine will create the pastrdatastore volume.
$ docker volume ls DRIVER VOLUME NAME local pastr_pastrdatastore $ docker volume inspect pastr_pastrdatastore [ { "CreatedAt": "2018-09-25T20:13:41-05:00", "Driver": "local", "Labels": { "com.docker.compose.project": "pastr", "com.docker.compose.version": "1.22.0", "com.docker.compose.volume": "pastrdatastore" }, "Mountpoint": "/var/lib/docker/volumes/pastr_pastrdatastore/_data", "Name": "pastr_pastrdatastore", "Options": null, "Scope": "local" } ]
It creates it a volume in the /var/lib/docker/volumes directory. This volume is mounted to the database container, which we see when we inspect it with docker inspect pastr_database_1 .
"Mounts": [ { "Type": "volume", "Name": "pastr_pastrdatastore", "Source": "/var/lib/docker/volumes/pastr_pastrdatastore/_data", "Destination": "/data", "Driver": "local", "Mode": "rw", "RW": true, "Propagation": "" } ],
Note that on a Linux machine, this volume exists on the native filesystem. However, on a Windows or Mac system, this volume exists within the virtual machine; you can’t access it directly, nor should you try to, even on a Linux machine. If you need to mount the data store to inspect its contents, you can run it with docker run -it –rm –mount source=pastr_pastrdatastore,destination=/mnt ubuntu /bin/bash.
For more details, please see the official Docker documentation:
https://docs.docker.com/storage/volumes/#start-a-service-with-volumes