Merge pull request #25963 from usha-mandya/Engdocs-3371

Remove the outdated docker workshop section
This commit is contained in:
Usha Mandya authored and GitHub committed 2026-08-28 12:19:39 +01:00
commit cbb8be0715
28 files changed
+48 -2612

No files matched your search

-4
View File
@@ -24,10 +24,6 @@ params:
description: Gain a better understanding of foundational Docker concepts.
link: /get-started/docker-concepts/the-basics/what-is-a-container/
icon: home
- title: Docker workshop
description: Get guided through a 45-minute workshop to learn about Docker.
link: /get-started/workshop/
icon: computer-desktop
aliases:
- /engine/get-started/
- /engine/tutorials/usingdocker/
@@ -17,6 +17,41 @@ params:
prerequisites: None
aliases:
- /guides/getting-started/
- /guides/get-started/
- /get-started/hands-on-overview/
- /guides/workshop/
- /get-started/workshop/
- /get-started/part2/
- /get-started/02_our_app/
- /guides/workshop/02_our_app/
- /guides/walkthroughs/containerize-your-app/
- /get-started/workshop/02_our_app/
- /get-started/03_updating_app/
- /guides/workshop/03_updating_app/
- /get-started/workshop/03_updating_app/
- /get-started/part3/
- /get-started/04_sharing_app/
- /guides/workshop/04_sharing_app/
- /get-started/workshop/04_sharing_app/
- /get-started/05_persisting_data/
- /guides/workshop/05_persisting_data/
- /get-started/workshop/05_persisting_data/
- /guides/walkthroughs/access-local-folder/
- /get-started/06_bind_mounts/
- /guides/workshop/06_bind_mounts/
- /get-started/workshop/06_bind_mounts/
- /get-started/07_multi_container/
- /guides/workshop/07_multi_container/
- /get-started/workshop/07_multi_container/
- /get-started/08_using_compose/
- /guides/workshop/08_using_compose/
- /get-started/workshop/08_using_compose/
- /get-started/09_image_best/
- /guides/workshop/09_image_best/
- /get-started/workshop/09_image_best/
- /get-started/11_what_next/
- /guides/workshop/10_what_next/
- /get-started/workshop/10_what_next/
---
## About this series
-170
View File
@@ -1,170 +0,0 @@
---
title: Containerize an application
weight: 20
linkTitle: "Part 1: Containerize an application"
keywords: |
dockerfile example, Containerize an application, run docker file, running
docker file, how to run dockerfile, example dockerfile, how to create a docker container,
create dockerfile, simple dockerfile, creating containers
description: |
Follow this step-by-step guide to learn how to create and run a containerized
application using Docker
aliases:
- /get-started/part2/
- /get-started/02_our_app/
- /guides/workshop/02_our_app/
- /guides/walkthroughs/containerize-your-app/
---
For the rest of this guide, you'll be working with a simple todo
list manager that runs on Node.js. If you're not familiar with Node.js,
don't worry. This guide doesn't require any prior experience with JavaScript.
## Prerequisites
- You have installed the latest version of [Docker Desktop](/get-started/get-docker.md).
- You have installed a [Git client](https://git-scm.com/downloads).
- You have an IDE or a text editor to edit files. Docker recommends using [Visual Studio Code](https://code.visualstudio.com/).
## Get the app
Before you can run the application, you need to get the application source code onto your machine.
1. Clone the [getting-started-app repository](https://github.com/docker/getting-started-app/tree/main) using the following command:
```console
$ git clone https://github.com/docker/getting-started-app.git
```
2. View the contents of the cloned repository. You should see the following files and sub-directories.
```text
├── getting-started-app/
│ ├── .dockerignore
│ ├── package.json
│ ├── package-lock.json
│ ├── README.md
│ ├── spec/
│ ├── src/
```
## Build the app's image
To build the image, you'll need to use a Dockerfile. A Dockerfile is simply a text-based file with no file extension that contains a script of instructions. Docker uses this script to build a container image.
1. In the `getting-started-app` directory, the same location as the
`package.json` file, create a file named `Dockerfile` with the following contents:
```dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install --omit=dev
CMD ["node", "src/index.js"]
EXPOSE 3000
```
This Dockerfile does the following:
- Uses `node:24-alpine` as the base image, a lightweight Linux image with Node.js pre-installed
- Sets `/app` as the working directory
- Copies source code into the image
- Installs the necessary dependencies
- Specifies the command to start the application
- Documents that the app listens on port 3000
2. Build the image using the following commands:
In the terminal, make sure you're in the `getting-started-app` directory. Replace `/path/to/getting-started-app` with the path to your `getting-started-app` directory.
```console
$ cd /path/to/getting-started-app
```
Build the image.
```console
$ docker build -t getting-started .
```
The `docker build` command uses the Dockerfile to build a new image. You might have noticed that Docker downloaded a lot of "layers". This is because you instructed the builder that you wanted to start from the `node:24-alpine` image. But, since you didn't have that on your machine, Docker needed to download the image.
After Docker downloaded the image, the instructions from the Dockerfile copied in your application and used `npm` to install your application's dependencies.
Finally, the `-t` flag tags your image. Think of this as a human-readable name for the final image. Since you named the image `getting-started`, you can refer to that image when you run a container.
The `.` at the end of the `docker build` command tells Docker that it should look for the `Dockerfile` in the current directory.
## Start an app container
Now that you have an image, you can run the application in a container using the `docker run` command.
1. Run your container using the `docker run` command and specify the name of the image you just created:
```console
$ docker run -d -p 127.0.0.1:3000:3000 getting-started
```
The `-d` flag (short for `--detach`) runs the container in the background.
This means that Docker starts your container and returns you to the terminal
prompt. Also, it does not display logs in the terminal.
The `-p` flag (short for `--publish`) creates a port mapping between the
host and the container. The `-p` flag takes a string value in the format of
`HOST:CONTAINER`, where `HOST` is the address on the host, and `CONTAINER`
is the port on the container. The command publishes the container's port
3000 to `127.0.0.1:3000` (`localhost:3000`) on the host. Without the port
mapping, you wouldn't be able to access the application from the host.
2. After a few seconds, open your web browser to [http://localhost:3000](http://localhost:3000).
You should see your app.
![Empty todo list](images/todo-list-empty.webp)
3. Add an item or two and see that it works as you expect. You can mark items as complete and remove them. Your frontend is successfully storing items in the backend.
At this point, you have a running todo list manager with a few items.
If you take a quick look at your containers, you should see at least one container running that's using the `getting-started` image and on port `3000`. To see your containers, you can use the CLI or Docker Desktop's graphical interface.
{{< tabs >}}
{{< tab name="CLI" >}}
Run the `docker ps` command in a terminal to list your containers.
```console
$ docker ps
```
Output similar to the following should appear.
```console
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
df784548666d getting-started "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 127.0.0.1:3000->3000/tcp priceless_mcclintock
```
{{< /tab >}}
{{< tab name="Docker Desktop" >}}
In Docker Desktop, select the **Containers** tab to see a list of your containers.
![Docker Desktop with get-started container running](images/dashboard-two-containers.webp)
{{< /tab >}}
{{< /tabs >}}
## Summary
In this section, you learned the basics about creating a Dockerfile to build an image. Once you built an image, you started a container and saw the running app.
Related information:
- [Dockerfile reference](/reference/dockerfile/)
- [docker CLI reference](/reference/cli/docker/)
## Next steps
Next, you're going to make a modification to your app and learn how to update your running application with a new image. Along the way, you'll learn a few other useful commands.
{{< button text="Update the application" url="03_updating_app.md" >}}
@@ -1,112 +0,0 @@
---
title: Update the application
weight: 30
linkTitle: "Part 2: Update the application"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Making changes to your application
aliases:
- /get-started/03_updating_app/
- /guides/workshop/03_updating_app/
---
In [part 1](./02_our_app.md), you containerized a todo application. In this part, you'll update the application and image. You'll also learn how to stop and remove a container.
## Update the source code
In the following steps, you'll change the "empty text" when you don't have any todo list items to "You have no todo items yet! Add one above!"
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>
```
2. Build your updated version of the image, using the `docker build` command.
```console
$ docker build -t getting-started .
```
3. Start a new container using the updated code.
```console
$ docker run -dp 127.0.0.1:3000:3000 getting-started
```
You probably saw an error like this:
```console
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 127.0.0.1:3000 failed: port is already allocated.
```
The error occurred because you aren't able to start the new container while your old container is still running. The reason is that the old container is already using the host's port 3000 and only one process on the machine (containers included) can listen to a specific port. To fix this, you need to remove the old container.
## Remove the old container
To remove a container, you first need to stop it. Once it has stopped, you can remove it. You can remove the old container using the CLI or Docker Desktop's graphical interface. Choose the option that you're most comfortable with.
{{< tabs >}}
{{< tab name="CLI" >}}
### Remove a container using the CLI
1. Get the ID of the container by using the `docker ps` command.
```console
$ docker ps
```
2. Use the `docker stop` command to stop the container. Replace `<the-container-id>` with the ID from `docker ps`.
```console
$ docker stop <the-container-id>
```
3. Once the container has stopped, you can remove it by using the `docker rm` command.
```console
$ 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>`
{{< /tab >}}
{{< tab name="Docker Desktop" >}}
### Remove a container using Docker Desktop
1. Open Docker Desktop to the **Containers** view.
2. Select the trash can icon under the **Actions** column for the container that you want to delete.
3. In the confirmation dialog, select **Delete forever**.
{{< /tab >}}
{{< /tabs >}}
### Start the updated app container
1. Now, start your updated app using the `docker run` command.
```console
$ docker run -dp 127.0.0.1:3000:3000 getting-started
```
2. Refresh your browser on [http://localhost:3000](http://localhost:3000) and you should see your updated help text.
## Summary
In this section, you learned how to update and rebuild an image, as well as how to stop and remove a container.
Related information:
- [docker CLI reference](/reference/cli/docker/)
## Next steps
Next, you'll learn how to share images with others.
{{< button text="Share the application" url="04_sharing_app.md" >}}
@@ -1,104 +0,0 @@
---
title: Share the application
weight: 40
linkTitle: "Part 3: Share the application"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop, docker hub, sharing
description: Sharing your image you built for your example application so you can
run it else where and other developers can use it
aliases:
- /get-started/part3/
- /get-started/04_sharing_app/
- /guides/workshop/04_sharing_app/
---
Now that you've built an image, you can 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 you've used have come from.
> **Docker ID**
>
> A Docker ID lets you access Docker Hub, which is the world's largest library and community for container images. Create a [Docker ID](https://hub.docker.com/signup) for free if you don't have one.
## Create a repository
To push an image, you first need to create a repository on Docker Hub.
1. [Sign up](https://www.docker.com/pricing?ref=Docs&refAction=DocsSharingApp) or Sign in to [Docker Hub](https://hub.docker.com).
2. Select the **Create Repository** button.
3. For the repository name, use `getting-started`. Make sure the **Visibility** is **Public**.
4. Select **Create**.
In the following image, you can see an example Docker command from Docker Hub. This command will push to this repository.
![Docker command with push example](images/push-command.webp)
## Push the image
Let's try to push the image to Docker Hub.
1. In the command line, run the following command:
```console
docker push docker/getting-started
```
You'll see an error like this:
```console
$ 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
```
This failure is expected because the image isn't tagged correctly yet.
Docker is looking for an image name `docker/getting-started`, but your
local image is still named `getting-started`.
You can confirm this by running:
```console
docker image ls
```
2. To fix this, first sign in to Docker Hub using your Docker ID: `docker login -u YOUR-USER-NAME`.
3. Use the `docker tag` command to give the `getting-started` image a new name. Replace `YOUR-USER-NAME` with your Docker ID.
```console
$ docker tag getting-started YOUR-USER-NAME/getting-started
```
4. Now run the `docker push` command again. If you're copying the value from
Docker Hub, you can drop the `tagname` part, as you didn't add a tag to the
image name. If you don't specify a tag, Docker uses a tag called `latest`.
```console
$ docker push YOUR-USER-NAME/getting-started
```
## Run the image on a new instance
Now that your image has been built and pushed into a registry, you can run your app on any machine that has Docker installed. Try pulling and running your image on another computer or a cloud instance.
## Summary
In this section, you learned how to share your images by pushing them to a
registry. You 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.
Related information:
- [docker CLI reference](/reference/cli/docker/)
- [Multi-platform images](/manuals/build/building/multi-platform.md)
- [Docker Hub overview](/manuals/docker-hub/_index.md)
## Next steps
In the next section, you'll learn how to persist data in your containerized application.
{{< button text="Persist the DB" url="05_persisting_data.md" >}}
@@ -1,210 +0,0 @@
---
title: Persist the DB
weight: 50
linkTitle: "Part 4: Persist the DB"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Making your DB persistent in your application
aliases:
- /get-started/05_persisting_data/
- /guides/workshop/05_persisting_data/
---
In case you didn't notice, your todo list is empty every single time
you launch the container. Why is this? In this part, you'll 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're using the same image.
### See this in practice
To see this in action, you're going to start two containers. In one container,
you'll create a file. In the other container, you'll check whether that same
file exists.
1. Start an Alpine container and create a new file in it.
```console
$ docker run --rm alpine touch greeting.txt
```
> [!TIP]
> Any commands you specify after the image name (in this case, `alpine`)
> are executed inside the container. In this case, the command `touch
> greeting.txt` puts a file named `greeting.txt` on the container's filesystem.
2. Run a new Alpine container and use the `stat` command to check whether the file exists.
```console
$ docker run --rm alpine stat greeting.txt
```
You should see output similar to the following that indicates the file does not exist in the new container.
```console
stat: can't stat 'greeting.txt': No such file or directory
```
The `greeting.txt` file created by the first container did not exist in the
second container. That is because the writeable "top layer" of each container
is isolated. Even though both containers shared the same underlying layers that
make up the base image, the writable layer is unique to each container.
## Container volumes
With the previous experiment, you 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 you remove the container
and Docker isolates all changes to that container. With volumes, you can change all of this.
[Volumes](/manuals/engine/storage/volumes.md) provide the ability to connect specific filesystem paths of
the container back to the host machine. If you mount a directory in the container, changes in that
directory are also seen on the host machine. If you mount that same directory across container restarts, you'd see
the same files.
There are two main types of volumes. You'll eventually use both, but you'll start with volume mounts.
## Persist the todo data
By default, the todo app stores its data in a SQLite database at
`/etc/todos/todo.db` in the container's filesystem. If you're not familiar with SQLite, no worries! It's simply a relational database that stores all the data in a single file. While this isn't the best for large-scale applications,
it works for small demos. You'll learn how to switch this to a different database engine later.
With the database being a single file, if you 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 where you stored the data, you can persist the data. As your container
writes to the `todo.db` file, it will persist the data to the host in the volume.
As mentioned, you're going to use a volume mount. Think of a volume mount as an opaque bucket of data.
Docker fully manages the volume, including the storage location on disk. You only need to remember the
name of the volume.
### Create a volume and start the container
You can create the volume and start the container using the CLI or Docker Desktop's graphical interface.
{{< tabs >}}
{{< tab name="CLI" >}}
1. Create a volume by using the `docker volume create` command.
```console
$ docker volume create todo-db
```
2. Stop and remove the todo app container once again with `docker rm -f <id>`,
as it is still running without using the persistent volume.
3. Start the todo app container, but add the `--mount` option to specify a
volume mount. Use the volume named `todo-db`, and mount it to `/etc/todos` in the
container, which captures all files created at the path.
```console
$ docker run -dp 127.0.0.1:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started
```
> [!NOTE]
>
> If you're using Git Bash, you must use different syntax for this command.
>
> ```console
> $ docker run -dp 127.0.0.1:3000:3000 --mount type=volume,src=todo-db,target=//etc/todos getting-started
> ```
>
> For more details about Git Bash's syntax differences, see
> [Working with Git Bash](/desktop/troubleshoot-and-support/troubleshoot/topics/#docker-commands-failing-in-git-bash).
{{< /tab >}}
{{< tab name="Docker Desktop" >}}
To create a volume:
1. Select **Volumes** in Docker Desktop.
2. In **Volumes**, select **Create**.
3. Specify `todo-db` as the volume name, and then select **Create**.
To stop and remove the app container:
1. Select **Containers** in Docker Desktop.
2. Select **Delete** in the **Actions** column for the container.
To start the todo app container with the volume mounted:
1. Select the search box at the top of Docker Desktop.
2. In the search window, select the **Images** tab.
3. In the search box, specify the image name, `getting-started`.
> [!TIP]
>
> Use the search filter to filter images and only show **Local images**.
4. Select your image and then select **Run**.
5. Select **Optional settings**.
6. In **Host port**, specify the port, for example, `3000`.
7. In **Host path**, specify the name of the volume, `todo-db`.
8. In **Container path**, specify `/etc/todos`.
9. Select **Run**.
{{< /tab >}}
{{< /tabs >}}
### Verify that the data persists
1. Once the container starts up, open the app and add a few items to your todo list.
![Items added to todo list](images/items-added.webp)
2. Stop and remove the container for the todo app. Use Docker Desktop or `docker ps` to get the ID and then `docker rm -f <id>` to remove it.
3. Start a new container using the previous steps.
4. Open the app. You should see your items still in your list.
5. Go ahead and remove the container when you're done checking out your list.
You've now learned how to persist data.
## Dive into the volume
A lot of people frequently ask "Where is Docker storing my data when I use a volume?" If you want to know,
you can use the `docker volume inspect` command.
```console
$ docker volume inspect todo-db
```
You should see output like the following:
```console
[
{
"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 of the data on the disk. Note that on most machines, you will
need to have root access to access this directory from the host.
## Summary
In this section, you learned how to persist container data.
Related information:
- [docker CLI reference](/reference/cli/docker/)
- [Volumes](/manuals/engine/storage/volumes.md)
## Next steps
Next, you'll learn how you can develop your app more efficiently using bind mounts.
{{< button text="Use bind mounts" url="06_bind_mounts.md" >}}
@@ -1,451 +0,0 @@
---
title: Use bind mounts
weight: 60
linkTitle: "Part 5: Use bind mounts"
keywords: 'get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop'
description: Using bind mounts in our application
aliases:
- /guides/walkthroughs/access-local-folder/
- /get-started/06_bind_mounts/
- /guides/workshop/06_bind_mounts/
---
In [part 4](./05_persisting_data.md), you used a volume mount to persist the
data in your database. A volume mount is a great choice when you need somewhere
persistent to store your application data.
A bind mount is another type of mount, which lets you share a directory from the
host's filesystem into the container. When working on an application, you can
use a bind mount to mount source code into the container. The container sees the
changes you make to the code immediately, as soon as you save a file. This means
that you can run processes in the container that watch for filesystem changes
and respond to them.
In this chapter, you'll see how you can use bind mounts and a tool called
[nodemon](https://npmjs.com/package/nodemon) to watch for file changes, and then restart the application
automatically. There are equivalent tools in most other languages and
frameworks.
## Quick volume type comparisons
The following are examples of a named volume and a bind mount using `--mount`:
- Named volume: `type=volume,src=my-volume,target=/usr/local/data`
- Bind mount: `type=bind,src=/path/to/data,target=/usr/local/data`
The following table outlines the main differences between volume mounts and bind
mounts.
| | Named volumes | Bind mounts |
| -------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------- |
| Host location | Docker chooses | You decide |
| Populates new volume with container contents | Yes | No |
| Supports Volume Drivers | Yes | No |
## Trying out bind mounts
Before looking at how you can use bind mounts for developing your application,
you can run a quick experiment to get a practical understanding of how bind mounts
work.
1. Verify that your `getting-started-app` directory is in a directory defined in
Docker Desktop's file sharing setting. This setting defines which parts of your
filesystem you can share with containers. For details about accessing the setting, see [File sharing](/manuals/desktop/settings-and-maintenance/settings.md#file-sharing).
> [!NOTE]
> The **File sharing** tab is only available in Hyper-V mode, because the files are automatically shared in WSL 2 mode and Windows container mode.
2. Open a terminal and change directory to the `getting-started-app`
directory.
3. Run the following command to start `bash` in an `ubuntu` container with a
bind mount.
{{< tabs >}}
{{< tab name="Mac / Linux" >}}
```console
$ docker run -it --mount type=bind,src=.,target=/src ubuntu bash
```
{{< /tab >}}
{{< tab name="Command Prompt" >}}
```console
$ docker run -it --mount "type=bind,src=%cd%,target=/src" ubuntu bash
```
{{< /tab >}}
{{< tab name="Git Bash" >}}
```console
$ docker run -it --mount type=bind,src="./",target=/src ubuntu bash
```
{{< /tab >}}
{{< tab name="PowerShell" >}}
```console
$ docker run -it --mount "type=bind,src=.,target=/src" ubuntu bash
```
{{< /tab >}}
{{< /tabs >}}
The `--mount type=bind` option tells Docker to create a bind mount, where `src` is the
current working directory on your host machine (`getting-started-app`), and
`target` is where that directory should appear inside the container (`/src`).
4. After running the command, Docker starts an interactive `bash` session in the
root directory of the container's filesystem.
```console
root@ac1237fad8db:/# pwd
/
root@ac1237fad8db:/# ls
bin dev home media opt root sbin srv tmp var
boot etc lib mnt proc run src sys usr
```
5. Change directory to the `src` directory.
This is the directory that you mounted when starting the container. Listing
the contents of this directory displays the same files as in the
`getting-started-app` directory on your host machine.
```console
root@ac1237fad8db:/# cd src
root@ac1237fad8db:/src# ls
Dockerfile node_modules package.json package-lock.json spec src
```
6. Create a new file named `myfile.txt`.
```console
root@ac1237fad8db:/src# touch myfile.txt
root@ac1237fad8db:/src# ls
Dockerfile myfile.txt node_modules package.json package-lock.json spec src
```
7. Open the `getting-started-app` directory on the host and observe that the
`myfile.txt` file is in the directory.
```text
├── getting-started-app/
│ ├── Dockerfile
│ ├── myfile.txt
│ ├── node_modules/
│ ├── package.json
│ ├── package-lock.json
│ ├── spec/
│ └── src/
```
8. From the host, delete the `myfile.txt` file.
9. In the container, list the contents of the `app` directory once more. Observe that the file is now gone.
```console
root@ac1237fad8db:/src# ls
Dockerfile node_modules package.json package-lock.json spec src
```
10. Stop the interactive container session with `Ctrl` + `D`.
That's all for a brief introduction to bind mounts. This procedure
demonstrated how files are shared between the host and the container, and how
changes are immediately reflected on both sides. Now you can use
bind mounts to develop software.
## Development containers
Using bind mounts is common for local development setups. The advantage is that the development machine doesn’t need to have all of the build tools and environments installed. With a single docker run command, Docker pulls dependencies and tools.
### Run your app in a development container
The following steps describe how to run a development container with a bind
mount that does the following:
- Mount your source code into the container
- Install all dependencies
- Start `nodemon` to watch for filesystem changes
You can use the CLI or Docker Desktop to run your container with a bind mount.
{{< tabs >}}
{{< tab name="Mac / Linux CLI" >}}
1. Make sure you don't have any `getting-started` containers currently running.
2. Run the following command from the `getting-started-app` directory.
```console
$ docker run -dp 127.0.0.1:3000:3000 \
-w /app --mount type=bind,src=.,target=/app \
node:24-alpine \
sh -c "npm install && npm run dev"
```
The following is a breakdown of the command:
- `-dp 127.0.0.1:3000:3000` - same as before. Run in detached (background) mode and
create a port mapping
- `-w /app` - sets the "working directory" or the current directory that the
command will run from
- `--mount type=bind,src=.,target=/app` - bind mount the current
directory from the host into the `/app` directory in the container
- `node:24-alpine` - the image to use. Note that this is the base image for
your app from the Dockerfile
- `sh -c "npm install && npm run dev"` - the command. You're starting a
shell using `sh` (alpine doesn't have `bash`) and running `npm install` to
install packages and then running `npm run dev` to start the development
server. If you look in the `package.json`, you'll see that the `dev` script
starts `nodemon`.
3. You can watch the logs using `docker logs <container-id>`. You'll know you're
ready to go when you see this:
```console
$ docker logs -f <container-id>
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[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`.
{{< /tab >}}
{{< tab name="PowerShell CLI" >}}
1. Make sure you don't have any `getting-started` containers currently running.
2. Run the following command from the `getting-started-app` directory.
```powershell
$ docker run -dp 127.0.0.1:3000:3000 `
-w /app --mount "type=bind,src=.,target=/app" `
node:24-alpine `
sh -c "npm install && npm run dev"
```
The following is a breakdown of the command:
- `-dp 127.0.0.1:3000:3000` - same as before. Run in detached (background) mode and
create a port mapping
- `-w /app` - sets the "working directory" or the current directory that the
command will run from
- `--mount "type=bind,src=.,target=/app"` - bind mount the current
directory from the host into the `/app` directory in the container
- `node:24-alpine` - the image to use. Note that this is the base image for
your app from the Dockerfile
- `sh -c "npm install && npm run dev"` - the command. You're starting a
shell using `sh` (alpine doesn't have `bash`) and running `npm install` to
install packages and then running `npm run dev` to start the development
server. If you look in the `package.json`, you'll see that the `dev` script
starts `nodemon`.
3. You can watch the logs using `docker logs <container-id>`. You'll know you're
ready to go when you see this:
```console
$ docker logs -f <container-id>
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[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`.
{{< /tab >}}
{{< tab name="Command Prompt CLI" >}}
1. Make sure you don't have any `getting-started` containers currently running.
2. Run the following command from the `getting-started-app` directory.
```console
$ docker run -dp 127.0.0.1:3000:3000 ^
-w /app --mount "type=bind,src=%cd%,target=/app" ^
node:24-alpine ^
sh -c "npm install && npm run dev"
```
The following is a breakdown of the command:
- `-dp 127.0.0.1:3000:3000` - same as before. Run in detached (background) mode and
create a port mapping
- `-w /app` - sets the "working directory" or the current directory that the
command will run from
- `--mount "type=bind,src=%cd%,target=/app"` - bind mount the current
directory from the host into the `/app` directory in the container
- `node:24-alpine` - the image to use. Note that this is the base image for
your app from the Dockerfile
- `sh -c "npm install && npm run dev"` - the command. You're starting a
shell using `sh` (alpine doesn't have `bash`) and running `npm install` to
install packages and then running `npm run dev` to start the development
server. If you look in the `package.json`, you'll see that the `dev` script
starts `nodemon`.
3. You can watch the logs using `docker logs <container-id>`. You'll know you're
ready to go when you see this:
```console
$ docker logs -f <container-id>
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[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`.
{{< /tab >}}
{{< tab name="Git Bash CLI" >}}
1. Make sure you don't have any `getting-started` containers currently running.
2. Run the following command from the `getting-started-app` directory.
```console
$ docker run -dp 127.0.0.1:3000:3000 \
-w //app --mount type=bind,src="./",target=/app \
node:24-alpine \
sh -c "npm install && npm run dev"
```
The following is a breakdown of the command:
- `-dp 127.0.0.1:3000:3000` - same as before. Run in detached (background) mode and
create a port mapping
- `-w //app` - sets the "working directory" or the current directory that the
command will run from
- `--mount type=bind,src="/.",target=/app` - bind mount the current
directory from the host into the `/app` directory in the container
- `node:24-alpine` - the image to use. Note that this is the base image for
your app from the Dockerfile
- `sh -c "npm install && npm run dev"` - the command. You're starting a
shell using `sh` (alpine doesn't have `bash`) and running `npm install` to
install packages and then running `npm run dev` to start the development
server. If you look in the `package.json`, you'll see that the `dev` script
starts `nodemon`.
3. You can watch the logs using `docker logs <container-id>`. You'll know you're
ready to go when you see this:
```console
$ docker logs -f <container-id>
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[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`.
{{< /tab >}}
{{< tab name="Docker Desktop" >}}
Make sure you don't have any `getting-started` containers currently running.
Run the image with a bind mount.
1. Select the search box at the top of Docker Desktop.
2. In the search window, select the **Images** tab.
3. In the search box, specify the container name, `getting-started`.
> [!TIP]
>
> Use the search filter to filter images and only show **Local images**.
4. Select your image and then select **Run**.
5. Select **Optional settings**.
6. In **Host path**, specify the path to the `getting-started-app` directory on your host machine.
7. In **Container path**, specify `/app`.
8. Select **Run**.
You can watch the container logs using Docker Desktop.
1. Select **Containers** in Docker Desktop.
2. Select your container name.
You'll know you're ready to go when you see this:
```console
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
```
{{< /tab >}}
{{< /tabs >}}
### Develop your app with the development container
Update your app on your host machine and see the changes reflected in the container.
1. In the `src/static/js/app.js` file, on line
109, change the "Add Item" button to simply say "Add":
```diff
- {submitting ? 'Adding...' : 'Add Item'}
+ {submitting ? 'Adding...' : 'Add'}
```
Save the file.
2. Refresh the page in your web browser, and you should see the change reflected
almost immediately because of the bind mount. Nodemon detects the change and
restarts the server. It might take a few seconds for the Node server to
restart. If you get an error, try refreshing after a few seconds.
![Screenshot of updated label for Add button](images/updated-add-button.webp)
3. Feel free to make any other changes you'd like to make. Each time you make a
change and save a file, the change is reflected in the container because of
the bind mount. When Nodemon detects a change, it restarts the app inside the
container automatically. When you're done, stop the container and build your
new image using:
```console
$ docker build -t getting-started .
```
## Summary
At this point, you can persist your database and see changes in your app as you develop without rebuilding the image.
In addition to volume mounts and bind mounts, Docker also supports other mount
types and storage drivers for handling more complex and specialized use cases.
Related information:
- [docker CLI reference](/reference/cli/docker/)
- [Manage data in Docker](https://docs.docker.com/storage/)
## Next steps
In order to prepare your app for production, you need to migrate your database
from working in SQLite to something that can scale a little better. For
simplicity, you'll keep using a relational database and switch your application
to use MySQL. But, how should you run MySQL? How do you allow the containers to
talk to each other? You'll learn about that in the next section.
{{< button text="Multi container apps" url="07_multi_container.md" >}}
@@ -1,331 +0,0 @@
---
title: Multi container apps
weight: 70
linkTitle: "Part 6: Multi-container apps"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Using more than one container in your application
aliases:
- /get-started/07_multi_container/
- /guides/workshop/07_multi_container/
---
Up to this point, you've been working with single container apps. But, now you will 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. The following are a few reasons to run the container separately:
- 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, like the following diagram, it's best to run your app in multiple containers.
![Todo App connected to MySQL container](images/multi-container.webp?w=350h=250)
## 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 you allow one container to talk to another? The answer is
networking. If you place the two containers on the same network, they can talk to each other.
## Start MySQL
There are two ways to put a container on a network:
- Assign the network when starting the container.
- Connect an already running container to a network.
In the following steps, you'll create the network first and then attach the MySQL container at startup.
1. Create the network.
```console
$ docker network create todo-app
```
2. Start a MySQL container and attach it to the network. You're also going to define a few environment variables that the
database will use to initialize the database. To learn more about the MySQL environment variables, see the "Environment Variables" section in the [MySQL Docker Hub listing](https://hub.docker.com/_/mysql/).
{{< tabs >}}
{{< tab name="Mac / Linux / Git Bash" >}}
```console
$ 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:8.0
```
{{< /tab >}}
{{< tab name="PowerShell" >}}
```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:8.0
```
{{< /tab >}}
{{< tab name="Command Prompt" >}}
```console
$ 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:8.0
```
{{< /tab >}}
{{< /tabs >}}
In the previous command, you can see the `--network-alias` flag. In a later section, you'll learn more about this flag.
> [!TIP]
>
> You'll notice a volume named `todo-mysql-data` in the above command that is mounted at `/var/lib/mysql`, which is where MySQL stores its data. However, you never ran a `docker volume create` command. Docker recognizes you want to use a named volume and creates one automatically for you.
3. To confirm you have the database up and running, connect to the database and verify that it connects.
```console
$ docker exec -it <mysql-container-id> mysql -u root -p
```
When the password prompt comes up, type in `secret`. In the MySQL shell, list the databases and verify
you see the `todos` database.
```console
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)
```
4. Exit the MySQL shell to return to the shell on your machine.
```console
mysql> exit
```
You now have a `todos` database and it's ready for you to use.
## Connect to MySQL
Now that you know MySQL is up and running, you can use it. But, how do you use it? If you run
another container on the same network, how do you find the container? Remember that each container has its own IP address.
To answer the questions above and better understand container networking, you'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.
```console
$ docker run -it --network todo-app nicolaka/netshoot
```
2. Inside the container, you're going to use the `dig` command, which is a useful DNS tool. You're going to look up
the IP address for the hostname `mysql`.
```console
$ dig mysql
```
You should get output like the following.
```text
; <<>> DiG 9.18.8 <<>> 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, you used the
`--network-alias` earlier.
What this means is that your app only simply needs to connect to a host named `mysql` and it'll talk to the
database.
## Run your 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
> [!NOTE]
>
> While using env vars to set connection settings is generally accepted for development, it's highly discouraged
> when running applications in production. Diogo Monica, a former lead of security at Docker,
> [wrote a fantastic blog post](https://blog.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.
You can now start your dev-ready container.
1. Specify each of the previous environment variables, as well as connect the container to your app network. Make sure that you are in the `getting-started-app` directory when you run this command.
{{< tabs >}}
{{< tab name="Mac / Linux" >}}
```console
$ docker run -dp 127.0.0.1:3000:3000 \
-w /app -v ".:/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:24-alpine \
sh -c "npm install && npm run dev"
```
{{< /tab >}}
{{< tab name="PowerShell" >}}
In Windows, run this command in PowerShell.
```powershell
$ docker run -dp 127.0.0.1:3000:3000 `
-w /app -v ".:/app" `
--network todo-app `
-e MYSQL_HOST=mysql `
-e MYSQL_USER=root `
-e MYSQL_PASSWORD=secret `
-e MYSQL_DB=todos `
node:24-alpine `
sh -c "npm install && npm run dev"
```
{{< /tab >}}
{{< tab name="Command Prompt" >}}
In Windows, run this command in Command Prompt.
```console
$ docker run -dp 127.0.0.1:3000:3000 ^
-w /app -v "%cd%:/app" ^
--network todo-app ^
-e MYSQL_HOST=mysql ^
-e MYSQL_USER=root ^
-e MYSQL_PASSWORD=secret ^
-e MYSQL_DB=todos ^
node:24-alpine ^
sh -c "npm install && npm run dev"
```
{{< /tab >}}
{{< tab name="Git Bash" >}}
```console
$ docker run -dp 127.0.0.1:3000:3000 \
-w //app -v "/.:/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:24-alpine \
sh -c "npm install && npm run dev"
```
{{< /tab >}}
{{< /tabs >}}
2. If you look at the logs for the container (`docker logs -f <container-id>`), you should see a message similar to the following, which indicates it's
using the mysql database.
```console
[nodemon] 3.1.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node src/index.js`
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
Listening on port 3000
```
3. Open the app in your browser and add a few items to your todo list.
4. Connect to the mysql database and prove that the items are being written to the database. Remember, the password
is `secret`.
```console
$ docker exec -it <mysql-container-id> mysql -p todos
```
And in the mysql shell, run the following:
```console
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 |
+--------------------------------------+--------------------+-----------+
```
Your table will look different because it has your items. But, you should see them stored there.
## Summary
At this point, you have an application that now stores its data in an external database running in a separate
container. You learned a little bit about container networking and service discovery using DNS.
Related information:
- [docker CLI reference](/reference/cli/docker/)
- [Networking overview](/manuals/engine/network/_index.md)
## Next steps
There's a good chance you are starting to feel a little overwhelmed with everything you need to do to start up
this application. You 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, you'll learn about Docker Compose. With Docker Compose, you can share your application stacks in a
much easier way and let others spin them up with a single, simple command.
{{< button text="Use Docker Compose" url="08_using_compose.md" >}}
@@ -1,292 +0,0 @@
---
title: Use Docker Compose
weight: 80
linkTitle: "Part 7: Use Docker Compose"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Using Docker Compose for multi-container applications
aliases:
- /get-started/08_using_compose/
- /guides/workshop/08_using_compose/
---
[Docker Compose](/manuals/compose/_index.md) is a tool that helps you define and
share multi-container applications. With Compose, you can create a YAML file to define the services
and with a single command, you 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 repository (it's now version controlled), and easily enable someone else to contribute to your project.
Someone would only need to clone your repository and start the app using Compose. In fact, you might see quite a few projects
on GitHub/GitLab doing exactly this now.
## Create the Compose file
In the `getting-started-app` directory, create a file named `compose.yaml`.
```text
├── getting-started-app/
│ ├── Dockerfile
│ ├── compose.yaml
│ ├── node_modules/
│ ├── package.json
│ ├── package-lock.json
│ ├── spec/
│ └── src/
```
## Define the app service
In [part 6](./07_multi_container.md), you used the following command to start the application service.
```console
$ docker run -dp 127.0.0.1:3000:3000 \
-w /app -v ".:/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:24-alpine \
sh -c "npm install && npm run dev"
```
You'll now define this service in the `compose.yaml` file.
1. Open `compose.yaml` in a text or code editor, and start by defining the name and image of the first service (or container) you want to run as part of your application.
The name will automatically become a network alias, which will be useful when defining your MySQL service.
```yaml
services:
app:
image: node:24-alpine
```
2. Typically, you will see `command` close to the `image` definition, although there is no requirement on ordering. Add the `command` to your `compose.yaml` file.
```yaml
services:
app:
image: node:24-alpine
command: sh -c "npm install && npm run dev"
```
3. Now migrate the `-p 127.0.0.1:3000:3000` part of the command by defining the `ports` for the service.
```yaml
services:
app:
image: node:24-alpine
command: sh -c "npm install && npm run dev"
ports:
- 127.0.0.1:3000:3000
```
4. Next, migrate both the working directory (`-w /app`) and the volume mapping
(`-v ".:/app"`) by using the `working_dir` and `volumes` definitions.
One advantage of Docker Compose volume definitions is you can use relative paths from the current directory.
```yaml
services:
app:
image: node:24-alpine
command: sh -c "npm install && npm run dev"
ports:
- 127.0.0.1:3000:3000
working_dir: /app
volumes:
- ./:/app
```
5. Finally, you need to migrate the environment variable definitions using the `environment` key.
```yaml
services:
app:
image: node:24-alpine
command: sh -c "npm install && npm run dev"
ports:
- 127.0.0.1:3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
```
### Define the MySQL service
Now, it's time to define the MySQL service. The command that you used for that container was the following:
```console
$ 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:8.0
```
1. First define the new service and name it `mysql` so it automatically gets the network alias. Also specify the image to use as well.
```yaml
services:
app:
# The app service definition
mysql:
image: mysql:8.0
```
2. Next, define the volume mapping. When you ran the container with `docker
run`, Docker created the named volume automatically. However, that doesn't
happen when running with Compose. You 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.
```yaml
services:
app:
# The app service definition
mysql:
image: mysql:8.0
volumes:
- todo-mysql-data:/var/lib/mysql
volumes:
todo-mysql-data:
```
3. Finally, you need to specify the environment variables.
```yaml
services:
app:
# The app service definition
mysql:
image: mysql:8.0
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
```
At this point, your complete `compose.yaml` should look like this:
```yaml
services:
app:
image: node:24-alpine
command: sh -c "npm install && npm run dev"
ports:
- 127.0.0.1:3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
mysql:
image: mysql:8.0
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
```
## Run the application stack
Now that you have your `compose.yaml` file, you can start your application.
1. Make sure no other copies of the containers are running first. Use `docker ps` to list the containers and `docker rm -f <ids>` to remove them.
2. Start up the application stack using the `docker compose up` command. Add the
`-d` flag to run everything in the background.
```console
$ docker compose up -d
```
When you run the previous command, you should see output like the following:
```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 Docker Compose created the volume as well as a network. By default, Docker Compose automatically creates a network specifically for the application stack (which is why you didn't define one in the Compose file).
3. 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 have run the command 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: '8.0.31' 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`).
4. At this point, you should be able to open your app in your browser on [http://localhost:3000](http://localhost:3000) and see it running.
## See the app stack in Docker Desktop Dashboard
If you look at the Docker Desktop Dashboard, you'll see that there is a group named **getting-started-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
`compose.yaml` was located in.
If you expand the stack, you'll see the two containers you defined in the Compose file. The names are also a little
more descriptive, as they follow the pattern of `<service-name>-<replica-number>`. So, it's very easy to
quickly see what container is your app and which container is the mysql database.
## Tear 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 Desktop Dashboard
for the entire app. The containers will stop and the network will be removed.
> [!WARNING]
>
> By default, named volumes in your compose file are not removed when you run `docker compose down`. If you want to
>remove the volumes, you need to add the `--volumes` flag.
>
> The Docker Desktop Dashboard does not remove volumes when you delete the app stack.
## Summary
In this section, you learned about Docker Compose and how it helps you simplify
the way you define and share multi-service applications.
Related information:
- [Compose overview](/manuals/compose/_index.md)
- [Compose file reference](/reference/compose-file/_index.md)
- [Compose CLI reference](/reference/cli/docker/compose/)
## Next steps
Next, you'll learn about a few best practices you can use to improve your Dockerfile.
{{< button text="Image-building best practices" url="09_image_best.md" >}}
@@ -1,217 +0,0 @@
---
title: Image-building best practices
weight: 90
linkTitle: "Part 8: Image-building best practices"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop
description: Tips for building images for your application
aliases:
- /get-started/09_image_best/
- /guides/workshop/09_image_best/
---
## Image layering
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.
```console
$ docker image history getting-started
```
You should get output that looks something like the following.
```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 npm install --omit=dev 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 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.
2. You'll notice that several of the lines are truncated. If you add the `--no-trunc` flag, you'll get the
full output.
```console
$ 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.
Look at the following Dockerfile you created for the getting started app.
```dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install --omit=dev
CMD ["node", "src/index.js"]
EXPOSE 3000
```
Going back to the image history output, you see that each command in the Dockerfile becomes a new layer in the image.
You might remember that when you made a change to the image, the dependencies had to be reinstalled. It doesn't make much sense to ship around the same dependencies every time you build.
To fix it, you need to restructure your Dockerfile to help support the caching
of the dependencies. For Node-based applications, those dependencies are defined
in the `package.json` file. You can copy only that file in first, install the
dependencies, and then copy in everything else. Then, you only recreate the
dependencies if there was a change to the `package.json`.
1. Update the Dockerfile to copy in the `package.json` first, install dependencies, and then copy everything else in.
```dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install --omit=dev
COPY . .
CMD ["node", "src/index.js"]
```
2. Build a new image using `docker build`.
```console
$ docker build -t getting-started .
```
You should see output like the following.
```plaintext
[+] Building 16.1s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 175B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/node:24-alpine
=> [internal] load build context
=> => transferring context: 53.37MB
=> [1/5] FROM docker.io/library/node:24-alpine
=> CACHED [2/5] WORKDIR /app
=> [3/5] COPY package.json package-lock.json ./
=> [4/5] RUN npm install --omit=dev
=> [5/5] COPY . .
=> exporting to image
=> => exporting layers
=> => writing image sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25
=> => naming to docker.io/library/getting-started
```
3. Now, make a change to the `src/static/index.html` file. For example, change the `<title>` to "The Awesome Todo App".
4. Build the Docker image now using `docker build -t getting-started .` again. This time, your output should look a little different.
```plaintext
[+] Building 1.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/node:24-alpine
=> [internal] load build context
=> => transferring context: 450.43kB
=> [1/5] FROM docker.io/library/node:24-alpine
=> CACHED [2/5] WORKDIR /app
=> CACHED [3/5] COPY package.json package-lock.json ./
=> CACHED [4/5] RUN npm install
=> [5/5] COPY . .
=> exporting to image
=> => exporting layers
=> => writing image sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda
=> => naming to docker.io/library/getting-started
```
First off, you should notice that the build was much faster. And, you'll see
that several steps are using previously cached layers. Pushing and pulling
this image and updates to it will be much faster as well.
## Multi-stage builds
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, you need a JDK 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 your final image. Multi-stage builds help.
```dockerfile
# syntax=docker/dockerfile:1
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, you use one stage (called `build`) to perform the actual Java build using Maven. In the second
stage (starting at `FROM tomcat`), you 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, you need a Node environment to compile the JS code (typically JSX), SASS stylesheets,
and more into static HTML, JS, and CSS. If you aren't doing server-side rendering, you don't even need a Node environment
for your production build. You can ship the static resources in a static nginx container.
```dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package* ./
RUN npm install
COPY public ./public
COPY src ./src
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
```
In the previous Dockerfile example, it uses the `node:24-alpine` image to perform the build (maximizing layer caching) and then copies the output
into an nginx container.
> [!Tips]
> This React example is for illustration purposes. The getting-started todo app is a `Node.js` backend application, not a React frontend.
## Summary
In this section, you learned a few image building best practices, including layer caching and multi-stage builds.
Related information:
- [Dockerfile reference](/reference/dockerfile/)
- [Dockerfile best practices](/manuals/build/building/best-practices.md)
## Next steps
In the next section, you'll learn about additional resources you can use to continue learning about containers.
{{< button text="What next" url="10_what_next.md" >}}
@@ -1,80 +0,0 @@
---
title: What next after the Docker workshop
weight: 100
linkTitle: "Part 9: What next"
keywords: get started, setup, orientation, quickstart, intro, concepts, containers,
docker desktop, AI, model runner, MCP, agents, hardened images, security
description: Explore what to do next after completing the Docker workshop, including securing your images, AI development, and language-specific guides.
aliases:
- /get-started/11_what_next/
- /guides/workshop/10_what_next/
summary: |
Now that you've completed the Docker workshop, you're ready to explore
securing your images with Docker Hardened Images, building AI-powered
applications, and diving into language-specific guides.
notoc: true
secure-images:
- title: What are Docker Hardened Images?
description: Understand secure, minimal, production-ready base images with near-zero CVEs.
link: /dhi/explore/what/
- title: Get started with DHI
description: Pull and run your first Docker Hardened Image in minutes.
link: /dhi/get-started/
- title: Use hardened images
description: Learn how to use DHI in your Dockerfiles and CI/CD pipelines.
link: /dhi/how-to/use/
- title: Explore the DHI catalog
description: Browse available hardened images, variants, and security attestations.
link: /dhi/how-to/explore/
ai-development:
- title: Docker Model Runner
description: Run and manage AI models locally using familiar Docker commands with OpenAI-compatible APIs.
link: /ai/model-runner/
- title: MCP Toolkit
description: Set up, manage, and run containerized MCP servers to power your AI agents.
link: /ai/mcp-catalog-and-toolkit/toolkit/
- title: Build AI agents with Docker Agent
description: Create teams of specialized AI agents that collaborate to solve complex problems.
link: /ai/docker-agent/
- title: Use AI models in Compose
description: Define AI model dependencies in your Docker Compose applications.
link: /ai/compose/models-and-compose/
language-guides:
- title: Node.js
description: Learn how to containerize and develop Node.js applications.
link: /guides/language/nodejs/
- title: Python
description: Build and run Python applications in containers.
link: /guides/language/python/
- title: Java
description: Containerize Java applications with best practices.
link: /guides/language/java/
- title: Go
description: Develop and deploy Go applications using Docker.
link: /guides/language/golang/
---
Congratulations on completing the Docker workshop. You've learned how to containerize applications, work with multi-container setups, use Docker Compose, and apply image-building best practices.
Here's what to explore next.
## Secure your images
Take your image-building skills to the next level with Docker Hardened Images—secure, minimal, and production-ready base images that are now free for everyone.
{{< grid items="secure-images" >}}
## Build with AI
Docker makes it easy to run AI models locally and build agentic AI applications. Explore Docker's AI tools and start building AI-powered apps.
{{< grid items="ai-development" >}}
## Language-specific guides
Apply what you've learned to your preferred programming language with hands-on tutorials.
{{< grid items="language-guides" >}}
-49
View File
@@ -1,49 +0,0 @@
---
title: Overview of the Docker workshop
linkTitle: Docker workshop
keywords: docker basics, how to start a docker container, container settings, setup
docker, how to setup docker, setting up docker, docker container guide, how to get
started with docker
description: Get started with the Docker basics in this workshop, You'll
learn about containers, images, and how to containerize your first application.
aliases:
- /guides/get-started/
- /get-started/hands-on-overview/
- /guides/workshop/
---
This 45-minute workshop contains step-by-step instructions on how to get started with Docker. This workshop shows you how to:
- Build and run an image as a container.
- Share images using Docker Hub.
- Deploy Docker applications using multiple containers with a database.
- Run applications using Docker Compose.
> [!NOTE]
>
> For a quick introduction to Docker and the benefits of containerizing your
> applications, see [Getting started](/get-started/introduction/_index.md).
## What is a container?
A container is a sandboxed process running on a host machine that is isolated from all other processes running on that 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 makes these capabilities approachable and easy to use. To summarize, a container:
- Is a runnable instance of an image. You can create, start, stop, move, or delete a container using the Docker API or CLI.
- Can be run on local machines, virtual machines, or deployed to the cloud.
- Is portable (and can be run on any OS).
- Is isolated from other containers and runs its own software, binaries, configurations, etc.
If you're familiar with `chroot`, then think of a container as an extended version of `chroot`. The filesystem comes from the image. However, a container adds additional isolation not available when using chroot.
## What is an image?
A running container uses an isolated filesystem. This isolated filesystem is provided by an image, and the image must contain everything needed to run an application - all dependencies, configurations, scripts, binaries, etc. The image also contains other configurations for the container, such as environment variables, a default command to run, and other metadata.
## Next steps
In this section, you learned about containers and images.
Next, you'll containerize a simple application and get hands-on with the concepts.
{{< button text="Containerize an application" url="02_our_app.md" >}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

-142
View File
@@ -1,142 +0,0 @@
---
title: Deploy to Kubernetes
keywords: kubernetes, pods, deployments, kubernetes services
description: Learn how to describe and deploy a simple application on Kubernetes.
aliases:
- /get-started/kube-deploy/
- /guides/deployment-orchestration/kube-deploy/
summary: |
Learn how to deploy and orchestrate Docker containers using Kubernetes.
params:
tags: [deployment]
time: 10 minutes
---
## Prerequisites
- Download and install Docker Desktop as described in [Get Docker](/get-started/get-docker.md).
- Work through containerizing an application in [Part 2](02_our_app.md).
- Make sure that Kubernetes is turned on in Docker Desktop:
If Kubernetes isn't running, follow the instructions in [Orchestration](orchestration.md) to finish setting it up.
## Introduction
Now that you've demonstrated that the individual components of your application run as stand-alone containers, it's time to arrange for them to be managed by an orchestrator like Kubernetes. Kubernetes provides many tools for scaling, networking, securing and maintaining your containerized applications, above and beyond the abilities of containers themselves.
In order to validate that your containerized application works well on Kubernetes, you'll use Docker Desktop's built in Kubernetes environment right on your development machine to deploy your application, before handing it off to run on a full Kubernetes cluster in production. The Kubernetes environment created by Docker Desktop is _fully featured_, meaning it has all the Kubernetes features your app will enjoy on a real cluster, accessible from the convenience of your development machine.
## Describing apps using Kubernetes YAML
All containers in Kubernetes are scheduled as pods, which are groups of co-located containers that share some resources. Furthermore, in a realistic application you almost never create individual pods. Instead, most of your workloads are scheduled as deployments, which are scalable groups of pods maintained automatically by Kubernetes. Lastly, all Kubernetes objects can and should be described in manifests called Kubernetes YAML files. These YAML files describe all the components and configurations of your Kubernetes app, and can be used to create and destroy your app in any Kubernetes environment.
You already wrote a basic Kubernetes YAML file in the Orchestration overview part of this tutorial. Now, you can write a slightly more sophisticated YAML file to run and manage your Todo app, the container `getting-started` image created in [Part 2](02_our_app.md) of the Quickstart tutorial. Place the following in a file called `bb.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: bb-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
bb: web
template:
metadata:
labels:
bb: web
spec:
containers:
- name: bb-site
image: getting-started
imagePullPolicy: Never
---
apiVersion: v1
kind: Service
metadata:
name: bb-entrypoint
namespace: default
spec:
type: NodePort
selector:
bb: web
ports:
- port: 3000
targetPort: 3000
nodePort: 30001
```
In this Kubernetes YAML file, there are two objects, separated by the `---`:
- A `Deployment`, describing a scalable group of identical pods. In this case, you'll get just one `replica`, or copy of your pod, and that pod (which is described under the `template:` key) has just one container in it, based off of your `getting-started` image from the previous step in this tutorial.
- A `NodePort` service, which will route traffic from port 30001 on your host to port 3000 inside the pods it routes to, allowing you to reach your Todo app from the network.
Also, notice that while Kubernetes YAML can appear long and complicated at first, it almost always follows the same pattern:
- The `apiVersion`, which indicates the Kubernetes API that parses this object
- The `kind` indicating what sort of object this is
- Some `metadata` applying things like names to your objects
- The `spec` specifying all the parameters and configurations of your object.
## Deploy and check your application
1. In a terminal, navigate to where you created `bb.yaml` and deploy your application to Kubernetes:
```console
$ kubectl apply -f bb.yaml
```
You should see output that looks like the following, indicating your Kubernetes objects were created successfully:
```shell
deployment.apps/bb-demo created
service/bb-entrypoint created
```
2. Make sure everything worked by listing your deployments:
```console
$ kubectl get deployments
```
if all is well, your deployment should be listed as follows:
```shell
NAME READY UP-TO-DATE AVAILABLE AGE
bb-demo 1/1 1 1 40s
```
This indicates all one of the pods you asked for in your YAML are up and running. Do the same check for your services:
```console
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
bb-entrypoint NodePort 10.106.145.116 <none> 3000:30001/TCP 53s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 138d
```
In addition to the default `kubernetes` service, we see our `bb-entrypoint` service, accepting traffic on port 30001/TCP.
3. Open a browser and visit your Todo app at `localhost:30001`. You should see your Todo application, the same as when you ran it as a stand-alone container in [Part 2](02_our_app.md) of the tutorial.
4. Once satisfied, tear down your application:
```console
$ kubectl delete -f bb.yaml
```
## Conclusion
At this point, you have successfully used Docker Desktop to deploy your application to a fully-featured Kubernetes environment on your development machine. You can now add other components to your app and taking advantage of all the features and power of Kubernetes, right on your own machine.
In addition to deploying to Kubernetes, you have also described your application as a Kubernetes YAML file. This simple text file contains everything you need to create your application in a running state. You can check it in to version control and share it with your colleagues. This let you distribute your applications to other clusters (like the testing and production clusters that probably come after your development environments).
## Kubernetes references
Further documentation for all new Kubernetes objects used in this article are available here:
- [Kubernetes Pods](https://kubernetes.io/docs/concepts/workloads/pods/pod/)
- [Kubernetes Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
- [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/)
-323
View File
@@ -1,323 +0,0 @@
---
title: Deployment and orchestration
keywords: orchestration, deploy, kubernetes, swarm,
description: Get oriented on some basics of Docker and install Docker Desktop.
aliases:
- /get-started/orchestration/
- /guides/deployment-orchestration/orchestration/
summary: |
Explore the essentials of container orchestration with Docker.
params:
tags: [deployment]
time: 10 minutes
---
Containerization provides an opportunity to move and scale applications to
clouds and data centers. Containers effectively guarantee that those applications run the
same way anywhere, allowing you to quickly and easily take advantage of all
these environments. Additionally, as you scale your applications up, you need some
tooling to help automate the maintenance of those applications, enable the
replacement of failed containers automatically, and manage the roll-out of
updates and reconfigurations of those containers during their lifecycle.
Tools to manage, scale, and maintain containerized applications are called
orchestrators. Two of the most popular orchestration tools are Kubernetes and
Docker Swarm. Docker Desktop provides development environments for both of these
orchestrators.
The advanced modules teach you how to:
1. [Set up and use a Kubernetes environment on your development machine](kube-deploy.md)
2. [Set up and use a Swarm environment on your development machine](swarm-deploy.md)
## Turn on Kubernetes
Docker Desktop sets up Kubernetes for you quickly and easily. Follow the setup and validation instructions appropriate for your operating system:
{{< tabs group="os" >}}
{{< tab name="Mac and Linux" >}}
### Mac
1. From the Docker Dashboard, navigate to **Settings**, and select the **Kubernetes** tab.
2. Select the checkbox labeled **Enable Kubernetes**, and select **Apply**. Docker Desktop automatically sets up Kubernetes for you. You'll know that Kubernetes has been successfully enabled when you see a green light beside 'Kubernetes _running_' in **Settings**.
3. To confirm that Kubernetes is up and running, create a text file called `pod.yaml` with the following content:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: testpod
image: alpine:latest
command: ["ping", "8.8.8.8"]
```
This describes a pod with a single container, isolating a simple ping to 8.8.8.8.
4. In a terminal, navigate to where you created `pod.yaml` and create your pod:
```console
$ kubectl apply -f pod.yaml
```
5. Check that your pod is up and running:
```console
$ kubectl get pods
```
You should see something like:
```shell
NAME READY STATUS RESTARTS AGE
demo 1/1 Running 0 4s
```
6. Check that you get the logs you'd expect for a ping process:
```console
$ kubectl logs demo
```
You should see the output of a healthy ping process:
```shell
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=37 time=21.393 ms
64 bytes from 8.8.8.8: seq=1 ttl=37 time=15.320 ms
64 bytes from 8.8.8.8: seq=2 ttl=37 time=11.111 ms
...
```
7. Finally, tear down your test pod:
```console
$ kubectl delete -f pod.yaml
```
{{< /tab >}}
{{< tab name="Windows" >}}
### Windows
1. From the Docker Dashboard, navigate to **Settings**, and select the **Kubernetes** tab.
2. Select the checkbox labeled **Enable Kubernetes**, and select **Apply**. Docker Desktop automatically sets up Kubernetes for you. You'll know that Kubernetes has been successfully enabled when you see a green light beside 'Kubernetes _running_' in the **Settings** menu.
3. To confirm that Kubernetes is up and running, create a text file called `pod.yaml` with the following content:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: testpod
image: alpine:latest
command: ["ping", "8.8.8.8"]
```
This describes a pod with a single container, isolating a simple ping to 8.8.8.8.
4. In PowerShell, navigate to where you created `pod.yaml` and create your pod:
```console
$ kubectl apply -f pod.yaml
```
5. Check that your pod is up and running:
```console
$ kubectl get pods
```
You should see something like:
```shell
NAME READY STATUS RESTARTS AGE
demo 1/1 Running 0 4s
```
6. Check that you get the logs you'd expect for a ping process:
```console
$ kubectl logs demo
```
You should see the output of a healthy ping process:
```shell
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=37 time=21.393 ms
64 bytes from 8.8.8.8: seq=1 ttl=37 time=15.320 ms
64 bytes from 8.8.8.8: seq=2 ttl=37 time=11.111 ms
...
```
7. Finally, tear down your test pod:
```console
$ kubectl delete -f pod.yaml
```
{{< /tab >}}
{{< /tabs >}}
## Enable Docker Swarm
Docker Desktop runs primarily on Docker Engine, which has everything you need to run a Swarm built in. Follow the setup and validation instructions appropriate for your operating system:
{{< tabs group="os" >}}
{{< tab name="Mac" >}}
### Mac
1. Open a terminal, and initialize Docker Swarm mode:
```console
$ docker swarm init
```
If all goes well, you should see a message similar to the following:
```shell
Swarm initialized: current node (tjjggogqpnpj2phbfbz8jd5oq) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3e0hh0jd5t4yjg209f4g5qpowbsczfahv2dea9a1ay2l8787cf-2h4ly330d0j917ocvzw30j5x9 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```
2. Run a simple Docker service that uses an alpine-based filesystem, and isolates a ping to 8.8.8.8:
```console
$ docker service create --name demo alpine:latest ping 8.8.8.8
```
3. Check that your service created one running container:
```console
$ docker service ps demo
```
You should see something like:
```shell
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
463j2s3y4b5o demo.1 alpine:latest docker-desktop Running Running 8 seconds ago
```
4. Check that you get the logs you'd expect for a ping process:
```console
$ docker service logs demo
```
You should see the output of a healthy ping process:
```shell
demo.1.463j2s3y4b5o@docker-desktop | PING 8.8.8.8 (8.8.8.8): 56 data bytes
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=0 ttl=37 time=13.005 ms
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=1 ttl=37 time=13.847 ms
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=2 ttl=37 time=41.296 ms
...
```
5. Finally, tear down your test service:
```console
$ docker service rm demo
```
{{< /tab >}}
{{< tab name="Windows" >}}
### Windows
1. Open a PowerShell, and initialize Docker Swarm mode:
```console
$ docker swarm init
```
If all goes well, you should see a message similar to the following:
```shell
Swarm initialized: current node (tjjggogqpnpj2phbfbz8jd5oq) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3e0hh0jd5t4yjg209f4g5qpowbsczfahv2dea9a1ay2l8787cf-2h4ly330d0j917ocvzw30j5x9 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```
2. Run a simple Docker service that uses an alpine-based filesystem, and isolates a ping to 8.8.8.8:
```console
$ docker service create --name demo alpine:latest ping 8.8.8.8
```
3. Check that your service created one running container:
```console
$ docker service ps demo
```
You should see something like:
```shell
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
463j2s3y4b5o demo.1 alpine:latest docker-desktop Running Running 8 seconds ago
```
4. Check that you get the logs you'd expect for a ping process:
```console
$ docker service logs demo
```
You should see the output of a healthy ping process:
```shell
demo.1.463j2s3y4b5o@docker-desktop | PING 8.8.8.8 (8.8.8.8): 56 data bytes
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=0 ttl=37 time=13.005 ms
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=1 ttl=37 time=13.847 ms
demo.1.463j2s3y4b5o@docker-desktop | 64 bytes from 8.8.8.8: seq=2 ttl=37 time=41.296 ms
...
```
5. Finally, tear down your test service:
```console
$ docker service rm demo
```
{{< /tab >}}
{{< /tabs >}}
## Conclusion
At this point, you've confirmed that you can run simple containerized workloads in Kubernetes and Swarm. The next step is to write a YAML file that describes how to run and manage these containers.
- [Deploy to Kubernetes](kube-deploy.md)
- [Deploy to Swarm](swarm-deploy.md)
## CLI references
Further documentation for all CLI commands used in this article are available here:
- [`kubectl apply`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply)
- [`kubectl get`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get)
- [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs)
- [`kubectl delete`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#delete)
- [`docker swarm init`](/reference/cli/docker/swarm/init/)
- [`docker service *`](/reference/cli/docker/service/)
-116
View File
@@ -1,116 +0,0 @@
---
title: Deploy to Swarm
keywords: swarm, swarm services, stacks
description: Learn how to describe and deploy a simple application on Docker Swarm.
aliases:
- /get-started/part4/
- /get-started/swarm-deploy/
- /guides/deployment-orchestration/swarm-deploy/
summary: |
Discover how to deploy and manage Docker containers using Docker Swarm.
params:
tags: [deployment]
time: 10 minutes
---
{{% include "swarm-mode.md" %}}
## Prerequisites
- Download and install Docker Desktop as described in [Get Docker](/get-started/get-docker.md).
- Work through containerizing an application in [Docker workshop part 2](/get-started/workshop/02_our_app.md)
- Make sure that Swarm is enabled on your Docker Desktop by typing `docker system info`, and looking for a message `Swarm: active` (you might have to scroll up a little).
If Swarm isn't running, simply type `docker swarm init` in a shell prompt to set it up.
## Introduction
Now that you've demonstrated that the individual components of your application run as stand-alone containers and shown how to deploy it using Kubernetes, you can look at how to arrange for them to be managed by Docker Swarm. Swarm provides many tools for scaling, networking, securing and maintaining your containerized applications, above and beyond the abilities of containers themselves.
In order to validate that your containerized application works well on Swarm, you'll use Docker Desktop's built in Swarm environment right on your development machine to deploy your application, before handing it off to run on a full Swarm cluster in production. The Swarm environment created by Docker Desktop is fully featured, meaning it has all the Swarm features your app will enjoy on a real cluster, accessible from the convenience of your development machine.
## Describe apps using stack files
Swarm never creates individual containers like you did in the previous step of this tutorial. Instead, all Swarm workloads are scheduled as services, which are scalable groups of containers with added networking features maintained automatically by Swarm. Furthermore, all Swarm objects can and should be described in manifests called stack files. These YAML files describe all the components and configurations of your Swarm app, and can be used to create and destroy your app in any Swarm environment.
Now you can write a simple stack file to run and manage your Todo app, the container `getting-started` image created in [Part 2](02_our_app.md) of the tutorial. Place the following in a file called `bb-stack.yaml`:
{{% include "swarm-compose-compat.md" %}}
```yaml
version: "3.7"
services:
bb-app:
image: getting-started
ports:
- "8000:3000"
```
In this Swarm YAML file, there is one object, a `service`, describing a scalable group of identical containers. In this case, you'll get just one container (the default), and that container will be based on your `getting-started` image created in [Part 2](02_our_app.md) of the tutorial. In addition, you've asked Swarm to forward all traffic arriving at port 8000 on your development machine to port 3000 inside our getting-started container.
> **Kubernetes Services and Swarm Services are very different**
>
> Despite the similar name, the two orchestrators mean very different things by
> the term 'service'. In Swarm, a service provides both scheduling and
> networking facilities, creating containers and providing tools for routing
> traffic to them. In Kubernetes, scheduling and networking are handled
> separately, deployments (or other controllers) handle the scheduling of
> containers as pods, while services are responsible only for adding
> networking features to those pods.
## Deploy and check your application
1. Deploy your application to Swarm:
```console
$ docker stack deploy -c bb-stack.yaml demo
```
If all goes well, Swarm will report creating all your stack objects with no complaints:
```shell
Creating network demo_default
Creating service demo_bb-app
```
Notice that in addition to your service, Swarm also creates a Docker network by default to isolate the containers deployed as part of your stack.
2. Make sure everything worked by listing your service:
```console
$ docker service ls
```
If all has gone well, your service will report with 1/1 of its replicas created:
```shell
ID NAME MODE REPLICAS IMAGE PORTS
il7elwunymbs demo_bb-app replicated 1/1 getting-started:latest *:8000->3000/tcp
```
This indicates 1/1 containers you asked for as part of your services are up and running. Also, you see that port 8000 on your development machine is getting forwarded to port 3000 in your getting-started container.
3. Open a browser and visit your Todo app at `localhost:8000`; you should see your Todo application, the same as when you ran it as a stand-alone container in [Part 2](02_our_app.md) of the tutorial.
4. Once satisfied, tear down your application:
```console
$ docker stack rm demo
```
## Conclusion
At this point, you've successfully used Docker Desktop to deploy your application to a fully-featured Swarm environment on your development machine. You can now add other components to your app and taking advantage of all the features and power of Swarm, right on your own machine.
In addition to deploying to Swarm, you've also described your application as a stack file. This simple text file contains everything you need to create your application in a running state; you can check it in to version control and share it with your colleagues, letting you to distribute your applications to other clusters (like the testing and production clusters that probably come after your development environments).
## Swarm and CLI references
Further documentation for all new Swarm objects and CLI commands used in this article are available here:
- [Swarm Mode](/manuals/engine/swarm/_index.md)
- [Swarm Mode Services](/manuals/engine/swarm/how-swarm-mode-works/services.md)
- [Swarm Stacks](/manuals/engine/swarm/stack-deploy.md)
- [`docker stack *`](/reference/cli/docker/stack/)
- [`docker service *`](/reference/cli/docker/service/)
@@ -50,7 +50,7 @@ To install Docker Desktop successfully, you must meet the [general system requir
## Next steps
- Explore [Docker's subscriptions](https://www.docker.com/pricing?ref=Docs&refAction=DocsDesktopArchlinuxInstall) to see what Docker can offer you.
- Take a look at the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Take a look at [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
- [Explore Docker Desktop](/manuals/desktop/use-desktop/_index.md) and all its features.
- [Troubleshooting](/manuals/desktop/troubleshoot-and-support/troubleshoot/_index.md) describes common problems, workarounds, how to run and submit diagnostics, and submit issues.
- [FAQs](/manuals/desktop/troubleshoot-and-support/faqs/general.md) provide answers to frequently asked questions.
@@ -84,7 +84,7 @@ $ sudo apt-get install ./docker-desktop-amd64.deb
## Next steps
- Explore [Docker's subscriptions](https://www.docker.com/pricing?ref=Docs&refAction=DocsDesktopDebianInstall) to see what Docker can offer you.
- Take a look at the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Take a look at [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
- [Explore Docker Desktop](/manuals/desktop/use-desktop/_index.md) and all its features.
- [Troubleshooting](/manuals/desktop/troubleshoot-and-support/troubleshoot/_index.md) describes common problems, workarounds, how to run and submit diagnostics, and submit issues.
- [FAQs](/manuals/desktop/troubleshoot-and-support/faqs/general.md) provide answers to frequently asked questions.
@@ -74,7 +74,7 @@ $ sudo dnf install ./docker-desktop-x86_64.rpm
## Next steps
- Explore [Docker's subscriptions](https://www.docker.com/pricing?ref=Docs&refAction=DocsDesktopFedoraInstall) to see what Docker can offer you.
- Take a look at the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Take a look at [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
- [Explore Docker Desktop](/manuals/desktop/use-desktop/_index.md) and all its features.
- [Troubleshooting](/manuals/desktop/troubleshoot-and-support/troubleshoot/_index.md) describes common problems, workarounds, how to run and submit diagnostics, and submit issues.
- [FAQs](/manuals/desktop/troubleshoot-and-support/faqs/general.md) provide answers to frequently asked questions.
@@ -127,7 +127,7 @@ $ sudo dnf install ./docker-desktop-<arch>-rhel.rpm
## Next steps
- Review [Docker's subscriptions](https://www.docker.com/pricing?ref=Docs&refAction=DocsDesktopRhelInstall) to see what Docker can offer you.
- Take a look at the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Take a look at [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
- [Explore Docker Desktop](/manuals/desktop/use-desktop/_index.md) and all its features.
- [Troubleshooting](/manuals/desktop/troubleshoot-and-support/troubleshoot/_index.md) describes common problems, workarounds, how to run and submit diagnostics, and submit issues.
- [FAQs](/manuals/desktop/troubleshoot-and-support/faqs/general.md) provide answers to frequently asked questions.
@@ -88,7 +88,7 @@ $ sudo apt install ./docker-desktop-amd64.deb
## Next steps
- Review [Docker's subscriptions](https://www.docker.com/pricing?ref=Docs&refAction=DocsDesktopUbuntuInstall) to see what Docker can offer you.
- Follow the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Follow [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
- [Explore Docker Desktop](/manuals/desktop/use-desktop/_index.md) and all its features.
- [Troubleshooting](/manuals/desktop/troubleshoot-and-support/troubleshoot/_index.md) describes common problems, workarounds, how to run and submit diagnostics, and submit issues.
- [FAQs](/manuals/desktop/troubleshoot-and-support/faqs/general.md) provide answers to frequently asked questions.
@@ -141,4 +141,4 @@ options:
## Next steps
- Take a look at the [Docker workshop](/get-started/workshop/_index.md) to learn how to build an image and run it as a containerized application.
- Take a look at [Get started with Docker](/get-started/introduction/_index.md) to learn how to build an image and run it as a containerized application.
@@ -25,16 +25,18 @@ If you are brand new to Docker, see [About Docker Engine](../../_index.md).
To run this tutorial, you need:
* [Three Linux hosts which can communicate over a network, with Docker installed](#three-networked-host-machines)
* [The IP address of the manager machine](#the-ip-address-of-the-manager-machine)
* [Open ports between the hosts](#open-protocols-and-ports-between-the-hosts)
- [Set up](#set-up)
- [Three networked host machines](#three-networked-host-machines)
- [Install Docker Engine on Linux machines](#install-docker-engine-on-linux-machines)
- [The IP address of the manager machine](#the-ip-address-of-the-manager-machine)
- [Open protocols and ports between the hosts](#open-protocols-and-ports-between-the-hosts)
- [Next steps](#next-steps)
### Three networked host machines
This tutorial requires three Linux hosts which have Docker installed and can
communicate over a network. These can be physical machines, virtual machines,
Amazon EC2 instances, or hosted in some other way. Check out
[Deploy to Swarm](/guides/swarm-deploy.md#prerequisites) for one possible set-up for the hosts.
Amazon EC2 instances, or hosted in some other way.
One of these machines is a manager (called `manager1`) and two of them are
workers (`worker1` and `worker2`).