Merge pull request #11838 from nebuk89/master
Adding general docker getting started to docs
|
@ -27,12 +27,26 @@ guides:
|
|||
path: /get-docker/
|
||||
- sectiontitle: Get started
|
||||
section:
|
||||
- title: "Part 1: Orientation and setup"
|
||||
- title: "Part 1: Getting started"
|
||||
path: /get-started/
|
||||
- title: "Part 2: Build and run your image"
|
||||
path: /get-started/part2/
|
||||
- title: "Part 3: Share images on Docker Hub"
|
||||
path: /get-started/part3/
|
||||
- title: "Part 2: Our Application"
|
||||
path: /get-started/02_our_app/
|
||||
- title: "Part 3: Updating our Application"
|
||||
path: /get-started/03_updating_app/
|
||||
- title: "Part 4: Sharing Our Application"
|
||||
path: /get-started/04_sharing_app/
|
||||
- title: "Part 5: Persiting our DB"
|
||||
path: /get-started/05_persisting_data/
|
||||
- title: "Part 6: Using Bind Mounts"
|
||||
path: /get-started/06_bind_mounts/
|
||||
- title: "Part 7: Multi-Container Applications"
|
||||
path: /get-started/07_multi_container/
|
||||
- title: "Part 8: Using Docker Compose"
|
||||
path: /get-started/08_using_compose/
|
||||
- title: "Part 9: Image building tips"
|
||||
path: /get-started/09_image_best/
|
||||
- title: "Part 10: What next?"
|
||||
path: /get-started/11_what_next/
|
||||
- sectiontitle: Develop with Docker
|
||||
section:
|
||||
- path: /develop/
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
title: "Our Application"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: overview of our simple applicaiton for learning docker
|
||||
---
|
||||
|
||||
|
||||
For the rest of this tutorial, we will be working with a simple todo
|
||||
list manager that is running in Node.js. If you're not familiar with Node.js,
|
||||
don't worry! No real JavaScript experience is needed!
|
||||
|
||||
At this point, your development team is quite small and you're simply
|
||||
building an app to prove out your MVP (minimum viable product). You want
|
||||
to show how it works and what it's capable of doing without needing to
|
||||
think about how it will work for a large team, multiple developers, etc.
|
||||
|
||||
{: style="width:50%;" }
|
||||
|
||||
|
||||
## Getting our App
|
||||
|
||||
Before we can run the application, we need to get the application source code onto
|
||||
our machine. For real projects, you will typically clone the repo. But, for this tutorial,
|
||||
we have created a ZIP file containing the application.
|
||||
|
||||
1. [Download the App contents](https://github.com/docker/getting-started/tree/master/app). You can either pull the entire project or download it as a zip and extract the app folder out to get started with
|
||||
|
||||
1. Once extracted, use your favorite code editor to open the project. If you're in need of
|
||||
an editor, you can use [Visual Studio Code](https://code.visualstudio.com/). You should
|
||||
see the `package.json` and two subdirectories (`src` and `spec`).
|
||||
|
||||
{: style="width:650px;margin-top:20px;"}
|
||||
{: .text-center }
|
||||
|
||||
## Building the App's Container Image
|
||||
|
||||
In order to build the application, we need to use a `Dockerfile`. A
|
||||
Dockerfile is simply a text-based script of instructions that is used to
|
||||
create a container image. If you've created Dockerfiles before, you might
|
||||
see a few flaws in the Dockerfile below. But, don't worry! We'll go over them.
|
||||
|
||||
1. Create a file named `Dockerfile` in the same folder as the file `package.json` with the following contents.
|
||||
|
||||
```dockerfile
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN yarn install --production
|
||||
CMD ["node", "src/index.js"]
|
||||
```
|
||||
|
||||
Please check that the file `Dockerfile` has no file extension like `.txt`. Some editors may append this file extension automatically and this would result in an error in the next step.
|
||||
|
||||
1. If you haven't already done so, open a terminal and go to the `app` directory with the `Dockerfile`. Now build the container image using the `docker build` command.
|
||||
|
||||
```bash
|
||||
docker build -t getting-started .
|
||||
```
|
||||
|
||||
This command used the Dockerfile to build a new container image. You might
|
||||
have noticed that a lot of "layers" were downloaded. This is because we instructed
|
||||
the builder that we wanted to start from the `node:12-alpine` image. But, since we
|
||||
didn't have that on our machine, that image needed to be downloaded.
|
||||
|
||||
After the image was downloaded, we copied in our application and used `yarn` to
|
||||
install our application's dependencies. The `CMD` directive specifies the default
|
||||
command to run when starting a container from this image.
|
||||
|
||||
Finally, the `-t` flag tags our image. Think of this simply as a human-readable name
|
||||
for the final image. Since we named the image `getting-started`, we can refer to that
|
||||
image when we run a container.
|
||||
|
||||
The `.` at the end of the `docker build` command tells that Docker should look for the `Dockerfile` in the current directory.
|
||||
|
||||
## Starting an App Container
|
||||
|
||||
Now that we have an image, let's run the application! To do so, we will use the `docker run`
|
||||
command (remember that from earlier?).
|
||||
|
||||
1. Start your container using the `docker run` command and specify the name of the image we
|
||||
just created:
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 getting-started
|
||||
```
|
||||
|
||||
Remember the `-d` and `-p` flags? We're running the new container in "detached" mode (in the
|
||||
background) and creating a mapping between the host's port 3000 to the container's port 3000.
|
||||
Without the port mapping, we wouldn't be able to access the application.
|
||||
|
||||
1. After a few seconds, open your web browser to [http://localhost:3000](http://localhost:3000).
|
||||
You should see our app!
|
||||
|
||||
{: style="width:450px;margin-top:20px;"}
|
||||
{: .text-center }
|
||||
|
||||
1. Go ahead and add an item or two and see that it works as you expect. You can mark items as
|
||||
complete and remove items. Your frontend is successfully storing items in the backend!
|
||||
Pretty quick and easy, huh?
|
||||
|
||||
|
||||
At this point, you should have a running todo list manager with a few items, all built by you!
|
||||
Now, let's make a few changes and learn about managing our containers.
|
||||
|
||||
If you take a quick look at the Docker Dashboard, you should see your two containers running now
|
||||
(this tutorial and your freshly launched app container)!
|
||||
|
||||

|
||||
|
||||
|
||||
## Recap
|
||||
|
||||
In this short section, we learned the very basics about building a container image and created a
|
||||
Dockerfile to do so. Once we built an image, we started the container and saw the running app!
|
||||
|
||||
Next, we're going to make a modification to our app and learn how to update our running application
|
||||
with a new image. Along the way, we'll learn a few other useful commands.
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
title: "Updating our Application"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Making changes to our example learning application
|
||||
---
|
||||
|
||||
|
||||
As a small feature request, we've been asked by the product team to
|
||||
change the "empty text" when we don't have any todo list items. They
|
||||
would like to transition it to the following:
|
||||
|
||||
> You have no todo items yet! Add one above!
|
||||
|
||||
Pretty simple, right? Let's make the change.
|
||||
|
||||
## Updating our Source Code
|
||||
|
||||
1. In the `src/static/js/app.js` file, update line 56 to use the new empty text.
|
||||
|
||||
```diff
|
||||
- <p className="text-center">No items yet! Add one above!</p>
|
||||
+ <p className="text-center">You have no todo items yet! Add one above!</p>
|
||||
```
|
||||
|
||||
1. Let's build our updated version of the image, using the same command we used before.
|
||||
|
||||
```bash
|
||||
docker build -t getting-started .
|
||||
```
|
||||
|
||||
1. Let's start a new container using the updated code.
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 getting-started
|
||||
```
|
||||
|
||||
**Uh oh!** You probably saw an error like this (the IDs will be different):
|
||||
|
||||
```bash
|
||||
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell
|
||||
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.
|
||||
```
|
||||
|
||||
So, what happened? We aren't able to start the new container because our old container is still
|
||||
running. The reason this is a problem is because that container is using the host's port 3000 and
|
||||
only one process on the machine (containers included) can listen to a specific port. To fix this,
|
||||
we need to remove the old container.
|
||||
|
||||
|
||||
## Replacing our Old Container
|
||||
|
||||
To remove a container, it first needs to be stopped. Once it has stopped, it can be removed. We have two
|
||||
ways that we can remove the old container. Feel free to choose the path that you're most comfortable with.
|
||||
|
||||
|
||||
### Removing a container using the CLI
|
||||
|
||||
1. Get the ID of the container by using the `docker ps` command.
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
1. Use the `docker stop` command to stop the container.
|
||||
|
||||
```bash
|
||||
# Swap out <the-container-id> with the ID from docker ps
|
||||
docker stop <the-container-id>
|
||||
```
|
||||
|
||||
1. Once the container has stopped, you can remove it by using the `docker rm` command.
|
||||
|
||||
```bash
|
||||
docker rm <the-container-id>
|
||||
```
|
||||
|
||||
>**Note**
|
||||
>
|
||||
>You can stop and remove a container in a single command by adding the "force" flag
|
||||
>to the `docker rm` command. For example: `docker rm -f <the-container-id>`
|
||||
>
|
||||
|
||||
### Removing a container using the Docker Dashboard
|
||||
|
||||
If you open the Docker dashboard, you can remove a container with two clicks! It's certainly
|
||||
much easier than having to look up the container ID and remove it.
|
||||
|
||||
1. With the dashboard opened, hover over the app container and you'll see a collection of action
|
||||
buttons appear on the right.
|
||||
|
||||
1. Click on the trash can icon to delete the container.
|
||||
|
||||
1. Confirm the removal and you're done!
|
||||
|
||||

|
||||
|
||||
|
||||
### Starting our updated app container
|
||||
|
||||
1. Now, start your updated app.
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 getting-started
|
||||
```
|
||||
|
||||
1. Refresh your browser on [http://localhost:3000](http://localhost:3000) and you should see your updated help text!
|
||||
|
||||
{: style="width:55%" }
|
||||
{: .text-center }
|
||||
|
||||
|
||||
|
||||
## Recap
|
||||
|
||||
While we were able to build an update, there were two things you might have noticed:
|
||||
|
||||
- All of the existing items in our todo list are gone! That's not a very good app! We'll talk about that
|
||||
shortly.
|
||||
- There were _a lot_ of steps involved for such a small change. In an upcoming section, we'll talk about
|
||||
how to see code updates without needing to rebuild and start a new container every time we make a change.
|
||||
|
||||
Before talking about persistence, we'll quickly see how to share these images with others.
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
title: "Sharing Our Application"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop, docker hub, sharing
|
||||
description: Sharing our image we built for our example application so we can run it else where and other developers can use it
|
||||
---
|
||||
|
||||
|
||||
Now that we've built an image, let's share it! To share Docker images, you have to use a Docker
|
||||
registry. The default registry is Docker Hub and is where all of the images we've used have come from.
|
||||
|
||||
## Create a Repo
|
||||
|
||||
To push an image, we first need to create a repo on Docker Hub.
|
||||
|
||||
1. Go to [Docker Hub](https://hub.docker.com) and log in if you need to.
|
||||
|
||||
1. Click the **Create Repository** button.
|
||||
|
||||
1. For the repo name, use `getting-started`. Make sure the Visibility is `Public`.
|
||||
|
||||
1. Click the **Create** button!
|
||||
|
||||
If you look on the right-side of the page, you'll see a section named **Docker commands**. This gives
|
||||
an example command that you will need to run to push to this repo.
|
||||
|
||||
{: style=width:75% }
|
||||
{: .text-center }
|
||||
|
||||
## Pushing our Image
|
||||
|
||||
1. In the command line, try running the push command you see on Docker Hub. Note that your command
|
||||
will be using your namespace, not "docker".
|
||||
|
||||
```plaintext
|
||||
$ docker push docker/getting-started
|
||||
The push refers to repository [docker.io/docker/getting-started]
|
||||
An image does not exist locally with the tag: docker/getting-started
|
||||
```
|
||||
|
||||
Why did it fail? The push command was looking for an image named docker/getting-started, but
|
||||
didn't find one. If you run `docker image ls`, you won't see one either.
|
||||
|
||||
To fix this, we need to "tag" our existing image we've built to give it another name.
|
||||
|
||||
1. Login to the Docker Hub using the command `docker login -u YOUR-USER-NAME`.
|
||||
|
||||
1. Use the `docker tag` command to give the `getting-started` image a new name. Be sure to swap out
|
||||
`YOUR-USER-NAME` with your Docker ID.
|
||||
|
||||
```bash
|
||||
docker tag getting-started YOUR-USER-NAME/getting-started
|
||||
```
|
||||
|
||||
1. Now try your push command again. If you're copying the value from Docker Hub, you can drop the
|
||||
`tagname` portion, as we didn't add a tag to the image name. If you don't specify a tag, Docker
|
||||
will use a tag called `latest`.
|
||||
|
||||
```bash
|
||||
docker push YOUR-USER-NAME/getting-started
|
||||
```
|
||||
|
||||
## Running our Image on a New Instance
|
||||
|
||||
Now that our image has been built and pushed into a registry, let's try running our app on a brand
|
||||
new instance that has never seen this container image! To do this, we will use Play with Docker.
|
||||
|
||||
1. Open your browser to [Play with Docker](http://play-with-docker.com).
|
||||
|
||||
1. Log in with your Docker Hub account.
|
||||
|
||||
1. Once you're logged in, click on the "+ ADD NEW INSTANCE" link in the left side bar. (If you don't see it, make your browser a little wider.) After a few seconds, a terminal window will be opened in your browser.
|
||||
|
||||
{: style=width:75% }
|
||||
{: .text-center }
|
||||
|
||||
|
||||
1. In the terminal, start your freshly pushed app.
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 YOUR-USER-NAME/getting-started
|
||||
```
|
||||
|
||||
You should see the image get pulled down and eventually start up!
|
||||
|
||||
1. Click on the 3000 badge when it comes up and you should see the app with your modifications! Hooray!
|
||||
If the 3000 badge doesn't show up, you can click on the "Open Port" button and type in 3000.
|
||||
|
||||
## Recap
|
||||
|
||||
In this section, we learned how to share our images by pushing them to a registry. We then went to a
|
||||
brand new instance and were able to run the freshly pushed image. This is quite common in CI pipelines,
|
||||
where the pipeline will create the image and push it to a registry and then the production environment
|
||||
can use the latest version of the image.
|
||||
|
||||
Now that we have that figured out, let's circle back around to what we noticed at the end of the last
|
||||
section. As a reminder, we noticed that when we restarted the app, we lost all of our todo list items.
|
||||
That's obviously not a great user experience, so let's learn how we can persist the data across
|
||||
restarts!
|
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
title: "Persisting our DB"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Making our DB persistent in our application
|
||||
---
|
||||
|
||||
In case you didn't notice, our todo list is being wiped clean every single time
|
||||
we launch the container. Why is this? Let's dive into how the container is working.
|
||||
|
||||
## The Container's Filesystem
|
||||
|
||||
When a container runs, it uses the various layers from an image for its filesystem.
|
||||
Each container also gets its own "scratch space" to create/update/remove files. Any
|
||||
changes won't be seen in another container, _even if_ they are using the same image.
|
||||
|
||||
### Seeing this in Practice
|
||||
|
||||
To see this in action, we're going to start two containers and create a file in each.
|
||||
What you'll see is that the files created in one container aren't available in another.
|
||||
|
||||
1. Start a `ubuntu` container that will create a file named `/data.txt` with a random number
|
||||
between 1 and 10000.
|
||||
|
||||
```bash
|
||||
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
|
||||
```
|
||||
|
||||
In case you're curious about the command, we're starting a bash shell and invoking two
|
||||
commands (why we have the `&&`). The first portion picks a single random number and writes
|
||||
it to `/data.txt`. The second command is simply watching a file to keep the container running.
|
||||
|
||||
1. Validate we can see the output by `exec`'ing into the container. To do so, open the Dashboard and click the first action of the container that is running the `ubuntu` image.
|
||||
|
||||
{: style=width:75% }
|
||||
{: .text-center }
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
cat /data.txt
|
||||
```
|
||||
|
||||
If you prefer the command line you can use the `docker exec` command to do the same. You need to get the
|
||||
container's ID (use `docker ps` to get it) and get the content with the following command.
|
||||
|
||||
```bash
|
||||
docker exec <container-id> cat /data.txt
|
||||
```
|
||||
|
||||
You should see a random number!
|
||||
|
||||
1. Now, let's start another `ubuntu` container (the same image) and we'll see we don't have the same
|
||||
file.
|
||||
|
||||
```bash
|
||||
docker run -it ubuntu ls /
|
||||
```
|
||||
|
||||
And look! There's no `data.txt` file there! That's because it was written to the scratch space for
|
||||
only the first container.
|
||||
|
||||
1. Go ahead and remove the first container using the `docker rm -f` command.
|
||||
|
||||
## Container Volumes
|
||||
|
||||
With the previous experiment, we saw that each container starts from the image definition each time it starts.
|
||||
While containers can create, update, and delete files, those changes are lost when the container is removed
|
||||
and all changes are isolated to that container. With volumes, we can change all of this.
|
||||
|
||||
[Volumes](/storage/volumes/) provide the ability to connect specific filesystem paths of
|
||||
the container back to the host machine. If a directory in the container is mounted, changes in that
|
||||
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**.
|
||||
|
||||
## Persisting our Todo Data
|
||||
|
||||
By default, the todo app stores its data in a [SQLite Database](https://www.sqlite.org/index.html) at
|
||||
`/etc/todos/todo.db`. 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.
|
||||
|
||||
With the database being a single file, if we can persist that file on the host and make it available to the
|
||||
next container, it should be able to pick up where the last one left off. By creating a volume and attaching
|
||||
(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.
|
||||
|
||||
1. Create a volume by using the `docker volume create` command.
|
||||
|
||||
```bash
|
||||
docker volume create todo-db
|
||||
```
|
||||
|
||||
1. Stop 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.
|
||||
|
||||
1. 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.
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
|
||||
```
|
||||
|
||||
1. Once the container starts up, open the app and add a few items to your todo list.
|
||||
|
||||
{: style="width: 55%; " }
|
||||
{: .text-center }
|
||||
|
||||
1. Remove the container for the todo app. Use the Dashboard or `docker ps` to get the ID and then `docker rm -f <id>` to remove it.
|
||||
|
||||
1. Start a new container using the same command from above.
|
||||
|
||||
1. Open the app. You should see your items still in your list!
|
||||
|
||||
1. Go ahead and remove the container when you're done checking out your list.
|
||||
|
||||
Hooray! You've now learned how to persist data!
|
||||
|
||||
>**Pro-tip**
|
||||
>
|
||||
>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.
|
||||
>
|
||||
|
||||
## Diving into our 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,
|
||||
you can use the `docker volume inspect` command.
|
||||
|
||||
```bash
|
||||
docker volume inspect todo-db
|
||||
[
|
||||
{
|
||||
"CreatedAt": "2019-09-26T02:18:36Z",
|
||||
"Driver": "local",
|
||||
"Labels": {},
|
||||
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
|
||||
"Name": "todo-db",
|
||||
"Options": {},
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Recap
|
||||
|
||||
At this point, we have a functioning application that can survive restarts! We can show it off to our investors and
|
||||
hope they can catch our vision!
|
||||
|
||||
However, we 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 we hinted at earlier), there is a better way! Let's take a look at that now!
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
title: "Using bind mounts"
|
||||
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.
|
||||
|
||||
With **bind mounts**, we control the exact mountpoint on the host. We can use this to persist data, but is 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.
|
||||
|
||||
For Node-based applications, [nodemon](https://npmjs.com/package/nodemon) is a great tool to watch for file
|
||||
changes and then restart the application. 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 uses cases ([SFTP](https://github.com/vieux/docker-volume-sshfs), [Ceph](https://ceph.com/geen-categorie/getting-started-with-the-docker-rbd-volume-plugin/), [NetApp](https://netappdvp.readthedocs.io/en/stable/), [S3](https://github.com/elementar/docker-s3-volume), and more).
|
||||
|
||||
| | 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 |
|
||||
|
||||
|
||||
## Starting a Dev-Mode Container
|
||||
|
||||
To run our container to support a development workflow, we will do the following:
|
||||
|
||||
- Mount our source code into the container
|
||||
- Install all dependencies, including the "dev" 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. Run the following command. We'll explain what's going on afterwards:
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
node:12-alpine \
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
If you are using PowerShell then use this command.
|
||||
|
||||
```powershell
|
||||
docker run -dp 3000:3000 `
|
||||
-w /app -v "$(pwd):/app" `
|
||||
node:12-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 in the container into the `/app` directory
|
||||
- `node:12-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`.
|
||||
|
||||
1. You can watch the logs using `docker logs -f <container-id>`. You'll know you're ready to go when you see this...
|
||||
|
||||
```bash
|
||||
docker logs -f <container-id>
|
||||
$ nodemon src/index.js
|
||||
[nodemon] 1.19.2
|
||||
[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`.
|
||||
|
||||
1. 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.
|
||||
|
||||
```diff
|
||||
- {submitting ? 'Adding...' : 'Add Item'}
|
||||
+ {submitting ? 'Adding...' : 'Add'}
|
||||
```
|
||||
|
||||
1. 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.
|
||||
|
||||
{: style="width:75%;"}
|
||||
{: .text-center }
|
||||
|
||||
1. 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 `docker build -t getting-started .`.
|
||||
|
||||
|
||||
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).
|
||||
|
||||
## Recap
|
||||
|
||||
At this point, we can persist our database and respond rapidly to the needs and demands of our investors and founders. Hooray!
|
||||
But, guess what? We received great news!
|
||||
|
||||
**Your project has been selected for future development!**
|
||||
|
||||
In order to prepare for production, we need to migrate our database from working in SQLite to something that can scale a
|
||||
little better. For simplicity, we'll keep with a relational database and switch our application to use MySQL. But, how
|
||||
should we run MySQL? How do we allow the containers to talk to each other? We'll talk about that next!
|
|
@ -0,0 +1,266 @@
|
|||
---
|
||||
title: "Multi container apps"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Using more than one container in our application
|
||||
---
|
||||
Up to this point, we have been working with single container apps. But, we now want to add MySQL to the
|
||||
application stack. The following question often arises - "Where will MySQL run? Install it in the same
|
||||
container or run it separately?" In general, **each container should do one thing and do it well.** A few
|
||||
reasons:
|
||||
|
||||
- There's a good chance you'd have to scale APIs and front-ends differently than databases
|
||||
- Separate containers let you version and update versions in isolation
|
||||
- While you may use a container for the database locally, you may want to use a managed service
|
||||
for the database in production. You don't want to ship your database engine with your app then.
|
||||
- Running multiple processes will require a process manager (the container only starts one process),
|
||||
which adds complexity to container startup/shutdown
|
||||
|
||||
And there are more reasons. So, we will update our application to work like this:
|
||||
|
||||

|
||||
{: .text-center }
|
||||
|
||||
|
||||
## Container Networking
|
||||
|
||||
Remember that containers, by default, run in isolation and don't know anything about other processes
|
||||
or containers on the same machine. So, how do we allow one container to talk to another? The answer is
|
||||
**networking**. Now, you don't have to be a network engineer (hooray!). Simply remember this rule...
|
||||
|
||||
> If two containers are on the same network, they can talk to each other. If they aren't, they can't.
|
||||
|
||||
|
||||
## Starting MySQL
|
||||
|
||||
There are two ways to put a container on a network: 1) Assign it at start or 2) connect an existing container.
|
||||
For now, we will create the network first and attach the MySQL container at startup.
|
||||
|
||||
1. Create the network.
|
||||
|
||||
```bash
|
||||
docker network create todo-app
|
||||
```
|
||||
|
||||
1. Start a MySQL container and attach it to the network. We're also going to define a few environment variables that the
|
||||
database will use to initialize the database (see the "Environment Variables" section in the [MySQL Docker Hub listing](https://hub.docker.com/_/mysql/)).
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--network todo-app --network-alias mysql \
|
||||
-v todo-mysql-data:/var/lib/mysql \
|
||||
-e MYSQL_ROOT_PASSWORD=secret \
|
||||
-e MYSQL_DATABASE=todos \
|
||||
mysql:5.7
|
||||
```
|
||||
|
||||
If you are using PowerShell then use this command.
|
||||
|
||||
```powershell
|
||||
docker run -d `
|
||||
--network todo-app --network-alias mysql `
|
||||
-v todo-mysql-data:/var/lib/mysql `
|
||||
-e MYSQL_ROOT_PASSWORD=secret `
|
||||
-e MYSQL_DATABASE=todos `
|
||||
mysql:5.7
|
||||
```
|
||||
|
||||
You'll also see we specified the `--network-alias` flag. We'll come back to that in just a moment.
|
||||
|
||||
>**Pro-tip**
|
||||
>
|
||||
>You'll notice we're using a volume named `todo-mysql-data` here and mounting it at `/var/lib/mysql`, which is
|
||||
>where MySQL stores its data. However, we never ran a `docker volume create` command. Docker recognizes we want
|
||||
>to use a named volume and creates one automatically for us.
|
||||
>
|
||||
|
||||
1. To confirm we have the database up and running, connect to the database and verify it connects.
|
||||
|
||||
```bash
|
||||
docker exec -it <mysql-container-id> mysql -p
|
||||
```
|
||||
|
||||
When the password prompt comes up, type in **secret**. In the MySQL shell, list the databases and verify
|
||||
you see the `todos` database.
|
||||
|
||||
```cli
|
||||
mysql> SHOW DATABASES;
|
||||
```
|
||||
|
||||
You should see output that looks like this:
|
||||
|
||||
```plaintext
|
||||
+--------------------+
|
||||
| Database |
|
||||
+--------------------+
|
||||
| information_schema |
|
||||
| mysql |
|
||||
| performance_schema |
|
||||
| sys |
|
||||
| todos |
|
||||
+--------------------+
|
||||
5 rows in set (0.00 sec)
|
||||
```
|
||||
|
||||
Hooray! We have our `todos` database and it's ready for us to use!
|
||||
|
||||
|
||||
## Connecting to MySQL
|
||||
|
||||
Now that we know MySQL is up and running, let's use it! But, the question is... how? If we run
|
||||
another container on the same network, how do we find the container (remember each container has its own IP
|
||||
address)?
|
||||
|
||||
To figure it out, we're going to make use of the [nicolaka/netshoot](https://github.com/nicolaka/netshoot) container,
|
||||
which ships with a _lot_ of tools that are useful for troubleshooting or debugging networking issues.
|
||||
|
||||
1. Start a new container using the nicolaka/netshoot image. Make sure to connect it to the same network.
|
||||
|
||||
```bash
|
||||
docker run -it --network todo-app nicolaka/netshoot
|
||||
```
|
||||
|
||||
1. Inside the container, we're going to use the `dig` command, which is a useful DNS tool. We're going to look up
|
||||
the IP address for the hostname `mysql`.
|
||||
|
||||
```bash
|
||||
dig mysql
|
||||
```
|
||||
|
||||
And you'll get an output like this...
|
||||
|
||||
```text
|
||||
; <<>> DiG 9.14.1 <<>> mysql
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
|
||||
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;mysql. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
mysql. 600 IN A 172.23.0.2
|
||||
|
||||
;; Query time: 0 msec
|
||||
;; SERVER: 127.0.0.11#53(127.0.0.11)
|
||||
;; WHEN: Tue Oct 01 23:47:24 UTC 2019
|
||||
;; MSG SIZE rcvd: 44
|
||||
```
|
||||
|
||||
In the "ANSWER SECTION", you will see an `A` record for `mysql` that resolves to `172.23.0.2`
|
||||
(your IP address will most likely have a different value). While `mysql` isn't normally a valid hostname,
|
||||
Docker was able to resolve it to the IP address of the container that had that network alias (remember the
|
||||
`--network-alias` flag we used earlier?).
|
||||
|
||||
What this means is... our app only simply needs to connect to a host named `mysql` and it'll talk to the
|
||||
database! It doesn't get much simpler than that!
|
||||
|
||||
|
||||
## Running our App with MySQL
|
||||
|
||||
The todo app supports the setting of a few environment variables to specify MySQL connection settings. They are:
|
||||
|
||||
- `MYSQL_HOST` - the hostname for the running MySQL server
|
||||
- `MYSQL_USER` - the username to use for the connection
|
||||
- `MYSQL_PASSWORD` - the password to use for the connection
|
||||
- `MYSQL_DB` - the database to use once connected
|
||||
|
||||
>**warning**
|
||||
>Setting Connection Settings via Env Vars
|
||||
>While using env vars to set connection settings is generally ok for development, it is **HIGHLY DISCOURAGED**
|
||||
>when running applications in production. Diogo Monica, the former lead of security at Docker,
|
||||
>[wrote a fantastic blog post](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/)
|
||||
>explaining why.
|
||||
>
|
||||
>A more secure mechanism is to use the secret support provided by your container orchestration framework. In most cases,
|
||||
>these secrets are mounted as files in the running container. You'll see many apps (including the MySQL image and the todo app)
|
||||
>also support env vars with a `_FILE` suffix to point to a file containing the variable.
|
||||
>
|
||||
>As an example, setting the `MYSQL_PASSWORD_FILE` var will cause the app to use the contents of the referenced file
|
||||
>as the connection password. Docker doesn't do anything to support these env vars. Your app will need to know to look for
|
||||
>the variable and get the file contents.
|
||||
|
||||
|
||||
With all of that explained, let's start our dev-ready container!
|
||||
|
||||
1. We'll specify each of the environment variables above, as well as connect the container to our app network.
|
||||
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
--network todo-app \
|
||||
-e MYSQL_HOST=mysql \
|
||||
-e MYSQL_USER=root \
|
||||
-e MYSQL_PASSWORD=secret \
|
||||
-e MYSQL_DB=todos \
|
||||
node:12-alpine \
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
If you are using PowerShell then use this command.
|
||||
|
||||
```powershell
|
||||
docker run -dp 3000:3000 `
|
||||
-w /app -v "$(pwd):/app" `
|
||||
--network todo-app `
|
||||
-e MYSQL_HOST=mysql `
|
||||
-e MYSQL_USER=root `
|
||||
-e MYSQL_PASSWORD=secret `
|
||||
-e MYSQL_DB=todos `
|
||||
node:12-alpine `
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
1. If we look at the logs for the container (`docker logs <container-id>`), we should see a message indicating it's
|
||||
using the mysql database.
|
||||
|
||||
```
|
||||
# Previous log messages omitted
|
||||
$ nodemon src/index.js
|
||||
[nodemon] 1.19.2
|
||||
[nodemon] to restart at any time, enter `rs`
|
||||
[nodemon] watching dir(s): *.*
|
||||
[nodemon] starting `node src/index.js`
|
||||
Connected to mysql db at host mysql
|
||||
Listening on port 3000
|
||||
```
|
||||
|
||||
1. Open the app in your browser and add a few items to your todo list.
|
||||
|
||||
1. Connect to the mysql database and prove that the items are being written to the database. Remember, the password
|
||||
is **secret**.
|
||||
|
||||
```bash
|
||||
docker exec -it <mysql-container-id> mysql -p todos
|
||||
```
|
||||
|
||||
And in the mysql shell, run the following:
|
||||
|
||||
```plaintext
|
||||
mysql> select * from todo_items;
|
||||
+--------------------------------------+--------------------+-----------+
|
||||
| id | name | completed |
|
||||
+--------------------------------------+--------------------+-----------+
|
||||
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 |
|
||||
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 |
|
||||
+--------------------------------------+--------------------+-----------+
|
||||
```
|
||||
|
||||
Obviously, your table will look different because it has your items. But, you should see them stored there!
|
||||
|
||||
If you take a quick look at the Docker Dashboard, you'll see that we have two app containers running. But, there's
|
||||
no real indication that they are grouped together in a single app. We'll see how to make that better shortly!
|
||||
|
||||

|
||||
|
||||
## Recap
|
||||
|
||||
At this point, we have an application that now stores its data in an external database running in a separate
|
||||
container. We learned a little bit about container networking and saw how service discovery can be performed
|
||||
using DNS.
|
||||
|
||||
But, there's a good chance you are starting to feel a little overwhelmed with everything you need to do to start up
|
||||
this application. We have to create a network, start containers, specify all of the environment variables, expose
|
||||
ports, and more! That's a lot to remember and it's certainly making things harder to pass along to someone else.
|
||||
|
||||
In the next section, we'll talk about Docker Compose. With Docker Compose, we can share our application stacks in a
|
||||
much easier way and let others spin them up with a single (and simple) command!
|
|
@ -0,0 +1,366 @@
|
|||
---
|
||||
title: "Using Docker Compose"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Making our lives easier with Compose for our application
|
||||
---
|
||||
|
||||
[Docker Compose](/compose/) is a tool that was developed to help define and
|
||||
share multi-container applications. With Compose, we can create a YAML file to define the services
|
||||
and with a single command, can spin everything up or tear it all down.
|
||||
|
||||
The _big_ advantage of using Compose is you can define your application stack in a file, keep it at the root of
|
||||
your project repo (it's now version controlled), and easily enable someone else to contribute to your project.
|
||||
Someone would only need to clone your repo and start the compose app. In fact, you might see quite a few projects
|
||||
on GitHub/GitLab doing exactly this now.
|
||||
|
||||
So, how do we get started?
|
||||
|
||||
## Installing Docker Compose
|
||||
|
||||
If you installed Docker Desktop/Toolbox for either Windows or Mac, you already have Docker Compose!
|
||||
Play-with-Docker instances already have Docker Compose installed as well. If you are on
|
||||
a Linux machine, you will need to install Docker Compose using
|
||||
[the instructions here](/compose/install/).
|
||||
|
||||
After installation, you should be able to run the following and see version information.
|
||||
|
||||
```bash
|
||||
docker-compose version
|
||||
```
|
||||
|
||||
|
||||
## Creating our Compose File
|
||||
|
||||
1. At the root of the app project, create a file named `docker-compose.yml`.
|
||||
|
||||
1. In the compose file, we'll start off by defining the schema version. In most cases, it's best to use
|
||||
the latest supported version. You can look at the [Compose file reference](https://docs.docker.com/compose/compose-file/)
|
||||
for the current schema versions and the compatibility matrix.
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
```
|
||||
|
||||
1. Next, we'll define the list of services (or containers) we want to run as part of our application.
|
||||
|
||||
```yaml hl_lines="3"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
```
|
||||
|
||||
And now, we'll start migrating a service at a time into the compose file.
|
||||
|
||||
|
||||
## Defining the App Service
|
||||
|
||||
To remember, this was the command we were using to define our app container.
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
--network todo-app \
|
||||
-e MYSQL_HOST=mysql \
|
||||
-e MYSQL_USER=root \
|
||||
-e MYSQL_PASSWORD=secret \
|
||||
-e MYSQL_DB=todos \
|
||||
node:12-alpine \
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
If you are using PowerShell then use this command.
|
||||
|
||||
```powershell
|
||||
docker run -dp 3000:3000 `
|
||||
-w /app -v "$(pwd):/app" `
|
||||
--network todo-app `
|
||||
-e MYSQL_HOST=mysql `
|
||||
-e MYSQL_USER=root `
|
||||
-e MYSQL_PASSWORD=secret `
|
||||
-e MYSQL_DB=todos `
|
||||
node:12-alpine `
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
1. First, let's define the service entry and the image for the container. We can pick any name for the service.
|
||||
The name will automatically become a network alias, which will be useful when defining our MySQL service.
|
||||
|
||||
```yaml hl_lines="4 5"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
```
|
||||
|
||||
1. Typically, you will see the command close to the `image` definition, although there is no requirement on ordering.
|
||||
So, let's go ahead and move that into our file.
|
||||
|
||||
```yaml hl_lines="6"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
|
||||
1. Let's migrate the `-p 3000:3000` part of the command by defining the `ports` for the service. We will use the
|
||||
[short syntax](https://docs.docker.com/compose/compose-file/#short-syntax-1) here, but there is also a more verbose
|
||||
[long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-1) available as well.
|
||||
|
||||
```yaml hl_lines="7 8"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
```
|
||||
|
||||
1. Next, we'll migrate both the working directory (`-w /app`) and the volume mapping (`-v "$(pwd):/app"`) by using
|
||||
the `working_dir` and `volumes` definitions. Volumes also has a [short](https://docs.docker.com/compose/compose-file/#short-syntax-3) and [long](https://docs.docker.com/compose/compose-file/#long-syntax-3) syntax.
|
||||
|
||||
One advantage of Docker Compose volume definitions is we can use relative paths from the current directory.
|
||||
|
||||
```yaml hl_lines="9 10 11"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./:/app
|
||||
```
|
||||
|
||||
1. Finally, we need to migrate the environment variable definitions using the `environment` key.
|
||||
|
||||
```yaml hl_lines="12 13 14 15 16"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./:/app
|
||||
environment:
|
||||
MYSQL_HOST: mysql
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DB: todos
|
||||
```
|
||||
|
||||
|
||||
### Defining the MySQL Service
|
||||
|
||||
Now, it's time to define the MySQL service. The command that we used for that container was the following:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--network todo-app --network-alias mysql \
|
||||
-v todo-mysql-data:/var/lib/mysql \
|
||||
-e MYSQL_ROOT_PASSWORD=secret \
|
||||
-e MYSQL_DATABASE=todos \
|
||||
mysql:5.7
|
||||
```
|
||||
|
||||
If you are using PowerShell then use this command.
|
||||
|
||||
```powershell
|
||||
docker run -d `
|
||||
--network todo-app --network-alias mysql `
|
||||
-v todo-mysql-data:/var/lib/mysql `
|
||||
-e MYSQL_ROOT_PASSWORD=secret `
|
||||
-e MYSQL_DATABASE=todos `
|
||||
mysql:5.7
|
||||
```
|
||||
|
||||
1. We will first define the new service and name it `mysql` so it automatically gets the network alias. We'll
|
||||
go ahead and specify the image to use as well.
|
||||
|
||||
```yaml hl_lines="6 7"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
```
|
||||
|
||||
1. Next, we'll define the volume mapping. When we ran the container with `docker run`, the named volume was created
|
||||
automatically. However, that doesn't happen when running with Compose. We need to define the volume in the top-level
|
||||
`volumes:` section and then specify the mountpoint in the service config. By simply providing only the volume name,
|
||||
the default options are used. There are [many more options available](https://docs.docker.com/compose/compose-file/#volume-configuration-reference) though.
|
||||
|
||||
```yaml hl_lines="8 9 10 11 12"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
|
||||
volumes:
|
||||
todo-mysql-data:
|
||||
```
|
||||
|
||||
1. Finally, we only need to specify the environment variables.
|
||||
|
||||
```yaml hl_lines="10 11 12"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: secret
|
||||
MYSQL_DATABASE: todos
|
||||
|
||||
volumes:
|
||||
todo-mysql-data:
|
||||
```
|
||||
|
||||
At this point, our complete `docker-compose.yml` should look like this:
|
||||
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./:/app
|
||||
environment:
|
||||
MYSQL_HOST: mysql
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DB: todos
|
||||
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: secret
|
||||
MYSQL_DATABASE: todos
|
||||
|
||||
volumes:
|
||||
todo-mysql-data:
|
||||
```
|
||||
|
||||
|
||||
## Running our Application Stack
|
||||
|
||||
Now that we have our `docker-compose.yml` file, we can start it up!
|
||||
|
||||
1. Make sure no other copies of the app/db are running first (`docker ps` and `docker rm -f <ids>`).
|
||||
|
||||
1. Start up the application stack using the `docker-compose up` command. We'll add the `-d` flag to run everything in the
|
||||
background.
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
When we run this, we should see output like this:
|
||||
|
||||
```plaintext
|
||||
Creating network "app_default" with the default driver
|
||||
Creating volume "app_todo-mysql-data" with default driver
|
||||
Creating app_app_1 ... done
|
||||
Creating app_mysql_1 ... done
|
||||
```
|
||||
|
||||
You'll notice that the volume was created as well as a network! By default, Docker Compose automatically creates a
|
||||
network specifically for the application stack (which is why we didn't define one in the compose file).
|
||||
|
||||
1. Let's look at the logs using the `docker-compose logs -f` command. You'll see the logs from each of the services interleaved
|
||||
into a single stream. This is incredibly useful when you want to watch for timing-related issues. The `-f` flag "follows" the
|
||||
log, so will give you live output as it's generated.
|
||||
|
||||
If you don't already, you'll see output that looks like this...
|
||||
|
||||
```plaintext
|
||||
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
|
||||
mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
|
||||
app_1 | Connected to mysql db at host mysql
|
||||
app_1 | Listening on port 3000
|
||||
```
|
||||
|
||||
The service name is displayed at the beginning of the line (often colored) to help distinguish messages. If you want to
|
||||
view the logs for a specific service, you can add the service name to the end of the logs command (for example,
|
||||
`docker-compose logs -f app`).
|
||||
|
||||
>**Pro tip** Waiting for the DB before starting the app
|
||||
>
|
||||
>When the app is starting up, it actually sits and waits for MySQL to be up and ready before trying to connect to it.
|
||||
>Docker doesn't have any built-in support to wait for another container to be fully up, running, and ready
|
||||
>before starting another container. For Node-based projects, you can use the
|
||||
>[wait-port](https://github.com/dwmkerr/wait-port) dependency. Similar projects exist for other languages/frameworks.
|
||||
|
||||
1. At this point, you should be able to open your app and see it running. And hey! We're down to a single command!
|
||||
|
||||
## Seeing our App Stack in Docker Dashboard
|
||||
|
||||
If we look at the Docker Dashboard, we'll see that there is a group named **app**. This is the "project name" from Docker
|
||||
Compose and used to group the containers together. By default, the project name is simply the name of the directory that the
|
||||
`docker-compose.yml` was located in.
|
||||
|
||||

|
||||
|
||||
If you twirl down the app, you will see the two containers we defined in the compose file. The names are also a little
|
||||
more descriptive, as they follow the pattern of `<project-name>_<service-name>_<replica-number>`. So, it's very easy to
|
||||
quickly see what container is our app and which container is the mysql database.
|
||||
|
||||

|
||||
|
||||
|
||||
## Tearing it All Down
|
||||
|
||||
When you're ready to tear it all down, simply run `docker-compose down` or hit the trash can on the Docker Dashboard
|
||||
for the entire app. The containers will stop and the network will be removed.
|
||||
|
||||
>**Warning**
|
||||
>Removing Volumes
|
||||
>By default, named volumes in your compose file are NOT removed when running `docker-compose down`. If you want to
|
||||
>remove the volumes, you will need to add the `--volumes` flag.
|
||||
>
|
||||
>The Docker Dashboard does _not_ remove volumes when you delete the app stack.
|
||||
|
||||
Once torn down, you can switch to another project, run `docker-compose up` and be ready to contribute to that project! It really
|
||||
doesn't get much simpler than that!
|
||||
|
||||
|
||||
## Recap
|
||||
|
||||
In this section, we learned about Docker Compose and how it helps us dramatically simplify the defining and
|
||||
sharing of multi-service applications. We created a Compose file by translating the commands we were
|
||||
using into the appropriate compose format.
|
||||
|
||||
At this point, we're starting to wrap up the tutorial. However, there are a few best practices about
|
||||
image building we want to cover, as there is a big issue with the Dockerfile we've been using. So,
|
||||
let's take a look!
|
|
@ -0,0 +1,275 @@
|
|||
---
|
||||
title: "Image building tips"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Tips for building the images for our application
|
||||
---
|
||||
## Security Scanning
|
||||
|
||||
When you have built an image, it is good practice to scan it for security vulnerabilities using the `docker scan` command.
|
||||
Docker has partnered with [Snyk](http://snyk.io) to provide the vulnerability scanning service.
|
||||
|
||||
For example, to scan the `getting-started` image you created earlier in the tutorial, you can just type
|
||||
|
||||
```bash
|
||||
docker scan getting-started
|
||||
```
|
||||
|
||||
The scan uses a constantly updated database of vulnerabilities, so the output you see will vary as new
|
||||
vulnerabilities are discovered, but it might look something like this:
|
||||
|
||||
```plaintext
|
||||
✗ Low severity vulnerability found in freetype/freetype
|
||||
Description: CVE-2020-15999
|
||||
Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
|
||||
Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
|
||||
From: freetype/freetype@2.10.0-r0
|
||||
From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
|
||||
Fixed in: 2.10.0-r1
|
||||
|
||||
✗ Medium severity vulnerability found in libxml2/libxml2
|
||||
Description: Out-of-bounds Read
|
||||
Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
|
||||
Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
|
||||
From: libxml2/libxml2@2.9.9-r3
|
||||
From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
|
||||
From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
|
||||
Fixed in: 2.9.9-r4
|
||||
```
|
||||
|
||||
The output lists the type of vulnerability, a URL to learn more, and importantly which version of the relevant library
|
||||
fixes the vulnerability.
|
||||
|
||||
There are several other options, which you can read about in the [docker scan documentation](https://docs.docker.com/engine/scan/).
|
||||
|
||||
As well as scanning your newly built image on the command line, you can also [configure Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/)
|
||||
to scan all newly pushed images automatically, and you can then see the results in both Docker Hub and Docker Desktop.
|
||||
|
||||
{: style=width:75% }
|
||||
{: .text-center }
|
||||
|
||||
## Image Layering
|
||||
|
||||
Did you know that you can look at what makes up an image? Using the `docker image history`
|
||||
command, you can see the command that was used to create each layer within an image.
|
||||
|
||||
1. Use the `docker image history` command to see the layers in the `getting-started` image you
|
||||
created earlier in the tutorial.
|
||||
|
||||
```bash
|
||||
docker image history getting-started
|
||||
```
|
||||
|
||||
You should get output that looks something like this (dates/IDs may be different).
|
||||
|
||||
```plaintext
|
||||
IMAGE CREATED CREATED BY SIZE COMMENT
|
||||
a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B
|
||||
f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB
|
||||
a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB
|
||||
9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B
|
||||
b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B
|
||||
<missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
|
||||
<missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B
|
||||
<missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB
|
||||
<missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B
|
||||
<missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB
|
||||
<missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B
|
||||
<missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
|
||||
<missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB
|
||||
```
|
||||
|
||||
Each of the lines represents a layer in the image. The display here shows the base at the bottom with
|
||||
the newest layer at the top. Using this, you can also quickly see the size of each layer, helping
|
||||
diagnose large images.
|
||||
|
||||
1. You'll notice that several of the lines are truncated. If you add the `--no-trunc` flag, you'll get the
|
||||
full output (yes... funny how you use a truncated flag to get untruncated output, huh?)
|
||||
|
||||
```bash
|
||||
docker image history --no-trunc getting-started
|
||||
```
|
||||
|
||||
|
||||
## Layer Caching
|
||||
|
||||
Now that you've seen the layering in action, there's an important lesson to learn to help decrease build
|
||||
times for your container images.
|
||||
|
||||
> Once a layer changes, all downstream layers have to be recreated as well
|
||||
|
||||
Let's look at the Dockerfile we were using one more time...
|
||||
|
||||
```dockerfile
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN yarn install --production
|
||||
CMD ["node", "src/index.js"]
|
||||
```
|
||||
|
||||
Going back to the image history output, we see that each command in the Dockerfile becomes a new layer in the image.
|
||||
You might remember that when we made a change to the image, the yarn dependencies had to be reinstalled. Is there a
|
||||
way to fix this? It doesn't make much sense to ship around the same dependencies every time we build, right?
|
||||
|
||||
To fix this, we need to restructure our Dockerfile to help support the caching of the dependencies. For Node-based
|
||||
applications, those dependencies are defined in the `package.json` file. So, what if we copied only that file in first,
|
||||
install the dependencies, and _then_ copy in everything else? Then, we only recreate the yarn dependencies if there was
|
||||
a change to the `package.json`. Make sense?
|
||||
|
||||
1. Update the Dockerfile to copy in the `package.json` first, install dependencies, and then copy everything else in.
|
||||
|
||||
```dockerfile
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --production
|
||||
COPY . .
|
||||
CMD ["node", "src/index.js"]
|
||||
```
|
||||
|
||||
1. Create a file named `.dockerignore` in the same folder as the Dockerfile with the following contents.
|
||||
|
||||
```ignore
|
||||
node_modules
|
||||
```
|
||||
|
||||
`.dockerignore` files are an easy way to selectively copy only image relevant files.
|
||||
You can read more about this
|
||||
[here](https://docs.docker.com/engine/reference/builder/#dockerignore-file).
|
||||
In this case, the `node_modules` folder should be omitted in the second `COPY` step because otherwise,
|
||||
it would possibly overwrite files which were created by the command in the `RUN` step.
|
||||
For further details on why this is recommended for Node.js applications and other best practices,
|
||||
have a look at their guide on
|
||||
[Dockerizing a Node.js web app](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/).
|
||||
|
||||
1. Build a new image using `docker build`.
|
||||
|
||||
```bash
|
||||
docker build -t getting-started .
|
||||
```
|
||||
|
||||
You should see output like this...
|
||||
|
||||
```plaintext
|
||||
Sending build context to Docker daemon 219.1kB
|
||||
Step 1/6 : FROM node:12-alpine
|
||||
---> b0dc3a5e5e9e
|
||||
Step 2/6 : WORKDIR /app
|
||||
---> Using cache
|
||||
---> 9577ae713121
|
||||
Step 3/6 : COPY package.json yarn.lock ./
|
||||
---> bd5306f49fc8
|
||||
Step 4/6 : RUN yarn install --production
|
||||
---> Running in d53a06c9e4c2
|
||||
yarn install v1.17.3
|
||||
[1/4] Resolving packages...
|
||||
[2/4] Fetching packages...
|
||||
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
|
||||
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
|
||||
[3/4] Linking dependencies...
|
||||
[4/4] Building fresh packages...
|
||||
Done in 10.89s.
|
||||
Removing intermediate container d53a06c9e4c2
|
||||
---> 4e68fbc2d704
|
||||
Step 5/6 : COPY . .
|
||||
---> a239a11f68d8
|
||||
Step 6/6 : CMD ["node", "src/index.js"]
|
||||
---> Running in 49999f68df8f
|
||||
Removing intermediate container 49999f68df8f
|
||||
---> e709c03bc597
|
||||
Successfully built e709c03bc597
|
||||
Successfully tagged getting-started:latest
|
||||
```
|
||||
|
||||
You'll see that all layers were rebuilt. Perfectly fine since we changed the Dockerfile quite a bit.
|
||||
|
||||
1. Now, make a change to the `src/static/index.html` file (like change the `<title>` to say "The Awesome Todo App").
|
||||
|
||||
1. Build the Docker image now using `docker build -t getting-started .` again. This time, your output should look a little different.
|
||||
|
||||
```plaintext
|
||||
Sending build context to Docker daemon 219.1kB
|
||||
Step 1/6 : FROM node:12-alpine
|
||||
---> b0dc3a5e5e9e
|
||||
Step 2/6 : WORKDIR /app
|
||||
---> Using cache
|
||||
---> 9577ae713121
|
||||
Step 3/6 : COPY package.json yarn.lock ./
|
||||
---> Using cache
|
||||
---> bd5306f49fc8
|
||||
Step 4/6 : RUN yarn install --production
|
||||
---> Using cache
|
||||
---> 4e68fbc2d704
|
||||
Step 5/6 : COPY . .
|
||||
---> cccde25a3d9a
|
||||
Step 6/6 : CMD ["node", "src/index.js"]
|
||||
---> Running in 2be75662c150
|
||||
Removing intermediate container 2be75662c150
|
||||
---> 458e5c6f080c
|
||||
Successfully built 458e5c6f080c
|
||||
Successfully tagged getting-started:latest
|
||||
```
|
||||
|
||||
First off, you should notice that the build was MUCH faster! And, you'll see that steps 1-4 all have
|
||||
`Using cache`. So, hooray! We're using the build cache. Pushing and pulling this image and updates to it
|
||||
will be much faster as well. Hooray!
|
||||
|
||||
|
||||
## Multi-Stage Builds
|
||||
|
||||
While we're not going to dive into it too much in this tutorial, multi-stage builds are an incredibly powerful
|
||||
tool to help use multiple stages to create an image. There are several advantages for them:
|
||||
|
||||
- Separate build-time dependencies from runtime dependencies
|
||||
- Reduce overall image size by shipping _only_ what your app needs to run
|
||||
|
||||
### Maven/Tomcat Example
|
||||
|
||||
When building Java-based applications, a JDK is needed to compile the source code to Java bytecode. However,
|
||||
that JDK isn't needed in production. Also, you might be using tools like Maven or Gradle to help build the app.
|
||||
Those also aren't needed in our final image. Multi-stage builds help.
|
||||
|
||||
```dockerfile
|
||||
FROM maven AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN mvn package
|
||||
|
||||
FROM tomcat
|
||||
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
|
||||
```
|
||||
|
||||
In this example, we use one stage (called `build`) to perform the actual Java build using Maven. In the second
|
||||
stage (starting at `FROM tomcat`), we copy in files from the `build` stage. The final image is only the last stage
|
||||
being created (which can be overridden using the `--target` flag).
|
||||
|
||||
|
||||
### React Example
|
||||
|
||||
When building React applications, we need a Node environment to compile the JS code (typically JSX), SASS stylesheets,
|
||||
and more into static HTML, JS, and CSS. If we aren't doing server-side rendering, we don't even need a Node environment
|
||||
for our production build. Why not ship the static resources in a static nginx container?
|
||||
|
||||
```dockerfile
|
||||
FROM node:12 AS build
|
||||
WORKDIR /app
|
||||
COPY package* yarn.lock ./
|
||||
RUN yarn install
|
||||
COPY public ./public
|
||||
COPY src ./src
|
||||
RUN yarn run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/build /usr/share/nginx/html
|
||||
```
|
||||
|
||||
Here, we are using a `node:12` image to perform the build (maximizing layer caching) and then copying the output
|
||||
into an nginx container. Cool, huh?
|
||||
|
||||
|
||||
## Recap
|
||||
|
||||
By understanding a little bit about how images are structured, we can build images faster and ship fewer changes.
|
||||
Scanning images gives us confidence that the containers we are running and distributing are secure.
|
||||
Multi-stage builds also help us reduce overall image size and increase final container security by separating
|
||||
build-time dependencies from runtime dependencies.
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: "What next"
|
||||
keywords: get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop
|
||||
description: Making sure you have more ideas of what you could do next with your application
|
||||
---
|
||||
|
||||
Although we're done with our workshop, there's still a LOT more to learn about containers!
|
||||
We're not going to go deep-dive here, but here are a few other areas to look at next!
|
||||
|
||||
## Container Orchestration
|
||||
|
||||
Running containers in production is tough. You don't want to log into a machine and simply run a
|
||||
`docker run` or `docker-compose up`. Why not? Well, what happens if the containers die? How do you
|
||||
scale across several machines? Container orchestration solves this problem. Tools like Kubernetes,
|
||||
Swarm, Nomad, and ECS all help solve this problem, all in slightly different ways.
|
||||
|
||||
The general idea is that you have "managers" who receive **expected state**. This state might be
|
||||
"I want to run two instances of my web app and expose port 80." The managers then look at all of the
|
||||
machines in the cluster and delegate work to "worker" nodes. The managers watch for changes (such as
|
||||
a container quitting) and then work to make **actual state** reflect the expected state.
|
||||
|
||||
|
||||
## Cloud Native Computing Foundation Projects
|
||||
|
||||
The CNCF is a vendor-neutral home for various open-source projects, including Kubernetes, Prometheus,
|
||||
Envoy, Linkerd, NATS, and more! You can view the [graduated and incubated projects here](https://www.cncf.io/projects/)
|
||||
and the entire [CNCF Landscape here](https://landscape.cncf.io/). There are a LOT of projects to help
|
||||
solve problems around monitoring, logging, security, image registries, messaging, and more!
|
||||
|
||||
So, if you're new to the container landscape and cloud-native application development, welcome! Please
|
||||
connect with the community, ask questions, and keep learning! We're excited to have you!
|
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 166 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 125 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 21 KiB |
|
@ -54,115 +54,83 @@ redirect_from:
|
|||
- /windows/step_two/
|
||||
---
|
||||
|
||||
{% include_relative nav.html selected="1" %}
|
||||
|
||||
Welcome! We are excited that you want to learn Docker.
|
||||
|
||||
This page contains step-by-step instructions on how to get started with Docker. We also recommend the video walkthrough from Dockercon 2020.
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/iqqDU2crIEQ?start=30" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
The Docker Quickstart training module teaches you how to:
|
||||
In this tutorial, you'll learn about creating and deploying Docker apps, including using multiple containers with a database, and using Docker Compose. You'll also deploy your containerized app to Azure.
|
||||
|
||||
1. Set up your Docker environment (on this page)
|
||||
|
||||
2. [Build and run your image](part2.md)
|
||||
## Start the tutorial
|
||||
|
||||
3. [Share images on Docker Hub](part3.md)
|
||||
If you've already run the command to get started with the tutorial, congratulations! If not, open a command prompt or bash window, and run the command:
|
||||
|
||||
## Docker concepts
|
||||
|
||||
Docker is a platform for developers and sysadmins to **build, run, and share**
|
||||
applications with containers. The use of containers to deploy applications
|
||||
is called _containerization_. Containers are not new, but their use for easily
|
||||
deploying applications is.
|
||||
|
||||
Containerization is increasingly popular because containers are:
|
||||
|
||||
- **Flexible**: Even the most complex applications can be containerized.
|
||||
- **Lightweight**: Containers leverage and share the host kernel,
|
||||
making them much more efficient in terms of system resources than virtual machines.
|
||||
- **Portable**: You can build locally, deploy to the cloud, and run anywhere.
|
||||
- **Loosely coupled**: Containers are highly self sufficient and encapsulated,
|
||||
allowing you to replace or upgrade one without disrupting others.
|
||||
- **Scalable**: You can increase and automatically distribute container replicas across a datacenter.
|
||||
- **Secure**: Containers apply aggressive constraints and isolations to processes without any configuration required on the part of the user.
|
||||
|
||||
### Images and containers
|
||||
|
||||
Fundamentally, a container is nothing but a running process,
|
||||
with some added encapsulation features applied to it in order to keep it isolated from the host and from other containers.
|
||||
One of the most important aspects of container isolation is that each container interacts with its own private filesystem; this filesystem is provided by a Docker **image**.
|
||||
An image includes everything needed to run an application - the code or binary,
|
||||
runtimes, dependencies, and any other filesystem objects required.
|
||||
|
||||
### Containers and virtual machines
|
||||
|
||||
A container runs _natively_ on Linux and shares the kernel of the host
|
||||
machine with other containers. It runs a discrete process, taking no more memory
|
||||
than any other executable, making it lightweight.
|
||||
|
||||
By contrast, a **virtual machine** (VM) runs a full-blown "guest" operating
|
||||
system with _virtual_ access to host resources through a hypervisor. In general,
|
||||
VMs incur a lot of overhead beyond what is being consumed by your application logic.
|
||||
|
||||
{:width="300px"} | {:width="300px"}
|
||||
|
||||
## Set up your Docker environment
|
||||
|
||||
### Download and install Docker Desktop
|
||||
|
||||
Docker Desktop is an easy-to-install application for your Mac or Windows environment that enables you to start coding and containerizing in minutes. Docker Desktop includes everything you need to build, run, and share containerized applications right from your machine.
|
||||
|
||||
Follow the instructions appropriate for your operating system to download and install Docker Desktop.
|
||||
|
||||
[Docker Desktop for Mac](/docker-for-mac/install/){: target="_blank" rel="noopener" class="_"}{: .button .outline-btn} [Docker Desktop for Windows](/docker-for-windows/install/){: target="_blank" rel="noopener" class="_"}{: .button .outline-btn}
|
||||
|
||||
### Test Docker version
|
||||
|
||||
After you've successfully installed Docker Desktop, open a terminal and run `docker --version` to check the version of Docker installed on your machine.
|
||||
|
||||
```shell
|
||||
$ docker --version
|
||||
Docker version 19.03.13, build 4484c46d9d
|
||||
```cli
|
||||
docker run -d -p 80:80 docker/getting-started
|
||||
```
|
||||
|
||||
### Test Docker installation
|
||||
You'll notice a few flags being used. Here's some more info on them:
|
||||
|
||||
1. Test that your installation works by running the [hello-world](https://hub.docker.com/_/hello-world/){: target="_blank" rel="noopener" class="_"} Docker image:
|
||||
- `-d` - run the container in detached mode (in the background)
|
||||
- `-p 80:80` - map port 80 of the host to port 80 in the container
|
||||
- `docker/getting-started` - the image to use
|
||||
|
||||
```shell
|
||||
$ docker run hello-world
|
||||
>**Pro tip**
|
||||
>
|
||||
>You can combine single character flags to shorten the full command.
|
||||
>As an example, the command above could be written as:
|
||||
>```
|
||||
>docker run -dp 80:80 docker/getting-started
|
||||
>```
|
||||
>
|
||||
|
||||
Unable to find image 'hello-world:latest' locally
|
||||
latest: Pulling from library/hello-world
|
||||
ca4f61b1923c: Pull complete
|
||||
Digest: sha256:ca0eeb6fb05351dfc8759c20733c91def84cb8007aa89a5bf606bc8b315b9fc7
|
||||
Status: Downloaded newer image for hello-world:latest
|
||||
## The Docker Dashboard
|
||||
|
||||
Hello from Docker!
|
||||
This message shows that your installation appears to be working correctly.
|
||||
...
|
||||
```
|
||||
Before going too far, we want to highlight the Docker Dashboard, which gives
|
||||
you a quick view of the containers running on your machine. It gives you quick
|
||||
access to container logs, lets you get a shell inside the container, and lets you
|
||||
easily manage container lifecycle (stop, remove, etc.).
|
||||
|
||||
2. Run `docker image ls` to list the `hello-world` image that you downloaded to your machine.
|
||||
To access the dashboard, follow the instructions for either
|
||||
[Mac](https://docs.docker.com/docker-for-mac/dashboard/) or
|
||||
[Windows](https://docs.docker.com/docker-for-windows/dashboard/). If you open the dashboard
|
||||
now, you will see this tutorial running! The container name (`jolly_bouman` below) is a
|
||||
randomly created name. So, you'll most likely have a different name.
|
||||
|
||||
3. List the `hello-world` container (spawned by the image) which exits after displaying its message. If it is still running, you do not need the `--all` option:
|
||||

|
||||
|
||||
```shell
|
||||
$ docker ps --all
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS
|
||||
54f4984ed6a8 hello-world "/hello" 20 seconds ago Exited (0) 19 seconds ago
|
||||
```
|
||||
## What is a container?
|
||||
|
||||
## Conclusion
|
||||
Now that you've run a container, what _is_ a container? Simply put, a container is
|
||||
simply another process on your machine that has been isolated from all other processes
|
||||
on the host machine. That isolation leverages [kernel namespaces and cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504), features that have been
|
||||
in Linux for a long time. Docker has worked to make these capabilities approachable and easy to use.
|
||||
|
||||
At this point, you've installed Docker Desktop on your development machine, and ran a quick test to ensure you are set up to build and run your first containerized application.
|
||||
!!! info "Creating Containers from Scratch"
|
||||
If you'd like to see how containers are built from scratch, Liz Rice from Aqua Security
|
||||
has a fantastic talk in which she creates a container from scratch in Go. While she makes
|
||||
a simple container, this talk doesn't go into networking, using images for the filesystem,
|
||||
and more. But, it gives a _fantastic_ deep dive into how things are working.
|
||||
|
||||
[On to Part 2 >>](part2.md){: class="button outline-btn" style="margin-bottom: 30px; margin-right: 100%"}
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/8fi7uSYlOdc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
For information on how to build and run your first containerized application using Node.js, go to [Build your Node.js image](/nodejs/build-images.md).
|
||||
## What is a container image?
|
||||
|
||||
When running a container, it uses an isolated filesystem. This custom filesystem is provided
|
||||
by a **container image**. Since the image contains the container's filesystem, it must contain everything
|
||||
needed to run an application - all dependencies, configuration, scripts, binaries, etc. The
|
||||
image also contains other configuration for the container, such as environment variables,
|
||||
a default command to run, and other metadata.
|
||||
|
||||
We'll dive deeper into images later on, covering topics such as layering, best practices, and more.
|
||||
|
||||
!!! info
|
||||
If you're familiar with `chroot`, think of a container as an extended version of `chroot`. The
|
||||
filesystem is simply coming from the image. But, a container adds additional isolation not
|
||||
available when simply using chroot.
|
||||
|
||||
## CLI references
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul class="pagination">
|
||||
<li {% if include.selected=="1"%}class="active"{% endif %}><a href="/get-started/part1/">Orientation and setup</a></li>
|
||||
<li {% if include.selected=="2"%}class="active"{% endif %}><a href="/get-started/part2/">Build and run your image</a></li>
|
||||
<li {% if include.selected=="1"%}class="active"{% endif %}><a href="/get-started/">Orientation and setup</a></li>
|
||||
<li {% if include.selected=="2"%}class="active"{% endif %}><a href="/get-started/02_our_app/">Our Application</a></li>
|
||||
<li {% if include.selected=="3"%}class="active"{% endif %}><a href="/get-started/part3/">Share images on Docker Hub</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
---
|
||||
title: "Build and run your image"
|
||||
keywords: containers, images, dockerfiles, node, code, coding, build, push, run
|
||||
description: Learn how to create a Docker image by writing a Dockerfile, and use it to run a simple container.
|
||||
---
|
||||
|
||||
{% include_relative nav.html selected="2" %}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Work through the orientation and setup in [Part 1](index.md).
|
||||
|
||||
## Introduction
|
||||
|
||||
Now that you've set up your development environment, you can begin to develop containerized applications. In general, the development workflow looks like this:
|
||||
|
||||
1. Create and test individual containers for each component of your application by first creating Docker images.
|
||||
|
||||
2. Assemble your containers and supporting infrastructure into a complete application.
|
||||
|
||||
3. Test, share, and deploy your complete containerized application.
|
||||
|
||||
In this stage of the tutorial, let's focus on step 1 of this workflow: creating the images that your containers will be based on. Remember, a Docker image captures the private filesystem that your containerized processes will run in; you need to create an image that contains just what your application needs to run.
|
||||
|
||||
## Set up
|
||||
|
||||
Let us download the `node-bulletin-board` example project. This is a simple bulletin board application written in Node.js.
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a data-toggle="tab" href="#clonegit">Git</a></li>
|
||||
<li><a data-toggle="tab" href="#clonewin">Windows (without Git)</a></li>
|
||||
<li><a data-toggle="tab" href="#clonemac">Mac or Linux (without Git)</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div id="clonegit" class="tab-pane fade in active">
|
||||
{% capture git-clone-content %}
|
||||
|
||||
### Git
|
||||
|
||||
If you are using Git, you can clone the example project from GitHub:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/dockersamples/node-bulletin-board
|
||||
cd node-bulletin-board/bulletin-board-app
|
||||
```
|
||||
|
||||
{% endcapture %}
|
||||
{{ git-clone-content | markdownify }}
|
||||
|
||||
</div>
|
||||
<div id="clonewin" class="tab-pane fade" markdown="1">
|
||||
{% capture win-clone-content %}
|
||||
|
||||
### Windows (without Git)
|
||||
|
||||
If you are using a Windows machine and prefer to download the example project without installing Git, run the following commands in PowerShell:
|
||||
|
||||
```shell
|
||||
curl.exe -LO https://github.com/dockersamples/node-bulletin-board/archive/master.zip
|
||||
tar.exe xf master.zip
|
||||
cd node-bulletin-board-master\bulletin-board-app
|
||||
```
|
||||
|
||||
{% endcapture %}
|
||||
{{ win-clone-content | markdownify }}
|
||||
</div>
|
||||
|
||||
<div id="clonemac" class="tab-pane fade" markdown="1">
|
||||
{% capture mac-clone-content %}
|
||||
|
||||
### Mac or Linux (without Git)
|
||||
|
||||
If you are using a Mac or a Linux machine and prefer to download the example project without installing Git, run the following commands in a terminal:
|
||||
|
||||
```shell
|
||||
curl -LO https://github.com/dockersamples/node-bulletin-board/archive/master.zip
|
||||
unzip master.zip
|
||||
cd node-bulletin-board-master/bulletin-board-app
|
||||
```
|
||||
|
||||
{% endcapture %}
|
||||
{{ mac-clone-content | markdownify }}
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
## Define a container with Dockerfile
|
||||
|
||||
After downloading the project, take a look at the file called `Dockerfile` in the bulletin board application. Dockerfiles describe how to assemble a private filesystem for a container, and can also contain some metadata describing how to run a container based on this image.
|
||||
|
||||
For more information about the Dockerfile used in the bulletin board application, see [Sample Dockerfile](#sample-dockerfile).
|
||||
|
||||
## Build and test your image
|
||||
|
||||
Now that you have some source code and a Dockerfile, it's time to build your first image, and make sure the containers launched from it work as expected.
|
||||
|
||||
Make sure you're in the directory `node-bulletin-board/bulletin-board-app` in a terminal or PowerShell using the `cd` command. Run the following command to build your bulletin board image:
|
||||
|
||||
```script
|
||||
docker build --tag bulletinboard:1.0 .
|
||||
```
|
||||
|
||||
You'll see Docker step through each instruction in your Dockerfile, building up your image as it goes. If successful, the build process should end with a message `Successfully tagged bulletinboard:1.0`.
|
||||
|
||||
> **Windows users:**
|
||||
>
|
||||
> This example uses Linux containers. Make sure your environment is running Linux containers by right-clicking on the Docker logo in your system tray, and clicking **Switch to Linux containers**. Don't worry - all the commands in this tutorial work the exact same way for Windows containers.
|
||||
>
|
||||
> You may receive a message titled 'SECURITY WARNING' after running the image, noting the read, write, and execute permissions being set for files added to your image. We aren't handling any sensitive information in this example, so feel free to disregard the warning in this example.
|
||||
|
||||
## Run your image as a container
|
||||
|
||||
1. Run the following command to start a container based on your new image:
|
||||
|
||||
```script
|
||||
docker run --publish 8000:8080 --detach --name bb bulletinboard:1.0
|
||||
```
|
||||
|
||||
There are a couple of common flags here:
|
||||
|
||||
- `--publish` asks Docker to forward traffic incoming on the host's port 8000 to the container's port 8080. Containers have their own private set of ports, so if you want to reach one from the network, you have to forward traffic to it in this way. Otherwise, firewall rules will prevent all network traffic from reaching your container, as a default security posture.
|
||||
- `--detach` asks Docker to run this container in the background.
|
||||
- `--name` specifies a name with which you can refer to your container in subsequent commands, in this case `bb`.
|
||||
|
||||
2. Visit your application in a browser at `localhost:8000`. You should see your bulletin board application up and running. At this step, you would normally do everything you could to ensure your container works the way you expected; now would be the time to run unit tests, for example.
|
||||
|
||||
3. Once you're satisfied that your bulletin board container works correctly, you can delete it:
|
||||
|
||||
```script
|
||||
docker rm --force bb
|
||||
```
|
||||
|
||||
The `--force` option stops a running container, so it can be removed. If you stop the container running with `docker stop bb` first, then you do not need to use `--force` to remove it.
|
||||
|
||||
## Conclusion
|
||||
|
||||
At this point, you've successfully built an image, performed a simple containerization of an application, and confirmed that your app runs successfully in its container. The next step will be to share your images on [Docker Hub](https://hub.docker.com/), so they can be easily downloaded and run on any destination machine.
|
||||
|
||||
[On to Part 3 >>](part3.md){: class="button outline-btn" style="margin-bottom: 30px; margin-right: 100%"}
|
||||
|
||||
## Deploying to the cloud
|
||||
|
||||
To run your containers in the cloud with either Azure or AWS, check out our docs on getting started with cloud deployments.
|
||||
* [Deploying with Docker and AWS](https://docs.docker.com/engine/context/ecs-integration/)
|
||||
* [Deploying with Docker and Azure](https://docs.docker.com/engine/context/aci-integration/)
|
||||
|
||||
## Sample Dockerfile
|
||||
|
||||
Writing a Dockerfile is the first step to containerizing an application. You can think of these Dockerfile commands as a step-by-step recipe on how to build up your image. The Dockerfile in the bulletin board app looks like this:
|
||||
|
||||
```dockerfile
|
||||
# Use the official image as a parent image.
|
||||
FROM node:current-slim
|
||||
|
||||
# Set the working directory.
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy the file from your host to your current location.
|
||||
COPY package.json .
|
||||
|
||||
# Run the command inside your image filesystem.
|
||||
RUN npm install
|
||||
|
||||
# Add metadata to the image to describe which port the container is listening on at runtime.
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the specified command within the container.
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
# Copy the rest of your app's source code from your host to your image filesystem.
|
||||
COPY . .
|
||||
```
|
||||
|
||||
The dockerfile defined in this example takes the following steps:
|
||||
|
||||
- Start `FROM` the pre-existing `node:current-slim` image. This is an *official image*, built by the node.js vendors and validated by Docker to be a high-quality image containing the Node.js Long Term Support (LTS) interpreter and basic dependencies.
|
||||
- Use `WORKDIR` to specify that all subsequent actions should be taken from the directory `/usr/src/app` *in your image filesystem* (never the host's filesystem).
|
||||
- `COPY` the file `package.json` from your host to the present location (`.`) in your image (so in this case, to `/usr/src/app/package.json`)
|
||||
- `RUN` the command `npm install` inside your image filesystem (which will read `package.json` to determine your app's node dependencies, and install them)
|
||||
- `COPY` in the rest of your app's source code from your host to your image filesystem.
|
||||
|
||||
You can see that these are much the same steps you might have taken to set up and install your app on your host. However, capturing these as a Dockerfile allows you to do the same thing inside a portable, isolated Docker image.
|
||||
|
||||
The steps above built up the filesystem of our image, but there are other lines in your Dockerfile.
|
||||
|
||||
The `CMD` directive is the first example of specifying some metadata in your image that describes how to run a container based on this image. In this case, it's saying that the containerized process that this image is meant to support is `npm start`.
|
||||
|
||||
The `EXPOSE 8080` informs Docker that the container is listening on port 8080 at runtime.
|
||||
|
||||
What you see above is a good way to organize a simple Dockerfile; always start with a `FROM` command, follow it with the steps to build up your private filesystem, and conclude with any metadata specifications. There are many more Dockerfile directives than just the few you see above. For a complete list, see the [Dockerfile reference](https://docs.docker.com/engine/reference/builder/).
|
||||
|
||||
## CLI references
|
||||
|
||||
Further documentation for all CLI commands used in this article are available here:
|
||||
|
||||
- [docker image](https://docs.docker.com/engine/reference/commandline/image/)
|
||||
- [docker container](https://docs.docker.com/engine/reference/commandline/container/)
|
||||
- [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)
|
|
@ -1,76 +0,0 @@
|
|||
---
|
||||
title: "Share images on Docker Hub"
|
||||
keywords: docker hub, push, images
|
||||
description: Learn how to share images on Docker Hub.
|
||||
redirect_from:
|
||||
- /get-started/part5/
|
||||
---
|
||||
|
||||
{% include_relative nav.html selected="3" %}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Work through the steps to build an image and run it as a containerized application in [Part 2](part2.md).
|
||||
|
||||
## Introduction
|
||||
|
||||
At this point, you've built a containerized application described in [Part 2](part2.md) on your local development machine.
|
||||
|
||||
The final step in developing a containerized application is to share your images on a registry like [Docker Hub](https://hub.docker.com/){: target="_blank" rel="noopener" class="_”}, so they can be easily downloaded and run on any destination machine.
|
||||
|
||||
## Set up your Docker Hub account
|
||||
|
||||
If you don't have a Docker ID, follow these steps to create one. A Docker ID allows you to share images on Docker Hub.
|
||||
|
||||
1. Visit the [Docker Hub sign up](https://hub.docker.com/signup){: target="_blank" rel="noopener" class="_”} page.
|
||||
|
||||
2. Fill out the form and submit to create your Docker ID.
|
||||
|
||||
3. Verify your email address to complete the registration process.
|
||||
|
||||
4. Click on the Docker icon in your toolbar or system tray, and click **Sign in / Create Docker ID**.
|
||||
|
||||
5. Fill in your new Docker ID and password. After you have successfully authenticated, your Docker ID appears in the Docker Desktop menu in place of the 'Sign in' option you just used.
|
||||
|
||||
You can also sign into Docker Hub from the command line by typing `docker login`.
|
||||
|
||||
## Create a Docker Hub repository and push your image
|
||||
|
||||
>
|
||||
> Before creating a repository, ensure you’ve set up your Docker Hub account and have connected it to your Docker Desktop.
|
||||
|
||||
Now let's create your first repository, and push your bulletin board image to Docker Hub.
|
||||
|
||||
1. Click on the Docker icon in your menu bar, and navigate to **Repositories > Create**. You'll be redirected to the **Create Repository** page on Docker Hub.
|
||||
|
||||
2. Type the repository name as `bulletinboard` and click **Create** at the bottom of the page. Do not fill any other details for now.
|
||||
|
||||
{:width="100%"}
|
||||
|
||||
3. You are now ready to share your image on Docker Hub, however, there's one thing you must do first: images must be *namespaced correctly* to share on Docker Hub. Specifically, you must name images like `<Your Docker ID>/<Repository Name>:<tag>`.
|
||||
|
||||
Make sure you’re in the `node-bulletin-board/bulletin-board-app` directory in a terminal or PowerShell then and run:
|
||||
|
||||
```shell
|
||||
docker tag bulletinboard:1.0 <Your Docker ID>/bulletinboard:1.0
|
||||
```
|
||||
|
||||
4. Finally, push your image to Docker Hub:
|
||||
|
||||
```shell
|
||||
docker push <Your Docker ID>/bulletinboard:1.0
|
||||
```
|
||||
|
||||
Visit your repository in [Docker Hub](https://hub.docker.com/repositories){: target="_blank" rel="noopener" class="_”}, and you'll see your new image there. Remember, Docker Hub repositories are public by default.
|
||||
|
||||
> **Having trouble pushing?** Remember, you must be signed into Docker Hub through Docker Desktop or the command line, and you must also name your images correctly, as per the above steps. If the push seemed to work, but you don't see it in Docker Hub, refresh your browser after a couple of minutes and check again.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Now that your image is available on Docker Hub, you'll be able to run it anywhere. If you try to use it on a new machine that doesn't have it yet, Docker will automatically try and download it from Docker Hub. By moving images around in this way, you no longer need to install any dependencies except Docker on the machines you want to run your software on. The dependencies of containerized applications are completely encapsulated and isolated within your images, which you can share using Docker Hub as described above.
|
||||
|
||||
Another thing to keep in mind: at the moment, you've only pushed your image to Docker Hub; what about your Dockerfile? A crucial best practice is to keep these in version control, perhaps alongside your source code for your application. You can add a link or note in your Docker Hub repository description indicating where these files can be found, preserving the record not only of how your image was built, but how it's meant to be run as a full application.
|
||||
|
||||
## Where to go next
|
||||
|
||||
We recommend that you take a look at the topics in [Develop with Docker](../develop/index.md) to learn how to develop your own applications using Docker.
|