├── exercises
├── dockerization
│ └── 1-rails_postgres
│ │ └── README.md
├── basic
│ ├── 5-volumes
│ │ ├── index.html
│ │ └── README.md
│ ├── 3-building_images
│ │ ├── Dockerfile
│ │ └── README.md
│ ├── 4-sharing_images
│ │ └── README.md
│ ├── 6-networking
│ │ └── README.md
│ ├── 2-changing_images
│ │ └── README.md
│ └── 1-running_containers
│ │ └── README.md
└── compose
│ └── 1-basics
│ └── README.md
└── README.md
/exercises/dockerization/1-rails_postgres/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/exercises/basic/5-volumes/index.html:
--------------------------------------------------------------------------------
1 |
It works in Docker!
2 |
--------------------------------------------------------------------------------
/exercises/basic/3-building_images/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 | LABEL author="David Elner"
3 |
4 | ENV PING_TARGET "google.com"
5 |
6 | RUN apt-get update \
7 | && apt-get install -y iputils-ping \
8 | && apt-get clean \
9 | && cd /var/lib/apt/lists && rm -fr *Release* *Sources* *Packages* \
10 | && truncate -s 0 /var/log/*log
11 |
12 | CMD ["sh", "-c", "ping $PING_TARGET"]
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker Training
2 |
3 | An introduction to the basics of using Docker.
4 |
5 | ### Before you get started...
6 |
7 | 1. [Install Docker on your machine.](https://docs.docker.com/engine/installation/)
8 |
9 | 2. [Clone this repo locally.](https://github.com/delner/docker-training)
10 |
11 | ### After you're setup...
12 |
13 | Follow the [presentation](https://1drv.ms/p/s!AoPSUV0rpt0Ajz1yfx_QLRBjmeqi), and open up each exercise when prompted.
14 |
15 | ### Basic Exercises
16 |
17 | 1. [Running and managing Docker containers and images](https://github.com/delner/docker-training/blob/master/exercises/basic/1-running_containers/README.md)
18 | 2. [Changing images](https://github.com/delner/docker-training/blob/master/exercises/basic/2-changing_images/README.md)
19 | 3. [Building your own images](https://github.com/delner/docker-training/blob/master/exercises/basic/3-building_images/README.md)
20 | 4. [Sharing images](https://github.com/delner/docker-training/blob/master/exercises/basic/4-sharing_images/README.md)
21 | 5. [Volumes](https://github.com/delner/docker-training/blob/master/exercises/basic/5-volumes/README.md)
22 | 6. [Networking](https://github.com/delner/docker-training/blob/master/exercises/basic/6-networking/README.md)
23 |
24 | ### Docker Compose
25 |
26 | 1. Basics
27 |
28 | ### Dockerization Exercises
29 |
30 | 1. Rails + Database application
--------------------------------------------------------------------------------
/exercises/basic/4-sharing_images/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 4: Sharing images
2 |
3 | In this exercise, we'll learn how to share Docker images using DockerHub. DockerHub is GitHub for Docker: a great place to find community images, and upload your own to.
4 |
5 | We'll need our `ping` image from the "Building Images" exercise, so be sure to complete that exercise first so you have an image to share.
6 |
7 | ### Getting Started
8 |
9 | To share images on DockerHub, you'll need a DockerHub account. You can sign up [here](https://hub.docker.com/).
10 |
11 | Most of the features on DockerHub should be pretty similar to GitHub: there's a search function for finding new images, repositories for your images, and organizations.
12 |
13 | The `docker` CLI tool also has integration with DockerHub. In order to use certain features, you'll need to login first:
14 |
15 | ```
16 | $ docker login
17 | Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
18 | Username: delner
19 | Password:
20 | Login Succeeded
21 | $
22 | ```
23 |
24 | ### Finding images
25 |
26 | Use the `docker search` command to search for new images:
27 |
28 | ```
29 | $ docker search kafka
30 | NAME DESCRIPTION STARS OFFICIAL AUTOMATED
31 | wurstmeister/kafka Multi-Broker Apache Kafka Image 319 [OK]
32 | spotify/kafka A simple docker image with both Kafka and ... 200 [OK]
33 | ches/kafka Apache Kafka. Tagged versions. JMX. Cluste... 70 [OK]
34 | sheepkiller/kafka-manager kafka-manager 61 [OK]
35 | $
36 | ```
37 |
38 | Once you found an image you like, you can pull it locally with `docker pull`, covered in the "Running Containers" exercise.
39 |
40 | ### Tagging images
41 |
42 | In the previous exercise "Building Images", we built and tagged an image as `/ping`. If you mistagged this image with something other than your DockerHub user name, it's no problem: we can re-tag it.
43 |
44 | Just use `docker tag` to add a new tag with your DockerHub user name, and give it a version:
45 |
46 | ```
47 | $ docker images
48 | REPOSITORY TAG IMAGE ID CREATED SIZE
49 | ping latest a980ae1c79ea 2 minutes ago 121MB
50 | ubuntu 16.04 6a2f32de169d 5 days ago 117MB
51 | $ docker tag ping delner/ping:1.0
52 | $ docker images
53 | REPOSITORY TAG IMAGE ID CREATED SIZE
54 | delner/ping 1.0 a980ae1c79ea 5 minutes ago 121MB
55 | ping latest a980ae1c79ea 5 minutes ago 121MB
56 | ubuntu 16.04 6a2f32de169d 5 days ago 117MB
57 | $
58 | ```
59 |
60 | You can see the same image ID now maps to both the old and new tag. To remove the old tag, run `docker rmi` with the old image tag:
61 |
62 | ```
63 | $ docker rmi ping
64 | Untagged: ping:latest
65 | $ docker images
66 | REPOSITORY TAG IMAGE ID CREATED SIZE
67 | delner/ping 1.0 a980ae1c79ea 6 minutes ago 121MB
68 | ubuntu 16.04 6a2f32de169d 5 days ago 117MB
69 | $
70 | ```
71 |
72 | ### Pushing images
73 |
74 | To push an image, all you need to do is call `docker push` with the tag.
75 |
76 | ```
77 | $ docker push delner/ping:1.0
78 | The push refers to a repository [docker.io/delner/ping]
79 | 3b372b8ab44b: Pushed
80 | ab4b9ad8d212: Mounted from library/ubuntu
81 | 57e913ee49e5: Mounted from library/ubuntu
82 | 2ea6deead2b0: Mounted from library/ubuntu
83 | 7cbd4b94e525: Mounted from library/ubuntu
84 | e86a0c422723: Mounted from library/ubuntu
85 | 1.0: digest: sha256:1881fd1efdde061d3aede939b8696c0e6d0b36e9f8b38abccb1074bc60592a60 size: 1568
86 | $
87 | ```
88 |
89 | It will automatically create a new public repository on DockerHub under the organization in the tag. By providing it with your user name, it creates a new repository at `https://hub.docker.com/r//ping/`. Feel free to check out your new repo page!
90 |
91 | It's also possible push images to non-DockerHub repositories, such as ones provided by AWS Elastic Container Registry, by changing the repository part of the tag to match the appropriate URL.
92 |
93 | # END OF EXERCISE 4
--------------------------------------------------------------------------------
/exercises/basic/5-volumes/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 5: Volumes
2 |
3 | In this exercise, we'll learn to work with Docker volumes, for persisting data between containers.
4 |
5 | To accomplish this, we'll setup an Apache HTTPD web server, and persist some HTML files in a volume.
6 |
7 | ### Setting up the server
8 |
9 | To run our Apache HTTPD server, run this command:
10 |
11 | ```
12 | $ docker run --rm -d --name apache -p 80:80 httpd:2.4
13 | d87e0a193dde5652ac762d8849983c2cadb5116b80c8a61a4180e350d678b4d2
14 | ```
15 |
16 | This command will start a new container from HTTP 2.4, name it `apache`, bind port `80` to the host machine (more on this later), and set a flag to delete the container when it stops.
17 |
18 | After it starts, we can run `curl localhost` to query the web server for the default page:
19 |
20 | ```
21 | $ curl localhost
22 | It works!
23 | $
24 | ```
25 |
26 | This is the default `index.html` file included with a new Apache 2.4 installation. Let's replace this HTML file with new content.
27 |
28 | To do so, we'll use the `docker cp` command, similar to `scp`, which copies files between the host and containers. Let's give it the `index.html` file from the directory this README is sitting in:
29 |
30 | ```
31 | $ docker cp index.html apache:/usr/local/apache2/htdocs/
32 | $
33 | ```
34 |
35 | The first path is the source path, representing our new file on our host machine, and the second path our destination. `apache` is the name of the container we want to copy into, and `/usr/local/apache2/htdocs/` is where the web server serves HTML from.
36 |
37 | Running `curl` again now looks a little different:
38 |
39 | ```
40 | $ curl localhost
41 | It works in Docker!
42 | $
43 | ```
44 |
45 | ##### A possible data problem
46 |
47 | This container, for its lifetime, will continue to serve our new HTML file.
48 |
49 | However, containers in Docker are, in practice, considered ephemeral. They can die unexpectedly, and in certain deployments, be removed without warning. If you're depending upon the container state for your application, you might lose important data when such containers die. This is especially a concern for applications like databases, which are supposed to be considered permanent datastores.
50 |
51 | In the case of our HTTPD server, simply stopping the container will cause it to be autoremoved. We can bring another container back up in it's place, but it won't have our changes any more.
52 |
53 | ```
54 | $ docker stop apache
55 | apache
56 | $ docker run --rm -d --name apache -p 80:80 httpd:2.4
57 | 9bd0620e3d8464456c368b1fe9b82733282d980a7c3f854b8cba7726f0a02958
58 | $ curl localhost
59 | It works!
60 | $
61 | ```
62 |
63 | To preserve our data between outages or system upgrades, we can use volumes to persist our data across generations of containers.
64 |
65 | ### Managing volumes
66 |
67 | Volumes in Docker are file stores, which sit independently of your Docker containers. The function like Amazon Web Services' EBS volumes, and other mountable media like USB thumb drives. They can be created, deleted, and mounted on containers at specific locations within an image, like you would with `mount` command in Linux.
68 |
69 | To list your volumes, run `docker volume ls`:
70 |
71 | ```
72 | $ docker volume ls
73 | DRIVER VOLUME NAME
74 | $
75 | ```
76 | To create a new volume, run `docker volume create` and give it a volume name.
77 |
78 | ```
79 | $ docker volume create myvolume
80 | myvolume
81 | $ docker volume ls
82 | DRIVER VOLUME NAME
83 | local myvolume
84 | $
85 | ```
86 |
87 | To remove a volume, run `docker volume rm` and give it the volume name.
88 |
89 | ```
90 | $ docker volume rm myvolume
91 | myvolume
92 | $ docker volume ls
93 | DRIVER VOLUME NAME
94 | $
95 | ```
96 |
97 | ### Mounting volumes on containers
98 |
99 | First create a new volume named `httpd_htdocs`:
100 |
101 | ```
102 | $ docker volume create httpd_htdocs
103 | httpd_htdocs
104 | $
105 | ```
106 |
107 | Then re-run our `docker run` command, providing the `-v` mount flag.
108 |
109 | ```
110 | $ docker run --rm -d --name apache -p 80:80 -v httpd_htdocs:/usr/local/apache2/htdocs/ httpd:2.4
111 | c21dd93fea83d710b4d4c954911862760030723df6a5b42650e462e388fe6049
112 | $
113 | ```
114 |
115 | And re-copy in our modified HTML file.
116 |
117 | ```
118 | $ docker cp index.html apache:/usr/local/apache2/htdocs/
119 | $
120 | ```
121 |
122 | And run `curl` to verify it worked.
123 |
124 | ```
125 | $ curl localhost
126 | It works in Docker!
127 | $
128 | ```
129 |
130 | Now to see the volume in action, let's stop the container. By providing the `--rm` flag during `run`, it should remove the container upon stopping.
131 |
132 | ```
133 | $ docker stop apache
134 | apache
135 | $
136 | ```
137 |
138 | Then once again start httpd with the same run command as last time. This time, however, we can `curl` and see our file changes are still there from before.
139 |
140 | ```
141 | $ docker run --rm -d --name apache -p 80:80 -v httpd_htdocs:/usr/local/apache2/htdocs/ httpd:2.4
142 | c21dd93fea83d710b4d4c954911862760030723df6a5b42650e462e388fe6049
143 | $ curl localhost
144 | It works in Docker!
145 | $
146 | ```
147 |
148 | We can take this volume and mount it on any HTTPD container now, which gives us flexibility in swapping out our container for newer versions without losing our data, if we wish.
149 |
150 | Go ahead and run `docker stop apache` to stop and remove the container, then `docker volume rm httpd_htdocs` to remove the volume.
151 |
152 | ### Mounting host directories on containers
153 |
154 | As an alternative to using volumes, if you have a directory on your host machine you'd like to use like a volume, you can mount those too. This is technique is useful in development environments, where you might want to mount your local repo onto a Docker image, and actively modify the contents of a Docker container without rebuilding or copying files to it.
155 |
156 | The `-v` flag to accomplish this is almost identical to the previous one. Simply specify an absolute path to a local directory instead. In our case, we'll pass `.` to specify the `5-volumes` directory in this repo, which conveniently contains a modified version of the HTML file.
157 |
158 | ```
159 | $ pwd
160 | /home/david/src/docker-training/exercises/basic/5-volumes/
161 | $ docker run --rm -d --name apache -p 80:80 -v /home/david/src/docker-training/exercises/basic/5-volumes/:/usr/local/apache2/htdocs/ httpd:2.4
162 | 0d91516b20ea6113b5dcca08ada6465095dc68663b3d2201dc0490165764f842
163 | $ curl localhost
164 | It works in Docker!
165 | $
166 | ```
167 |
168 | With the host directory mount in place, modify the `index.html` file in this directory with whatever message you like, then save the file and re-run `curl`.
169 |
170 | ```
171 | $ curl localhost
172 | It works quite well in Docker!
173 | $
174 | ```
175 |
176 | You can see file changes take place immediately on the Docker container without any need to run `docker cp`.
177 |
178 | Go ahead and run `docker stop apache` to stop and remove the container.
179 |
180 | # END OF EXERCISE 5
--------------------------------------------------------------------------------
/exercises/compose/1-basics/README.md:
--------------------------------------------------------------------------------
1 | # TBD:
2 |
3 | Following section is some misc content. Move it somewhere else?
4 |
5 | ### Configuring containers
6 |
7 | 6. Some Docker images, especially full fledged applications, require additional configuration to run properly.
8 |
9 | To this end, the `docker run` command offers many options to customize configuration for your container, which you can see by running `--help`.
10 |
11 | ```
12 | $ docker run --help
13 |
14 | Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
15 |
16 | Run a command in a new container
17 |
18 | Options:
19 | --add-host list Add a custom host-to-IP mapping (host:ip) (default [])
20 | -a, --attach list Attach to STDIN, STDOUT or STDERR (default [])
21 | --blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
22 | --blkio-weight-device list Block IO weight (relative device weight) (default [])
23 | --cap-add list Add Linux capabilities (default [])
24 | --cap-drop list Drop Linux capabilities (default [])
25 | --cgroup-parent string Optional parent cgroup for the container
26 | --cidfile string Write the container ID to the file
27 | --cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
28 | --cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
29 | --cpu-rt-period int Limit CPU real-time period in microseconds
30 | --cpu-rt-runtime int Limit CPU real-time runtime in microseconds
31 | -c, --cpu-shares int CPU shares (relative weight)
32 | --cpus decimal Number of CPUs (default 0.000)
33 | --cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
34 | --cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
35 | -d, --detach Run container in background and print container ID
36 | --detach-keys string Override the key sequence for detaching a container
37 | --device list Add a host device to the container (default [])
38 | --device-cgroup-rule list Add a rule to the cgroup allowed devices list (default [])
39 | --device-read-bps list Limit read rate (bytes per second) from a device (default [])
40 | --device-read-iops list Limit read rate (IO per second) from a device (default [])
41 | --device-write-bps list Limit write rate (bytes per second) to a device (default [])
42 | --device-write-iops list Limit write rate (IO per second) to a device (default [])
43 | --disable-content-trust Skip image verification (default true)
44 | --dns list Set custom DNS servers (default [])
45 | --dns-option list Set DNS options (default [])
46 | --dns-search list Set custom DNS search domains (default [])
47 | --entrypoint string Overwrite the default ENTRYPOINT of the image
48 | -e, --env list Set environment variables (default [])
49 | --env-file list Read in a file of environment variables (default [])
50 | --expose list Expose a port or a range of ports (default [])
51 | --group-add list Add additional groups to join (default [])
52 | --health-cmd string Command to run to check health
53 | --health-interval duration Time between running the check (ns|us|ms|s|m|h) (default 0s)
54 | --health-retries int Consecutive failures needed to report unhealthy
55 | --health-timeout duration Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)
56 | --help Print usage
57 | -h, --hostname string Container host name
58 | --init Run an init inside the container that forwards signals and reaps processes
59 | --init-path string Path to the docker-init binary
60 | -i, --interactive Keep STDIN open even if not attached
61 | --ip string IPv4 address (e.g., 172.30.100.104)
62 | --ip6 string IPv6 address (e.g., 2001:db8::33)
63 | --ipc string IPC namespace to use
64 | --isolation string Container isolation technology
65 | --kernel-memory bytes Kernel memory limit
66 | -l, --label list Set meta data on a container (default [])
67 | --label-file list Read in a line delimited file of labels (default [])
68 | --link list Add link to another container (default [])
69 | --link-local-ip list Container IPv4/IPv6 link-local addresses (default [])
70 | --log-driver string Logging driver for the container
71 | --log-opt list Log driver options (default [])
72 | --mac-address string Container MAC address (e.g., 92:d0:c6:0a:29:33)
73 | -m, --memory bytes Memory limit
74 | --memory-reservation bytes Memory soft limit
75 | --memory-swap bytes Swap limit equal to memory plus swap: '-1' to enable unlimited swap
76 | --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
77 | --name string Assign a name to the container
78 | --network string Connect a container to a network (default "default")
79 | --network-alias list Add network-scoped alias for the container (default [])
80 | --no-healthcheck Disable any container-specified HEALTHCHECK
81 | --oom-kill-disable Disable OOM Killer
82 | --oom-score-adj int Tune host's OOM preferences (-1000 to 1000)
83 | --pid string PID namespace to use
84 | --pids-limit int Tune container pids limit (set -1 for unlimited)
85 | --privileged Give extended privileges to this container
86 | -p, --publish list Publish a container's port(s) to the host (default [])
87 | -P, --publish-all Publish all exposed ports to random ports
88 | --read-only Mount the container's root filesystem as read only
89 | --restart string Restart policy to apply when a container exits (default "no")
90 | --rm Automatically remove the container when it exits
91 | --runtime string Runtime to use for this container
92 | --security-opt list Security Options (default [])
93 | --shm-size bytes Size of /dev/shm
94 | --sig-proxy Proxy received signals to the process (default true)
95 | --stop-signal string Signal to stop a container (default "SIGTERM")
96 | --stop-timeout int Timeout (in seconds) to stop a container
97 | --storage-opt list Storage driver options for the container (default [])
98 | --sysctl map Sysctl options (default map[])
99 | --tmpfs list Mount a tmpfs directory (default [])
100 | -t, --tty Allocate a pseudo-TTY
101 | --ulimit ulimit Ulimit options (default [])
102 | -u, --user string Username or UID (format: [:])
103 | --userns string User namespace to use
104 | --uts string UTS namespace to use
105 | -v, --volume list Bind mount a volume (default [])
106 | --volume-driver string Optional volume driver for the container
107 | --volumes-from list Mount volumes from the specified container(s) (default [])
108 | -w, --workdir string Working directory inside the container
109 | $
110 | ```
111 |
112 | Typically you won't need to use many of these options, so we'll focus on the most commonly used ones instead.
113 |
114 | ##### Environment variables
115 |
116 | The most important of these options for configuring new containers is the `-e` flag, which allows you to specify environment variables to set in the container. Most Docker images use environment variables to drive their configuration, which can be customized per container.
117 |
118 | Let's try it out. Just pass the `-e` into a run command to start a container:
119 |
120 | ```
121 | $ docker run -it --rm -e 'MESSAGE=Hello world!' ubuntu:16.04 /bin/bash
122 | root@59e07d26092c:/# echo $MESSAGE
123 | Hello world!
124 | root@59e07d26092c:/# exit
125 | $
126 | ```
127 |
128 | Although the value of setting an environment variable is limited in this example, it really shines through in other images you can find on DockerHub.
129 |
130 | For example, with the `postgres` image, you can use environment variables to specify the default user/password:
131 |
132 | ```
133 | $ docker run --rm -e POSTGRES_USER=archer -e POSTGRES_PASSWORD=guest postgres
134 | ```
135 |
136 | For your own custom images, you might also use environment variables for setting sensitive configuration, such as API keys and database URLS, for your application.
137 |
138 | ##### Ports
139 |
140 | In many cases, you'll want to connect to processes running on your Docker containers, such as a web server on port 80.
141 |
142 | By default, however, Docker containers are network inaccessible from your local machine. We'll cover this more in detail in the *Networking* tutorial, but for now, it's important to know you must expose and map ports between Docker containers and your host machine.
143 |
144 |
--------------------------------------------------------------------------------
/exercises/basic/3-building_images/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 3: Building images
2 |
3 | In this exercise, we'll learn how to create a new Docker image.
4 |
5 | To do this, like in "Changing Images", we'll add the `ping` utility to the `ubuntu:16.04` image. The outcome from each of these exercises should be equivalent.
6 |
7 | ### Getting setup
8 |
9 | First download the `ubuntu:16.04` image with `docker pull`. (You might already have this if you have completed the previous tutorial.)
10 |
11 | ```
12 | $ docker pull ubuntu:16.04
13 | 16.04: Pulling from library/ubuntu
14 | c62795f78da9: Pull complete
15 | d4fceeeb758e: Pull complete
16 | 5c9125a401ae: Pull complete
17 | 0062f774e994: Pull complete
18 |
19 | 6b33fd031fac: Pull complete
20 | Digest: sha256:c2bbf50d276508d73dd865cda7b4ee9b5243f2648647d21e3a471dd3cc4209a0
21 | Status: Downloaded newer image for ubuntu:16.04
22 | $
23 | ```
24 |
25 | If you still have the image from the previous "Changing Images", let's also remove that now. Be sure to `docker rm` any containers based on that image first, otherwise deleting the image will fail.
26 |
27 | ```
28 | $ docker images
29 | REPOSITORY TAG IMAGE ID CREATED SIZE
30 | delner/ping latest 78ba830008a6 45 minutes ago 159MB
31 | ubuntu 16.04 6a2f32de169d 4 days ago 117MB
32 | $ docker rmi 78b
33 | Untagged: delner/ping:latest
34 | Deleted: sha256:78ba830008a61a09f9eae8ca4ead0966ff501457c23df0f635e0651253b3d0e3
35 | Deleted: sha256:94b1207f7fb25468a3e1c16e604f8e65d9ed4df783fa057c368b635d7b086e39
36 | $
37 | ```
38 |
39 |
40 | ### Creating a Dockerfile
41 |
42 | Like the "Changing Images" exercise, we'll install `ping` on top of `ubuntu` to create a new image. Unlike that exercise, however, we will not run or modify any containers to do so.
43 |
44 | Instead, we'll use `Dockerfile`. The `Dockerfile` is a "recipe" of sorts, that contains a list of instructions on how to build a new image.
45 |
46 | 1. Create a new file named `Dockerfile` in your working directory:
47 |
48 | ```
49 | $ touch Dockerfile
50 | $
51 | ```
52 |
53 | 2. Open `Dockerfile` with your favorite text editor.
54 |
55 | 2. Inside the file, we'll need to add some important headers.
56 |
57 | The `FROM` directive specifies what base image this new image will be built upon. (Ubuntu in our case.)
58 |
59 | The `LABEL` directive adds a label the image. Useful for adding metadata.
60 |
61 | Add the following two lines to the top of your file:
62 |
63 | ```
64 | FROM ubuntu:16.04
65 | LABEL author="David Elner"
66 | ```
67 |
68 | 3. Then we'll need to add some commands to modify the image.
69 |
70 | The `RUN` directive runs a command inside the image, and rolls any changes to the filesystem into a commit. A typical Dockerfile will contain several `RUN` statements, each committing their changes on top of the previous.
71 |
72 | To install `ping`, we'll need to run `apt-get update` and `apt-get install`. First, add the `apt-get update` command:
73 |
74 | ```
75 | RUN apt-get update
76 | ```
77 |
78 | Then add the `apt-get install` command:
79 |
80 | ```
81 | RUN apt-get install -y iputils-ping
82 | ```
83 |
84 | Notice we added the `-y` flag. When building Docker images, these commands will run non-interactively. Normally the `apt-get` command will prompt you "Y/n?" if you want to proceed. The `-y` flag avoids that prompt by always answering "Y".
85 |
86 | Our file should now look something like this:
87 |
88 | ```
89 | FROM ubuntu:16.04
90 | LABEL author="David Elner"
91 |
92 | RUN apt-get update
93 |
94 | RUN apt-get install -y iputils-ping
95 | ```
96 |
97 | And with that, we should be ready to build our image.
98 |
99 | ### Building the Dockerfile
100 |
101 | To build Docker images from Dockerfiles, we use the `docker build` command. The `docker build` command reads a Dockerfile, and runs its instructions to create a new image.
102 |
103 | 1. Let's build our image.
104 |
105 | Running the following builds and tags the image:
106 |
107 | ```
108 | $ docker build -t 'delner/ping' .
109 | Sending build context to Docker daemon 190kB
110 | Step 1/4 : FROM ubuntu:16.04
111 | ---> 6a2f32de169d
112 | Step 2/4 : LABEL author "David Elner"
113 | ---> Running in 50f765b29144
114 | ---> 27bfa513216f
115 | Removing intermediate container 50f765b29144
116 | Step 3/4 : RUN apt-get update
117 | ---> Running in ae8647e54bd1
118 | Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
119 | Get:2 http://security.ubuntu.com/ubuntu xenial-security/universe Sources [30.0 kB]
120 | ...
121 | ---> 1f4fe5596cb1
122 | Removing intermediate container ae8647e54bd1
123 | Step 4/4 : RUN apt-get install -y iputils-ping
124 | ---> Running in e6a838d41cef
125 | Reading package lists...
126 | Building dependency tree...
127 | ...
128 | ---> 918648f00b92
129 | Removing intermediate container e6a838d41cef
130 | Successfully built 918648f00b92
131 | $
132 | ```
133 |
134 | The use of `.` in the arguments is significant here. `docker build` looks for files named `Dockerfile` by default to run. So by giving it a `.`, we're telling Docker to use the `Dockerfile` in our current directory. If this Dockerfile was actually named anything else, you'd change this to match the name of the file.
135 |
136 | Also notice the output about "steps" here. Each directive in your Dockerfile maps to a step here, and after each step is completed, it becomes a commit. Why does this matter though?
137 |
138 | Docker layers each commit on top of the other, like an onion. By doing so, it can keep image sizes small, and when rebuilding images, it can even reuse commits that are unaffected by changes to make builds run quicker.
139 |
140 | We can see this caching behavior in action if we simply rerun the same command again:
141 |
142 | ```
143 | $ docker build -t 'delner/ping' .
144 | Sending build context to Docker daemon 191kB
145 | Step 1/4 : FROM ubuntu:16.04
146 | ---> 6a2f32de169d
147 | Step 2/4 : LABEL author "David Elner"
148 | ---> Using cache
149 | ---> 27bfa513216f
150 | Step 3/4 : RUN apt-get update
151 | ---> Using cache
152 | ---> 1f4fe5596cb1
153 | Step 4/4 : RUN apt-get install -y iputils-ping
154 | ---> Using cache
155 | ---> 918648f00b92
156 | Successfully built 918648f00b92
157 | $
158 | ```
159 |
160 | Running `docker images`, we can see our newly built image.
161 |
162 | ```
163 | $ docker images
164 | REPOSITORY TAG IMAGE ID CREATED SIZE
165 | delner/ping latest 918648f00b92 9 minutes ago 159MB
166 | ubuntu 16.04 6a2f32de169d 4 days ago 117MB
167 | ```
168 |
169 | ### Optimizing the Dockerfile
170 |
171 | Looking at new image, we can see it is `159MB` in size versus its base image of `117MB`. That's a pretty big change in size for installing some utilities. That will take more disk space and add additional time to pushes/pulls of this image.
172 |
173 | But why is it that much bigger? The secret is in the `RUN` commands. As mentioned before, any filesystem changes are committed after the `RUN` command completes. This includes any logs, or temporary data written to the filesystem which might be completely inconsequential to our image.
174 |
175 | In our case, the use of `apt-get` generates a lot of this fluff we don't need in our image. We'll need to modify these `RUN` directives slightly.
176 |
177 | We can start with removing any old logs after the install completes. Adding the following to the bottom of the Dockerfile:
178 |
179 | ```
180 | RUN apt-get clean \
181 | && cd /var/lib/apt/lists && rm -fr *Release* *Sources* *Packages* \
182 | && truncate -s 0 /var/log/*log
183 | ```
184 |
185 | Then running `build` again yields...
186 |
187 | ```
188 | $ docker images
189 | REPOSITORY TAG IMAGE ID CREATED SIZE
190 | delner/ping latest 912bc1c7c059 4 seconds ago 159MB
191 | ubuntu 16.04 6a2f32de169d 4 days ago 117MB
192 | $
193 | ```
194 |
195 | ...yields no improvement? What's going on here?
196 |
197 | Turns out because how commits are layered one upon the other, if there's fluff hanging around from a previous commit, it won't matter if you clean it up in a future `RUN` directive. It will be permanently apart of the history, thus the image size.
198 |
199 | Our fluff actually happens to originate from the `apt-get update` command, which leaves a bunch of package lists around that we don't need. The easiest way to deal with this is to collapse all of the related `RUN` directives together.
200 |
201 | The rewritten Dockerfile should like:
202 |
203 | ```
204 | FROM ubuntu:16.04
205 | LABEL author="David Elner"
206 |
207 | RUN apt-get update \
208 | && apt-get install -y iputils-ping \
209 | && apt-get clean \
210 | && cd /var/lib/apt/lists && rm -fr *Release* *Sources* *Packages* \
211 | && truncate -s 0 /var/log/*log
212 | ```
213 |
214 | Then after rerunning `build`, our images now like:
215 |
216 | ```
217 | $ docker images
218 | REPOSITORY TAG IMAGE ID CREATED SIZE
219 | delner/ping latest 622e555950e0 12 seconds ago 121MB
220 | ubuntu 16.04 6a2f32de169d 4 days ago 117MB
221 | $
222 | ```
223 |
224 | Now the new image is only 4MB larger in size. A major improvement.
225 |
226 | ### Other Dockerfile directives
227 |
228 | There are [many other useful directives](https://docs.docker.com/engine/reference/builder/) available in the Dockerfile.
229 |
230 | Some important ones:
231 |
232 | - `COPY`: Copy files from your host into the Docker image.
233 | - `WORKDIR`: Specify a default directory to execute commands from.
234 | - `CMD`: Specify a default command to run.
235 | - `ENV`: Specify a default environment variable.
236 | - `EXPOSE`: Expose a port by default.
237 | - `ARG`: Specify a build-time argument (for more configurable, advanced builds.)
238 |
239 | Since our Dockerfile is build for `ping`, let's add the `ENV` and `CMD` directives.
240 |
241 | ```
242 | FROM ubuntu:16.04
243 | LABEL author="David Elner"
244 |
245 | ENV PING_TARGET "google.com"
246 |
247 | RUN apt-get update \
248 | && apt-get install -y iputils-ping \
249 | && apt-get clean \
250 | && cd /var/lib/apt/lists && rm -fr *Release* *Sources* *Packages* \
251 | && truncate -s 0 /var/log/*log
252 |
253 | CMD ["sh", "-c", "ping $PING_TARGET"]
254 | ```
255 |
256 | These new directives mean that our image when run with `docker run -it delner/ping` will automatically run `ping google.com`.
257 |
258 | ```
259 | $ docker run -it delner/ping
260 | PING google.com (172.217.10.46) 56(84) bytes of data.
261 | 64 bytes from lga34s13-in-f14.1e100.net (172.217.10.46): icmp_seq=1 ttl=37 time=0.300 ms
262 | 64 bytes from lga34s13-in-f14.1e100.net (172.217.10.46): icmp_seq=2 ttl=37 time=0.373 ms
263 | 64 bytes from lga34s13-in-f14.1e100.net (172.217.10.46): icmp_seq=3 ttl=37 time=0.378 ms
264 | ^C
265 | --- google.com ping statistics ---
266 | 3 packets transmitted, 3 received, 0% packet loss, time 2078ms
267 | rtt min/avg/max/mdev = 0.300/0.350/0.378/0.038 ms
268 | $
269 | ```
270 |
271 | ### Learning more about Dockerfiles
272 |
273 | Most images in the Docker world are built from Dockerfiles, and looking at other Dockerfiles from your favorite repos can be a wonderful source of information about how they function, and how you can improve your own images. Seek them out on both DockerHub and GitHub!
274 |
275 | # END OF EXERCISE 3
--------------------------------------------------------------------------------
/exercises/basic/6-networking/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 6: Networking
2 |
3 | In this exercise, we'll learn to work with Docker networks, and interconnect containers.
4 |
5 | To accomplish this, we'll setup two Postgres databases, and query between them.
6 |
7 | ### Listing networks
8 |
9 | Docker defines networks, which groups containers for interoperability and DNS functions.
10 |
11 | To list networks, run `docker network ls`:
12 |
13 | ```
14 | $ docker network ls
15 | NETWORK ID NAME DRIVER SCOPE
16 | 99cbf6b3d074 bridge bridge local
17 | 4a2071abf006 host host local
18 | 3789e459bd9c none null local
19 | $
20 | ```
21 |
22 | There are 3 default networks: `bridge`, `host`, and `none` listed here. Any other custom networks will also be listed here. The `host` and `none` networks are not important for this exercise, but `bridge` is of interest.
23 |
24 | ### The default `bridge` network
25 |
26 | All new containers, if given no other configuration, will be automatically added the `bridge` network. This network acts as a pass through to your host's ethernet, so your Docker containers can access the internet.
27 |
28 | We can inspect the `bridge` network by running `docker network inspect bridge`:
29 |
30 | ```
31 | $ docker network inspect bridge
32 | [
33 | {
34 | "Name": "bridge",
35 | "Id": "99cbf6b3d07476bca2aaca71413b1a7609338d7d8deae9d4af77b062a98672de",
36 | "Created": "2017-04-17T20:27:36.319424753-04:00",
37 | "Scope": "local",
38 | "Driver": "bridge",
39 | "EnableIPv6": false,
40 | "IPAM": {
41 | "Driver": "default",
42 | "Options": null,
43 | "Config": [
44 | {
45 | "Subnet": "172.17.0.0/16",
46 | "Gateway": "172.17.0.1"
47 | }
48 | ]
49 | },
50 | "Internal": false,
51 | "Attachable": false,
52 | "Containers": {},
53 | "Options": {
54 | "com.docker.network.bridge.default_bridge": "true",
55 | "com.docker.network.bridge.enable_icc": "true",
56 | "com.docker.network.bridge.enable_ip_masquerade": "true",
57 | "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
58 | "com.docker.network.bridge.name": "docker0",
59 | "com.docker.network.driver.mtu": "1500"
60 | },
61 | "Labels": {}
62 | }
63 | ]
64 | $
65 | ```
66 |
67 | There's some miscellaneous information about the network, but notice the `"Containers": {}` entry. You can see any containers that are currently connected to the network here.
68 |
69 | Let's start a `ping` container, and inspect this again:
70 |
71 | ```
72 | $ docker run --rm -d --name dummy delner/ping:1.0
73 | 104633917dbfe00843722336838f163b800dde46e632e47470b204c21fc44f21
74 | $ docker network inspect bridge
75 | ...
76 | "Containers": {
77 | "104633917dbfe00843722336838f163b800dde46e632e47470b204c21fc44f21": {
78 | "Name": "dummy",
79 | "EndpointID": "38f01d182b8d55de5f8ed3221f12086dd2eac3426b159cc8e6bda0075dbd0f47",
80 | "MacAddress": "02:42:ac:11:00:02",
81 | "IPv4Address": "172.17.0.2/16",
82 | "IPv6Address": ""
83 | }
84 | },
85 | ...
86 | $
87 | ```
88 |
89 | You can see the container was added to the default network. Now let's add another `ping` container, and set it to ping our first.
90 |
91 | ```
92 | $ docker run --rm -d -e PING_TARGET=172.17.0.2 --name pinger delner/ping:1.0
93 | 3a79f28b8ac36c0e7aae523c4831c9405c110d593c15a30639606250595b245b
94 | $ docker ps
95 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96 | 3a79f28b8ac3 delner/ping:1.0 "sh -c 'ping $PING..." 4 seconds ago Up 3 seconds pinger
97 | 104633917dbf delner/ping:1.0 "sh -c 'ping $PING..." About a minute ago Up About a minute dummy
98 | $ docker logs pinger
99 | PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
100 | 64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.171 ms
101 | 64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.100 ms
102 | 64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.098 ms
103 | 64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.098 ms
104 | $
105 | ```
106 |
107 | Inspecting the logs for `pinger` we can see it was able to successfully ping the other container in the network. While IP address does work, it's very cumbersome and prone to error if addresses change. It would be better to use a hostname, specifically the container name `dummy`, to always resolve to the correct container.
108 |
109 | Running `ping` with the `dummy` as the target:
110 |
111 | ```
112 | $ docker run --rm -d -e PING_TARGET=dummy --name pinger delner/ping:1.0
113 | 3a79f28b8ac36c0e7aae523c4831c9405c110d593c15a30639606250595b245b
114 | $ docker ps
115 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
116 | 104633917dbf delner/ping:1.0 "sh -c 'ping $PING..." About a minute ago Up About a minute dummy
117 | $
118 | ```
119 |
120 | ...results in failure. The host name couldn't be resolved, thus causing the command to error and the container exit and autoremove.
121 |
122 | The default `bridge` network will not automatically allow you to network containers by container name. We can, however, easily accomplish host resolution using a custom network.
123 |
124 | Stop and remove the `dummy` container by running `docker stop dummy`.
125 |
126 | ### Managing custom networks
127 |
128 | To create a new network, use the `docker network create` command and provide it a network name.
129 |
130 | ```
131 | $ docker network create skynet
132 | c234438e88ab579be943859dbc0a89788563226c3a9a13b4f1a2c78d1d8000c9
133 | $ docker network ls
134 | NETWORK ID NAME DRIVER SCOPE
135 | 99cbf6b3d074 bridge bridge local
136 | 4a2071abf006 host host local
137 | 3789e459bd9c none null local
138 | c234438e88ab skynet bridge local
139 | $ docker network inspect skynet
140 | [
141 | {
142 | "Name": "skynet",
143 | "Id": "c234438e88ab579be943859dbc0a89788563226c3a9a13b4f1a2c78d1d8000c9",
144 | "Created": "2017-04-18T01:28:56.982335289-04:00",
145 | "Scope": "local",
146 | "Driver": "bridge",
147 | "EnableIPv6": false,
148 | "IPAM": {
149 | "Driver": "default",
150 | "Options": {},
151 | "Config": [
152 | {
153 | "Subnet": "172.26.0.0/16",
154 | "Gateway": "172.26.0.1"
155 | }
156 | ]
157 | },
158 | "Internal": false,
159 | "Attachable": false,
160 | "Containers": {},
161 | "Options": {},
162 | "Labels": {}
163 | }
164 | ]
165 | $
166 | ```
167 |
168 | To remove networks, run `docker network rm` and provide it the network name.
169 |
170 | ### Adding containers to a network
171 |
172 | Let's rerun the `ping` container, this time assigning it a network:
173 |
174 | ```
175 | $ docker run --rm -d --network skynet --name dummy delner/ping:1.0
176 | ```
177 |
178 | Then the pinger, targeting the dummy `ping` container:
179 |
180 | ```
181 | $ docker run --rm -d --network skynet -e PING_TARGET=dummy --name pinger delner/ping:1.0
182 | 28e68fed9fe28a4346951fa8b6f4147a16f2afec8671357f1ed5f27425914b0a
183 | $ docker logs pinger
184 | PING dummy (172.26.0.2) 56(84) bytes of data.
185 | 64 bytes from dummy.skynet (172.26.0.2): icmp_seq=1 ttl=64 time=0.101 ms
186 | 64 bytes from dummy.skynet (172.26.0.2): icmp_seq=2 ttl=64 time=0.102 ms
187 | 64 bytes from dummy.skynet (172.26.0.2): icmp_seq=3 ttl=64 time=0.116 ms
188 | $
189 | ```
190 |
191 | Notice this time the host name resolves successfully. This is Docker's Embedded DNS in action. It's most useful when orchestrating multiple containers in a single application, such as a web server, database, and cache. Instead of using IP addresses, you can define each of the respective connection strings using container names to leverage DNS resolution.
192 |
193 | Stop and remove the containers by running `docker stop pinger` and `docker stop dummy`.
194 |
195 | ### Connecting between containers in a network
196 |
197 | We can resolve host names and ping, but this isn't the same as connecting with TCP/UDP between containers.
198 |
199 | Let's setup two `postgres` databases to connect to one another: a `widget` database, and `gadget` database.
200 |
201 | Start each of the databases and add them to the network:
202 |
203 | ```
204 | $ docker run --rm -d --name widgetdb --network skynet -p 5432 postgres
205 | 7f0248e3c0f4f03159ef966fd9767a4c7e3412801f8b0445cebb933d1e84e020
206 | $ docker run --rm -d --name gadgetdb --network skynet -p 5432 postgres
207 | 8dc66701837c695728abb9046db71924112a9b8f2f1e096094ab5b5d631e2f73
208 | $ docker ps
209 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
210 | 8dc66701837c postgres "docker-entrypoint..." 11 seconds ago Up 10 seconds 0.0.0.0:32769->5432/tcp gadgetdb
211 | 7f0248e3c0f4 postgres "docker-entrypoint..." 40 seconds ago Up 39 seconds 0.0.0.0:32768->5432/tcp widgetdb
212 | $
213 | ```
214 |
215 | By default, port 5432 is blocked and inaccessible. However, by adding `-p 5432`, we are permitting other containers to access it through port 5432, the default Postgres port.
216 |
217 | Now that they're running, start a shell session in the `widgetdb` using `docker exec`:
218 |
219 | ```
220 | $ docker exec -it widgetdb /bin/bash
221 | root@7f0248e3c0f4:/#
222 | ```
223 |
224 | You can then connect to the local database using `psql`. (End the `psql` session by entering `\q`.)
225 |
226 | ```
227 | root@7f0248e3c0f4:/# psql -U postgres
228 | psql (9.6.2)
229 | Type "help" for help.
230 |
231 | postgres=# \q
232 | root@7f0248e3c0f4:/#
233 | ```
234 |
235 | Or to the `gadget` database by referring to it by name:
236 |
237 | ```
238 | root@7f0248e3c0f4:/# psql -U postgres -h gadgetdb
239 | psql (9.6.2)
240 | Type "help" for help.
241 |
242 | postgres=# \q
243 | root@7f0248e3c0f4:/#
244 | ```
245 |
246 | Type `exit` to end the session, then `docker stop widgetdb gadgetdb` to stop and remove the containers.
247 |
248 | ### Binding ports to the host
249 |
250 | Sometimes its useful to access an application running in a Docker container directly, as if it were running on your host machine.
251 |
252 | To this end, you can bind ports from a container to a port on your host machine. To do this, the altered command from our previous Postgres example would look like:
253 |
254 | ```
255 | $ docker run --rm -d --name widgetdb --network skynet -p 5432:5432 postgres
256 | ```
257 |
258 | The `-p` flag given `:` does this mapping, making the server available as `localhost:5432`:
259 |
260 | You can then run `psql` (if the utility is installed) on your host machine to access the Postgres database
261 |
262 | ```
263 | $ psql -U postgres -h localhost
264 | psql (9.6.2)
265 | Type "help" for help.
266 |
267 | postgres=# \q
268 | $
269 | ```
270 |
271 | It's important to keep in mind that you can only bind one application to a host port at a time. If you try to start any applications on your host machine, or other Docker containers that want to bind to a port already in use, they will fail to do so.
272 |
273 | Type `docker stop widgetdb` to stop and remove the container.
274 |
275 | # END OF EXERCISE 6
--------------------------------------------------------------------------------
/exercises/basic/2-changing_images/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 2: Changing images
2 |
3 | In this exercise, we'll learn how to modify an existing Docker image, and commit it as a new one.
4 |
5 | To accomplish this, we'll modify the `ubuntu:16.04` image to include the `ping` utility.
6 |
7 | ### Getting setup
8 |
9 | First download the `ubuntu:16.04` image with `docker pull`. (You might already have this if you have completed the previous tutorial.)
10 |
11 | ```
12 | $ docker pull ubuntu:16.04
13 | 16.04: Pulling from library/ubuntu
14 | c62795f78da9: Pull complete
15 | d4fceeeb758e: Pull complete
16 | 5c9125a401ae: Pull complete
17 | 0062f774e994: Pull complete
18 | 6b33fd031fac: Pull complete
19 | Digest: sha256:c2bbf50d276508d73dd865cda7b4ee9b5243f2648647d21e3a471dd3cc4209a0
20 | Status: Downloaded newer image for ubuntu:16.04
21 | $
22 | ```
23 |
24 | ### Modifying an image
25 |
26 | Let's run the image in a new container and install the `ping` utility.
27 |
28 | 1. First start the container with `/bin/bash`:
29 |
30 | ```
31 | $ docker run -it ubuntu:16.04 /bin/bash
32 | root@786b94c53c6d:/#
33 | ```
34 |
35 | 2. Try running `ping` in the terminal.
36 |
37 | ```
38 | root@786b94c53c6d:/# ping google.com
39 | bash: ping: command not found
40 | root@786b94c53c6d:/#
41 | ```
42 |
43 | The command doesn't exist. The Ubuntu image for Docker only has the bare minimum of software installed to operate the container. That's okay though: we can install the `ping` command.
44 |
45 | 2. But first we'll update our software list.
46 |
47 | In Debian-based Linux environments (such as Ubuntu), you can install new software using the `apt` package manager. For those who have experience with Macs, this program is the equivalent of `homebrew`.
48 |
49 | By default, to reduce the image size, the Ubuntu image doesn't have a list of the available software packages. We need to update the list of available software:
50 |
51 | ```
52 | root@786b94c53c6d:/# apt-get update
53 | Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
54 | Get:2 http://security.ubuntu.com/ubuntu xenial-security/universe Sources [29.6 kB]
55 | Get:3 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
56 | Get:4 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [308 kB]
57 | Get:5 http://security.ubuntu.com/ubuntu xenial-security/restricted amd64 Packages [12.8 kB]
58 | Get:6 http://security.ubuntu.com/ubuntu xenial-security/universe amd64 Packages [132 kB]
59 | Get:7 http://security.ubuntu.com/ubuntu xenial-security/multiverse amd64 Packages [2936 B]
60 | Get:8 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
61 | Get:9 http://archive.ubuntu.com/ubuntu xenial-backports InRelease [102 kB]
62 | Get:10 http://archive.ubuntu.com/ubuntu xenial/universe Sources [9802 kB]
63 | Get:11 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1558 kB]
64 | Get:12 http://archive.ubuntu.com/ubuntu xenial/restricted amd64 Packages [14.1 kB]
65 | Get:13 http://archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [9827 kB]
66 | Get:14 http://archive.ubuntu.com/ubuntu xenial/multiverse amd64 Packages [176 kB]
67 | Get:15 http://archive.ubuntu.com/ubuntu xenial-updates/universe Sources [186 kB]
68 | Get:16 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [652 kB]
69 | Get:17 http://archive.ubuntu.com/ubuntu xenial-updates/restricted amd64 Packages [13.2 kB]
70 | Get:18 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [577 kB]
71 | Get:19 http://archive.ubuntu.com/ubuntu xenial-updates/multiverse amd64 Packages [9809 B]
72 | Get:20 http://archive.ubuntu.com/ubuntu xenial-backports/main amd64 Packages [4929 B]
73 | Get:21 http://archive.ubuntu.com/ubuntu xenial-backports/universe amd64 Packages [2567 B]
74 | Fetched 23.9 MB in 5s (4409 kB/s)
75 | Reading package lists... Done
76 | root@786b94c53c6d:/#
77 | ```
78 |
79 | 3. Now we can install the `ping` command.
80 |
81 | Call `apt-get install iputils-ping` to install the package containing `ping`:
82 |
83 | ```
84 | root@786b94c53c6d:/# apt-get install iputils-ping
85 | Reading package lists... Done
86 | Building dependency tree
87 | Reading state information... Done
88 | The following additional packages will be installed:
89 | libffi6 libgmp10 libgnutls-openssl27 libgnutls30 libhogweed4 libidn11 libnettle6 libp11-kit0 libtasn1-6
90 | Suggested packages:
91 | gnutls-bin
92 | The following NEW packages will be installed:
93 | iputils-ping libffi6 libgmp10 libgnutls-openssl27 libgnutls30 libhogweed4 libidn11 libnettle6 libp11-kit0 libtasn1-6
94 | 0 upgraded, 10 newly installed, 0 to remove and 0 not upgraded.
95 | Need to get 1303 kB of archives.
96 | After this operation, 3778 kB of additional disk space will be used.
97 | Do you want to continue? [Y/n] Y
98 | Get:1 http://archive.ubuntu.com/ubuntu xenial/main amd64 libgmp10 amd64 2:6.1.0+dfsg-2 [240 kB]
99 | Get:2 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libnettle6 amd64 3.2-1ubuntu0.16.04.1 [93.5 kB]
100 | Get:3 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libhogweed4 amd64 3.2-1ubuntu0.16.04.1 [136 kB]
101 | Get:4 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libidn11 amd64 1.32-3ubuntu1.1 [45.6 kB]
102 | Get:5 http://archive.ubuntu.com/ubuntu xenial/main amd64 libffi6 amd64 3.2.1-4 [17.8 kB]
103 | Get:6 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libp11-kit0 amd64 0.23.2-5~ubuntu16.04.1 [105 kB]
104 | Get:7 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libtasn1-6 amd64 4.7-3ubuntu0.16.04.1 [43.2 kB]
105 | Get:8 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libgnutls30 amd64 3.4.10-4ubuntu1.2 [547 kB]
106 | Get:9 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libgnutls-openssl27 amd64 3.4.10-4ubuntu1.2 [21.9 kB]
107 | Get:10 http://archive.ubuntu.com/ubuntu xenial/main amd64 iputils-ping amd64 3:20121221-5ubuntu2 [52.7 kB]
108 | Fetched 1303 kB in 0s (1767 kB/s)
109 | debconf: delaying package configuration, since apt-utils is not installed
110 | Selecting previously unselected package libgmp10:amd64.
111 | (Reading database ... 4764 files and directories currently installed.)
112 | Preparing to unpack .../libgmp10_2%3a6.1.0+dfsg-2_amd64.deb ...
113 | Unpacking libgmp10:amd64 (2:6.1.0+dfsg-2) ...
114 | Selecting previously unselected package libnettle6:amd64.
115 | Preparing to unpack .../libnettle6_3.2-1ubuntu0.16.04.1_amd64.deb ...
116 | Unpacking libnettle6:amd64 (3.2-1ubuntu0.16.04.1) ...
117 | Selecting previously unselected package libhogweed4:amd64.
118 | Preparing to unpack .../libhogweed4_3.2-1ubuntu0.16.04.1_amd64.deb ...
119 | Unpacking libhogweed4:amd64 (3.2-1ubuntu0.16.04.1) ...
120 | Selecting previously unselected package libidn11:amd64.
121 | Preparing to unpack .../libidn11_1.32-3ubuntu1.1_amd64.deb ...
122 | Unpacking libidn11:amd64 (1.32-3ubuntu1.1) ...
123 | Selecting previously unselected package libffi6:amd64.
124 | Preparing to unpack .../libffi6_3.2.1-4_amd64.deb ...
125 | Unpacking libffi6:amd64 (3.2.1-4) ...
126 | Selecting previously unselected package libp11-kit0:amd64.
127 | Preparing to unpack .../libp11-kit0_0.23.2-5~ubuntu16.04.1_amd64.deb ...
128 | Unpacking libp11-kit0:amd64 (0.23.2-5~ubuntu16.04.1) ...
129 | Selecting previously unselected package libtasn1-6:amd64.
130 | Preparing to unpack .../libtasn1-6_4.7-3ubuntu0.16.04.1_amd64.deb ...
131 | Unpacking libtasn1-6:amd64 (4.7-3ubuntu0.16.04.1) ...
132 | Selecting previously unselected package libgnutls30:amd64.
133 | Preparing to unpack .../libgnutls30_3.4.10-4ubuntu1.2_amd64.deb ...
134 | Unpacking libgnutls30:amd64 (3.4.10-4ubuntu1.2) ...
135 | Selecting previously unselected package libgnutls-openssl27:amd64.
136 | Preparing to unpack .../libgnutls-openssl27_3.4.10-4ubuntu1.2_amd64.deb ...
137 | Unpacking libgnutls-openssl27:amd64 (3.4.10-4ubuntu1.2) ...
138 | Selecting previously unselected package iputils-ping.
139 | Preparing to unpack .../iputils-ping_3%3a20121221-5ubuntu2_amd64.deb ...
140 | Unpacking iputils-ping (3:20121221-5ubuntu2) ...
141 | Processing triggers for libc-bin (2.23-0ubuntu7) ...
142 | Setting up libgmp10:amd64 (2:6.1.0+dfsg-2) ...
143 | Setting up libnettle6:amd64 (3.2-1ubuntu0.16.04.1) ...
144 | Setting up libhogweed4:amd64 (3.2-1ubuntu0.16.04.1) ...
145 | Setting up libidn11:amd64 (1.32-3ubuntu1.1) ...
146 | Setting up libffi6:amd64 (3.2.1-4) ...
147 | Setting up libp11-kit0:amd64 (0.23.2-5~ubuntu16.04.1) ...
148 | Setting up libtasn1-6:amd64 (4.7-3ubuntu0.16.04.1) ...
149 | Setting up libgnutls30:amd64 (3.4.10-4ubuntu1.2) ...
150 | Setting up libgnutls-openssl27:amd64 (3.4.10-4ubuntu1.2) ...
151 | Setting up iputils-ping (3:20121221-5ubuntu2) ...
152 | Setcap is not installed, falling back to setuid
153 | Processing triggers for libc-bin (2.23-0ubuntu7) ...
154 | root@786b94c53c6d:/#
155 | ```
156 |
157 | 4. Finally, we should be able to use `ping`.
158 |
159 | Ping your favorite website. When you've seen enough, `Ctrl+C` to interrupt, then `exit` the container.
160 |
161 | ```
162 | root@786b94c53c6d:/# ping google.com
163 | PING google.com (172.217.4.206) 56(84) bytes of data.
164 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=1 ttl=37 time=0.936 ms
165 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=2 ttl=37 time=0.367 ms
166 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=3 ttl=37 time=0.258 ms
167 | ^C
168 | --- google.com ping statistics ---
169 | 3 packets transmitted, 3 received, 0% packet loss, time 2033ms
170 | rtt min/avg/max/mdev = 0.258/0.520/0.936/0.297 ms
171 | root@786b94c53c6d:/# exit
172 | exit
173 | $
174 | ```
175 |
176 | ### Committing changes
177 |
178 | Installing `ping` isn't very special in itself. But what if you wanted to have `ping` on all of your `ubuntu` containers? You'd have to redo this installation each time you spin up a new container, and that isn't much fun.
179 |
180 | The Docker way is to create a new image. There are two ways to do this: 1) build a new image from scratch or 2) commit a container state as a new image. We'll cover how to do #1 in the "Building Images" exercise, but we can do #2 now.
181 |
182 | 1. Let's find our container to create the new image from.
183 |
184 | Fortunately, we have a Docker container with our `ping` utility already installed from the previous steps. It should be stopped right now, but let's find its container ID.
185 |
186 | ```
187 | $ docker ps -a
188 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
189 | 786b94c53c6d ubuntu:16.04 "/bin/bash" 13 minutes ago Exited (0) 22 seconds ago angry_pike
190 | $
191 | ```
192 |
193 | 2. Now let's commit it as a new image.
194 |
195 | `docker commit` takes a container, and allows you to commit its changes as a new image.
196 |
197 | ```
198 | $ docker commit --help
199 |
200 | Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
201 |
202 | Create a new image from a container's changes
203 |
204 | Options:
205 | -a, --author string Author (e.g., "John Hannibal Smith ")
206 | -c, --change list Apply Dockerfile instruction to the created image (default [])
207 | --help Print usage
208 | -m, --message string Commit message
209 | -p, --pause Pause container during commit (default true)
210 | $
211 | ```
212 |
213 | Pass the container ID, an author, commit message, and give it the name `/ping`:
214 |
215 | ```
216 | $ docker commit -a 'David Elner' -m 'Added ping utility.' 786 delner/ping
217 | sha256:78ba830008a61a09f9eae8ca4ead0966ff501457c23df0f635e0651253b3d0e3
218 | $
219 | ```
220 |
221 | Then check `docker images` to see your new image:
222 |
223 | ```
224 | $ docker images
225 | REPOSITORY TAG IMAGE ID CREATED SIZE
226 | delner/ping latest 78ba830008a6 About a minute ago 159MB
227 | ubuntu 16.04 6a2f32de169d 4 days ago 117MB
228 | $
229 | ```
230 |
231 | 3. Finally run your new image in a new container to see it in action!
232 |
233 | ```
234 | $ docker run -it --rm delner/ping /bin/bash
235 | root@3ab21a456c9f:/# ping google.com
236 | PING google.com (172.217.4.206) 56(84) bytes of data.
237 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=1 ttl=37 time=1.12 ms
238 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=2 ttl=37 time=0.380 ms
239 | 64 bytes from lga15s48-in-f14.1e100.net (172.217.4.206): icmp_seq=3 ttl=37 time=0.352 ms
240 | ^C
241 | --- google.com ping statistics ---
242 | 3 packets transmitted, 3 received, 0% packet loss, time 2024ms
243 | rtt min/avg/max/mdev = 0.352/0.620/1.129/0.360 ms
244 | root@3ab21a456c9f:/#
245 | ```
246 |
247 | # END OF EXERCISE 2
--------------------------------------------------------------------------------
/exercises/basic/1-running_containers/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 1: Running containers
2 |
3 | In this exercise, we'll learn the basics of pulling images, starting, stopping, and removing containers.
4 |
5 | ### Pulling an image
6 |
7 | To run containers, we'll first need to pull some images.
8 |
9 | 1. Let's see what images we have currently on our machine, by running `docker images`:
10 |
11 | ```
12 | $ docker images
13 | REPOSITORY TAG IMAGE ID CREATED SIZE
14 | ```
15 |
16 | 2. On a fresh Docker installation, we should have no images. Let's pull one from Dockerhub.
17 |
18 | We usually pull images from DockerHub by tag. These look like:
19 |
20 | ```
21 | # Official Docker images
22 | :
23 | # ubuntu:16.04
24 | # elasticsearch:5.2
25 | # nginx:latest
26 |
27 | # User or organization made images
28 | /:
29 | # delner/ubuntu:16.04
30 | # bitnami/rails:latest
31 | ```
32 |
33 | We can search for images using `docker search `
34 |
35 | ```
36 | $ docker search ubuntu
37 | NAME DESCRIPTION STARS OFFICIAL AUTOMATED
38 | ubuntu Ubuntu is a Debian-based Linux operating s... 5582 [OK]
39 | rastasheep/ubuntu-sshd Dockerized SSH service, built on top of of... 73 [OK]
40 | ubuntu-upstart Upstart is an event-based replacement for ... 70 [OK]
41 | consol/ubuntu-xfce-vnc Ubuntu container with "headless" VNC sessi... 43 [OK]
42 | ubuntu-debootstrap debootstrap --variant=minbase --components... 27 [OK]
43 | ```
44 |
45 | You can also find images online at [DockerHub](https://hub.docker.com/).
46 |
47 | Run `docker pull ubuntu:16.04` to pull an image of Ubuntu 16.04 from DockerHub.
48 |
49 | ```
50 | $ docker pull ubuntu:16:04
51 | 16.04: Pulling from library/ubuntu
52 | 8aec416115fd: Pull complete
53 | 695f074e24e3: Pull complete
54 | 946d6c48c2a7: Pull complete
55 | bc7277e579f0: Pull complete
56 | 2508cbcde94b: Pull complete
57 | Digest: sha256:71cd81252a3563a03ad8daee81047b62ab5d892ebbfbf71cf53415f29c130950
58 | Status: Downloaded newer image for ubuntu:16.04
59 | ```
60 | 3. We can also pull different versions on the same image.
61 |
62 | Run `docker pull ubuntu:16.10` to pull an image of Ubuntu 16.10.
63 |
64 | ```
65 | 16.10: Pulling from library/ubuntu
66 | 3a635c0fcefb: Pull complete
67 | bf3f7e9b4869: Pull complete
68 | ad323864e1f8: Pull complete
69 | b4d3fc870200: Pull complete
70 | 4e69d6ff0e56: Pull complete
71 | Digest: sha256:609c1726180221d95a66ce3ed1e898f4a543c5be9ff3dbb1f10180a6cb2a6fdc
72 | Status: Downloaded newer image for ubuntu:16.10
73 | ```
74 |
75 | Then when we run `docker images again, we should get:
76 |
77 | ```
78 | REPOSITORY TAG IMAGE ID CREATED SIZE
79 | ubuntu 16.10 31005225a745 4 weeks ago 103 MB
80 | ubuntu 16.04 f49eec89601e 4 weeks ago 129 MB
81 | ```
82 |
83 | 4. Over time, your machine can collect a lot of images, so it's nice to remove unwanted images.
84 |
85 | Run `docker rmi ` to remove the Ubuntu 16.10 image we won't be using.
86 |
87 | ```
88 | $ docker rmi 31005225a745
89 | Untagged: ubuntu:16.10
90 | Untagged: ubuntu@sha256:609c1726180221d95a66ce3ed1e898f4a543c5be9ff3dbb1f10180a6cb2a6fdc
91 | Deleted: sha256:31005225a74578ec48fbe5a833ef39a3e41ebcbf0714ad3867070405b3efd81e
92 | Deleted: sha256:c9fcffc56240d2382f78da3130215afcfc7130b29210184f30ffce3a3eae677d
93 | Deleted: sha256:7a8ffa53e9616698d138da12474f8f7441f00e129bb06c7f12b9264828bcad1e
94 | Deleted: sha256:c71fbd03fa070b80919b1712f7b335829fecd0157915cf4b60775988c18a5687
95 | Deleted: sha256:3b1d2c1b8ae337cacea8271862bded89d920bbbf5049fa1b4927169cb3b3974c
96 | Deleted: sha256:6b720ab3505cb593509654fac976193e75bc881d9c72abdffe4c29278396c636
97 | ```
98 |
99 | Alternatively, you can delete images by tag or by a partial image ID. In the previous example, the following would have been equivalent:
100 |
101 | - `docker rmi 31`
102 | - `docker rmi ubuntu:16.10`
103 |
104 | Running `docker images` should reflect the deleted image.
105 |
106 | ```
107 | $ docker images
108 | REPOSITORY TAG IMAGE ID CREATED SIZE
109 | ubuntu 16.04 f49eec89601e 4 weeks ago 129 MB
110 | ```
111 |
112 | A nice shortcut for removing all images from your system is `docker rmi $(docker images -a -q)`
113 |
114 | ### Running our container
115 |
116 | Using the Ubuntu 16.04 image we downloaded, we can run a our first container. Unlike a traditional virtualization framework like VirtualBox or VMWare, we can't just start a virtual machine running this image without anything else: we have to give it a command to run.
117 |
118 | The command can be anything you want, as long as it exists on the image. In the case of the Ubuntu image, it's a Linux kernel with many of the typical applications you'd find in a basic Linux environment.
119 |
120 | 1. Let's do a very simple example. Run `docker run ubuntu:16.04 /bin/echo 'Hello world!'`
121 |
122 | ```
123 | $ docker run ubuntu:16.04 /bin/echo 'Hello world!'
124 | Hello world!
125 | ```
126 |
127 | The `/bin/echo` command is a really simple application that just prints whatever you give it to the terminal. We passed it 'Hello world!', so it prints `Hello world!` to the terminal.
128 |
129 | When you run the whole `docker run` command, it creates a new container from the image specified, then runs the command inside the container. From the previous example, the Docker container started, then ran the `/bin/echo` command in the container.
130 |
131 | 2. Let's check what containers we have after running this. Run `docker ps`
132 |
133 | ```
134 | $ docker ps
135 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
136 | ```
137 |
138 | That's strange: no containers right? The `ps` command doesn't show stopped containers by default, add the `-a` flag.
139 |
140 | ```
141 | $ docker ps -a
142 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
143 | 4fc37e27944a ubuntu:16.04 "/bin/echo 'Hello ..." About a minute ago Exited (0) About a minute ago zen_swartz
144 | ```
145 |
146 | Okay, there's our container. But why is the status "Exited"?
147 |
148 | *Docker containers only run as long as the command it starts with is running.* In our example, it ran `/bin/echo` successfully, printed some output, then exited with status code 0 (which means no errors.) When Docker saw this command exit, the container stopped.
149 |
150 | 3. Let's do something a bit more interactive. Run `docker run ubuntu:16.04 /bin/bash`
151 |
152 | ```
153 | $ docker run ubuntu:16.04 /bin/bash
154 | $
155 | ```
156 |
157 | Notice nothing happened. When we run `docker ps -a`
158 |
159 | ```
160 | $ docker ps -a
161 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
162 | 654792eb403b ubuntu:16.04 "/bin/bash" 4 seconds ago Exited (0) 2 seconds ago distracted_minsky
163 | ```
164 |
165 | The container exited instantly. Why? We were running the `/bin/bash` command, which is an interactive program. However, the `docker run` command doesn't run interactively by default, therefore the `/bin/bash` command exited, and the container stopped.
166 |
167 | Instead, let's add the `-it` flags, which tells Docker to run the command interactively with your terminal.
168 |
169 | ```
170 | $ docker run -it ubuntu:16.04 /bin/bash
171 | root@5fa68739793c:/#
172 | ```
173 |
174 | This looks a lot better. This means you're in a BASH session inside the Ubuntu container. Notice you're running as `root` and the container ID that follows.
175 |
176 | You can now use this like a normal Linux shell. Try `pwd` and `ls` to look at the file system.
177 |
178 | ```
179 | root@5fa68739793c:/# pwd
180 | /
181 | root@5fa68739793c:/# ls
182 | bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
183 | ```
184 |
185 | You can type `exit` to end the BASH session, terminating the command and stopping the container.
186 |
187 | ```
188 | root@5fa68739793c:/# exit
189 | exit
190 | $
191 | ```
192 | 4. By default your terminal remains attached to the container when you run `docker run`. What if you don't want to remain attached?
193 |
194 | By adding the `-d` flag, we can run in detached mode, meaning the container will continue to run as long as the command is, but it won't print the output.
195 |
196 | Let's run `/bin/sleep 3600`, which will run the container idly for 1 hour:
197 |
198 | ```
199 | $ docker run -d ubuntu:16.04 /bin/sleep 3600
200 | be730b8c554b69383f30f71222b9ac264367c7454790dc2a4eb0cda33c0baa2a
201 | $
202 | ```
203 |
204 | If we check the container, we can see it's running the sleep command in a new container.
205 |
206 | ```
207 | $ docker ps
208 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
209 | be730b8c554b ubuntu:16.04 "/bin/sleep 3600" 41 seconds ago Up 40 seconds jovial_goldstine
210 | $
211 | ```
212 |
213 | 5. Now that the container is running in the background, what if we want to reattach to it?
214 |
215 | Conceivably, if this were something like a web server or other process where we might like to inspect logs while it runs, it'd be useful to run something on the container without interrupting the current process.
216 |
217 | To this end, there is another command, called `docker exec`. `docker exec` runs a command within a container that is already running. It works exactly like `docker run`, except instead of taking an image ID, it takes a container ID.
218 |
219 | This makes the `docker exec` command useful for tailing logs, or "SSHing" into an active container.
220 |
221 | Let's do that now, running the following, passing the first few characters of the container ID:
222 |
223 | ```
224 | $ docker exec -it be7 /bin/bash
225 | root@be730b8c554b:/#
226 | ```
227 |
228 | The container ID appearing at the front of the BASH prompt tells us we're inside the container. Once inside a session, we can interact with the container like any SSH session.
229 |
230 | Let's list the running processes:
231 |
232 | ```
233 | root@be730b8c554b:/# ps aux
234 | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
235 | root 1 0.2 0.0 4380 796 ? Ss 15:41 0:00 /bin/sleep 3600
236 | root 6 0.6 0.1 18240 3208 ? Ss 15:41 0:00 /bin/bash
237 | root 16 0.0 0.1 34424 2808 ? R+ 15:41 0:00 ps aux
238 | root@be730b8c554b:/#
239 | ```
240 |
241 | There we can see our running `/bin/sleep 3600` command. Whenever we're done, we can type `exit` to exit our current BASH session, and leave the container running.
242 |
243 | ```
244 | root@be730b8c554b:/# exit
245 | exit
246 | $ docker ps
247 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
248 | be730b8c554b ubuntu:16.04 "/bin/sleep 3600" 9 minutes ago Up 9 minutes jovial_goldstine
249 | $
250 | ```
251 |
252 | And finally checking `docker ps`, we can see the container is still running.
253 |
254 | 6. Instead of waiting 1 hour for this command to stop (and the container exit), what if we'd like to stop the Docker container now?
255 |
256 | To that end, we have the `docker stop` and the `docker kill` commands. The prior is a graceful stop, whereas the latter is a forceful one.
257 |
258 | Let's use `docker stop`, passing it the first few characters of the container name we want to stop.
259 |
260 | ```
261 | $ docker stop be73
262 | be73
263 | ```
264 |
265 | Then checking `docker ps -a`...
266 |
267 | ```
268 | $ docker ps -a
269 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
270 | be730b8c554b ubuntu:16.04 "/bin/sleep 600" 1 minute ago Exited (137) 1 minute ago jovial_goldstine
271 | $
272 | ```
273 |
274 | We can see that it exited with code `137`, which in Linux world means the command was likely aborted with a `kill -9` command.
275 |
276 | ### Removing containers
277 |
278 | 7. After working with Docker containers, you might want to delete old, obsolete ones.
279 |
280 | ```
281 | $ docker ps -a
282 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
283 | be730b8c554b ubuntu:16.04 "/bin/sleep 600" 1 minute ago Exited (137) 1 minute ago jovial_goldstine
284 | $
285 | ```
286 |
287 | From our previous example, we can see with `docker ps -a` that we have a container hanging around.
288 |
289 | To remove this we can use the `docker rm` command which removes stopped containers.
290 |
291 | ```
292 | $ docker rm be73
293 | be73
294 | ```
295 |
296 | A nice shortcut for removing all containers from your system is `docker rm $(docker ps -a -q)`
297 |
298 | It can be tedious to remove old containers each time after you run them. To address this, Docker also allows you to specify the `--rm` flag to the `docker run` command, which will remove the container after it exits.
299 |
300 | ```
301 | $ docker run --rm ubuntu:16.04 /bin/echo 'Hello and goodbye!'
302 | Hello and goodbye!
303 | $ docker ps -a
304 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
305 | $
306 | ```
307 |
308 | # END OF EXERCISE 1
--------------------------------------------------------------------------------