feedback: improve getting started documentation on bind mounts

Signed-off-by: David Karlsson <david.karlsson@docker.com>
This commit is contained in:
David Karlsson 2022-12-14 16:23:46 +01:00
parent 756d3c1e40
commit 310dfbe856
3 changed files with 202 additions and 105 deletions

View File

@ -31,7 +31,7 @@ What you'll see is that the files created in one container aren't available in a
2. Validate that you can see the output by accessing the terminal in the container. To do so, go to **Containers** in Docker Desktop, hover over the container running the **ubuntu** image, and select the **Show container actions** menu. From the dropdown menu, select **Open in terminal**.
You will see a terminal that is running a shell in the ubuntu container. Run the following command to see the content of the `/data.txt` file. Close this terminal afterwards again.
You will see a terminal that is running a shell in the Ubuntu container. Run the following command to see the content of the `/data.txt` file. Close this terminal afterwards again.
```console
$ cat /data.txt
@ -69,11 +69,11 @@ the container back to the host machine. If a directory in the container is mount
directory are also seen on the host machine. If we mount that same directory across container restarts, we'd see
the same files.
There are two main types of volumes. We will eventually use both, but we will start with **named volumes**.
There are two main types of volumes. We will eventually use both, but we will start with volume mounts.
## Persist the todo data
By default, the todo app stores its data in a [SQLite Database](https://www.sqlite.org/index.html){:target="_blank" rel="noopener" class="_"} at
By default, the todo app stores its data in a SQLite database at
`/etc/todos/todo.db` in the container's filesystem. If you're not familiar with SQLite, no worries! It's simply a relational database in
which all of the data is stored in a single file. While this isn't the best for large-scale applications,
it works for small demos. We'll talk about switching this to a different database engine later.
@ -83,9 +83,9 @@ next container, it should be able to pick up where the last one left off. By cre
(often called "mounting") it to the directory the data is stored in, we can persist the data. As our container
writes to the `todo.db` file, it will be persisted to the host in the volume.
As mentioned, we are going to use a **named volume**. Think of a named volume as simply a bucket of data.
Docker maintains the physical location on the disk and you only need to remember the name of the volume.
Every time you use the volume, Docker will make sure the correct data is provided.
As mentioned, we are going to use a volume mount. Think of a volume mount as an opaque bucket of data.
Docker fully manages the volume, including where it is stored on disk. You only need to remember the
name of the volume.
1. Create a volume by using the `docker volume create` command.
@ -95,11 +95,11 @@ Every time you use the volume, Docker will make sure the correct data is provide
2. Stop and remove the todo app container once again in the Dashboard (or with `docker rm -f <id>`), as it is still running without using the persistent volume.
3. Start the todo app container, but add the `-v` flag to specify a volume mount. We will use the named volume and mount
it to `/etc/todos`, which will capture all files created at the path.
3. Start the todo app container, but add the `--mount` option to specify a volume mount. We will give the volume a name, and mount
it to `/etc/todos` in the container, which will capture all files created at the path.
```console
$ docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
$ docker run -dp 3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started
```
4. Once the container starts up, open the app and add a few items to your todo list.
@ -117,17 +117,9 @@ Every time you use the volume, Docker will make sure the correct data is provide
Hooray! You've now learned how to persist data!
>**Note**
>
>While named volumes and bind mounts (which we'll talk about in a minute) are the two main types of volumes supported
>by a default Docker engine installation, there are many volume driver plugins available to support NFS, SFTP, NetApp,
>and more! This will be especially important once you start running containers on multiple hosts in a clustered
>environment with Swarm, Kubernetes, etc.
>
## Dive into the volume
A lot of people frequently ask "Where is Docker _actually_ storing my data when I use a named volume?" If you want to know,
A lot of people frequently ask "Where is Docker storing my data when I use a volume?" If you want to know,
you can use the `docker volume inspect` command.
```console
@ -148,11 +140,11 @@ $ docker volume inspect todo-db
The `Mountpoint` is the actual location on the disk where the data is stored. Note that on most machines, you will
need to have root access to access this directory from the host. But, that's where it is!
>**Accessing volume data directly on Docker Desktop**
>
>While running in Docker Desktop, the Docker commands are actually running inside a small VM on your machine.
>If you wanted to look at the actual contents of the Mountpoint directory, you would need to first get inside
>of the VM.
> **Accessing volume data directly on Docker Desktop**
>
> While running in Docker Desktop, the Docker commands are actually running inside a small VM on your machine.
> If you wanted to look at the actual contents of the Mount point directory, you would need to look inside of
> that VM.
## Next steps
@ -161,4 +153,4 @@ At this point, you have a functioning application that can survive restarts! You
However, you saw earlier that rebuilding images for every change takes quite a bit of time. There's got to be a better
way to make changes, right? With bind mounts (which was hinted at earlier), there is a better way!
[Use bind mounts](06_bind_mounts.md){: .button .primary-btn}
[Use bind mounts](06_bind_mounts.md){: .button .primary-btn}

View File

@ -1,121 +1,226 @@
---
title: "Use bind mounts"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
keywords: >
get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Using bind mounts in our application
---
In the previous chapter, we talked about and used a **named volume** to persist the data in our database.
Named volumes are great if we simply want to store data, as we don't have to worry about _where_ the data
is stored.
In the previous chapter, we talked about and used a volume mount to persist the
data in our database. A volume mount is a great choice when you need somewhere
persistent to store your application data.
With **bind mounts**, we control the exact mountpoint on the host. We can use this to persist data, but it's often
used to provide additional data into containers. When working on an application, we can use a bind mount to
mount our source code into the container to let it see code changes, respond, and let us see the changes right
away.
A bind mount is another type of mount, which lets you share a directory from the
host's filesystem into the container. When working on an application, you can
use a bind mount to mount source code into the container. The container sees the
changes you make to the code immediately, as soon as you save a file. This means
that you can run processes in the container that watch for filesystem changes
and respond to them.
For Node-based applications, [nodemon](https://npmjs.com/package/nodemon){:target="_blank" rel="noopener" class="_"} is a great tool to watch for file
changes and then restart the application. There are equivalent tools in most other languages and frameworks.
In this chapter, we'll see how we can use bind mounts and a tool called
[nodemon](https://npmjs.com/package/nodemon){:target="_blank" rel="noopener"
class="_"} to watch for file changes, and then restart the application
automatically. There are equivalent tools in most other languages and
frameworks.
## Quick volume type comparisons
Bind mounts and named volumes are the two main types of volumes that come with the Docker engine. However, additional
volume drivers are available to support other use cases ([SFTP](https://github.com/vieux/docker-volume-sshfs){:target="_blank" rel="noopener" class="_"}, [Ceph](https://ceph.com/geen-categorie/getting-started-with-the-docker-rbd-volume-plugin/){:target="_blank" rel="noopener" class="_"}, [NetApp](https://netappdvp.readthedocs.io/en/stable/){:target="_blank" rel="noopener" class="_"}, [S3](https://github.com/elementar/docker-s3-volume){:target="_blank" rel="noopener" class="_"}, and more).
The following table outlines the main differences between volume mounts and bind
mounts.
| | Named Volumes | Bind Mounts |
| - | ------------- | ----------- |
| Host Location | Docker chooses | You control |
| Mount Example (using `-v`) | my-volume:/usr/local/data | /path/to/data:/usr/local/data |
| Populates new volume with container contents | Yes | No |
| Supports Volume Drivers | Yes | No |
| | Named volumes | Bind mounts |
| -------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------- |
| Host location | Docker chooses | You decide |
| Mount example (using `--mount`) | `type=volume,src=my-volume,target=/usr/local/data` | `type=bind,src=/path/to/data,target=/usr/local/data` |
| Populates new volume with container contents | Yes | No |
| Supports Volume Drivers | Yes | No |
## Start a dev-mode container
## Trying out bind mounts
To run our container to support a development workflow, we will do the following:
Before looking at how we can use bind mounts for developing our application,
let's run a quick experiment to get a practical understanding of how bind mounts
work.
If you're following these steps on Windows, make sure to use PowerShell and not
command prompt (`cmd`).
1. Open a terminal and make sure your current working directory is in the `app`
directory of the getting started repository.
2. Run the following command to start `bash` in an `ubuntu` container with a
bind mount.
```console
$ docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash
```
The `--mount` option tells Docker to create a bind mount, where `src` is the
current working directory on your host machine (`getting-started/app`), and
`target` is where that directory should appear inside the container (`/src`).
3. After running the command, Docker starts an interactive `bash` session in the
root directory of the container's filesystem.
```console
root@ac1237fad8db:/# pwd
/
root@ac1237fad8db:/# ls
bin dev home media opt root sbin srv tmp var
boot etc lib mnt proc run src sys usr
```
4. Now, change directory in the `src` directory.
This is the directory that you mounted when starting the container. Listing
the contents of this directory displays the same files as in the
`getting-started/app` directory on your host machine.
```console
root@ac1237fad8db:/# cd src
root@ac1237fad8db:/src# ls
Dockerfile node_modules package.json spec src yarn.lock
```
5. Create a new file named `myfile.txt`.
```console
root@ac1237fad8db:/src# touch myfile.txt
root@ac1237fad8db:/src# ls
Dockerfile myfile.txt node_modules package.json spec src yarn.lock
```
6. Now if you open this directory on the host, you'll see the `myfile.txt` file
has been created in the directory.
![File viewer on the host machine that sees the file created from the container](images/bind-mount-newfile.png)
7. From the host, delete the `myfile.txt` file.
8. In the container, list the contents of the `app` directory once more. You'll
see that the file is now gone.
```console
root@ac1237fad8db:/src# ls
Dockerfile node_modules package.json spec src yarn.lock
```
9. Stop the interactive container session with `Ctrl` + `D`.
And that's all for a brief introduction to bind mounts. This procedure
demonstrated how files are shared between the host and the container, and how
changes are immediately reflected on both sides. Now let's see how we can use
bind mounts to develop software.
## Run your app in a development container
The following steps describe how to run a development container with a bind
mount that does the following:
- Mount our source code into the container
- Install all dependencies, including the "dev" dependencies
- Start nodemon to watch for filesystem changes
- Install all dependencies
- Start `nodemon` to watch for filesystem changes
So, let's do it!
1. Make sure you don't have any previous `getting-started` containers running.
1. Make sure you don't have any `getting-started` containers currently running.
2. Run the following command from the app directory. We'll explain what's going on afterwards.
2. Run the following command from the `getting-started/app` directory.
If you are using an x86-64 Mac or Linux device, then use the following command.
If you are using an Mac or Linux device, then use the following command.
```console
$ docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
node:18-alpine \
sh -c "yarn install && yarn run dev"
```
```console
$ docker run -dp 3000:3000 \
-w /app --mount type=bind,src="$(pwd)",target=/app \
node:18-alpine \
sh -c "yarn install && yarn run dev"
```
If you are using Windows, then use the following command in PowerShell.
If you are using Windows, then use the following command in PowerShell.
```powershell
$ docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" `
node:18-alpine `
sh -c "yarn install && yarn run dev"
```
```powershell
$ docker run -dp 3000:3000 `
-w /app --mount type=bind,src="$(pwd)",target=/app `
node:18-alpine `
sh -c "yarn install && yarn run dev"
```
- `-dp 3000:3000` - same as before. Run in detached (background) mode and create a port mapping
- `-w /app` - sets the "working directory" or the current directory that the command will run from
- `-v "$(pwd):/app"` - bind mount the current directory from the host into the `/app` directory in the container
- `node:18-alpine` - the image to use. Note that this is the base image for our app from the Dockerfile
- `sh -c "yarn install && yarn run dev"` - the command. We're starting a shell using `sh` (alpine doesn't have `bash`) and
running `yarn install` to install _all_ dependencies and then running `yarn run dev`. If we look in the `package.json`,
we'll see that the `dev` script is starting `nodemon`.
- `-dp 3000:3000` - same as before. Run in detached (background) mode and
create a port mapping
- `-w /app` - sets the "working directory" or the current directory that the
command will run from
- `--mount type=bind,src="$(pwd)",target=/app` - bind mount the current
directory from the host into the `/app` directory in the container
- `node:18-alpine` - the image to use. Note that this is the base image for
our app from the Dockerfile
- `sh -c "yarn install && yarn run dev"` - the command. We're starting a
shell using `sh` (alpine doesn't have `bash`) and running `yarn install` to
install packages and then running `yarn run dev` to start the development
server. If we look in the `package.json`, we'll see that the `dev` script
starts `nodemon`.
3. You can watch the logs using `docker logs`. You'll know you're ready to go when you see this:
3. You can watch the logs using `docker logs`. You'll know you're ready to go
when you see this:
```console
$ docker logs -f <container-id>
nodemon src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
```
```console
$ docker logs -f <container-id>
nodemon src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
```
When you're done watching the logs, exit out by hitting `Ctrl`+`C`.
When you're done watching the logs, exit out by hitting `Ctrl`+`C`.
4. Now, let's make a change to the app. In the `src/static/js/app.js` file, let's change the "Add Item" button to simply say
"Add". This change will be on line 109:
4. Now, make a change to the app. In the `src/static/js/app.js` file, on line
109, change the "Add Item" button to simply say "Add":
```diff
- {submitting ? 'Adding...' : 'Add Item'}
+ {submitting ? 'Adding...' : 'Add'}
```
```diff
- {submitting ? 'Adding...' : 'Add Item'}
+ {submitting ? 'Adding...' : 'Add'}
```
5. Simply refresh the page (or open it) and you should see the change reflected in the browser almost immediately. It might
take a few seconds for the Node server to restart, so if you get an error, just try refreshing after a few seconds.
Save the file.
![Screenshot of updated label for Add button](images/updated-add-button.png){: style="width:75%;"}
{: .text-center }
5. Refresh the page in your web browser, and you should see the change reflected
almost immediately. It might take a few seconds for the Node server to
restart. If you get an error, try refreshing after a few seconds.
6. Feel free to make any other changes you'd like to make. When you're done, stop the container and build your new image
using:
![Screenshot of updated label for Add button](images/updated-add-button.png){:
style="width:75%;" .text-center}
```console
$ docker build -t getting-started .
```
6. Feel free to make any other changes you'd like to make. Each time you make a
change and save a file, the `nodemon` process restarts the app inside the
container automatically. When you're done, stop the container and build your
new image using:
Using bind mounts is _very_ common for local development setups. The advantage is that the dev machine doesn't need to have
all of the build tools and environments installed. With a single `docker run` command, the dev environment is pulled and ready
to go. We'll talk about Docker Compose in a future step, as this will help simplify our commands (we're already getting a lot
of flags).
```console
$ docker build -t getting-started .
```
Using bind mounts is common for local development setups. The advantage is that
the development machine doesn't need to have all of the build tools and
environments installed. With a single `docker run` command, dependencies and
tools are pulled and ready to go. We'll talk about Docker Compose in a future
step, as this will help simplify our commands (we're already getting a lot of
flags).
In addition to volume mounts and bind mounts, Docker also supports other mount
types and storage drivers for handling more complex and specialized use cases.
To learn more about the advanced storage concepts, see
[Manage data in Docker](https://docs.docker.com/storage/).
## Next steps
At this point, you can persist your database and respond rapidly to the needs and demands of your investors and founders. Hooray!
But, guess what? You received great news! Your project has been selected for future development!
At this point, you can persist your database and respond rapidly to the needs
and demands of your investors and founders. Hooray! But, guess what? You
received great news! Your project has been selected for future development!
In order to prepare for production, you need to migrate your database from working in SQLite to something that can scale a
little better. For simplicity, you'll keep with a relational database and switch your application to use MySQL. But, how
should you run MySQL? How do you allow the containers to talk to each other? You'll learn about that next!
In order to prepare for production, you need to migrate your database from
working in SQLite to something that can scale a little better. For simplicity,
you'll keep with a relational database and switch your application to use MySQL.
But, how should you run MySQL? How do you allow the containers to talk to each
other? You'll learn about that next!
[Multi container apps](07_multi_container.md){: .button .primary-btn}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB