├── 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 --------------------------------------------------------------------------------