diff --git a/get-started/05_persisting_data.md b/get-started/05_persisting_data.md index bcab809721..2deb31aaeb 100644 --- a/get-started/05_persisting_data.md +++ b/get-started/05_persisting_data.md @@ -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 `), 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} \ No newline at end of file +[Use bind mounts](06_bind_mounts.md){: .button .primary-btn} diff --git a/get-started/06_bind_mounts.md b/get-started/06_bind_mounts.md index e2d2b7627e..ebbbac4031 100644 --- a/get-started/06_bind_mounts.md +++ b/get-started/06_bind_mounts.md @@ -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 - 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 + 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} diff --git a/get-started/images/bind-mount-newfile.png b/get-started/images/bind-mount-newfile.png new file mode 100644 index 0000000000..41b9a3ecc2 Binary files /dev/null and b/get-started/images/bind-mount-newfile.png differ