{"id":14975898,"url":"https://github.com/mediacomem/docker-brownbag","last_synced_at":"2026-03-01T23:32:00.943Z","repository":{"id":73832457,"uuid":"131975027","full_name":"MediaComem/docker-brownbag","owner":"MediaComem","description":"Docker: From Hello World to Swarm","archived":false,"fork":false,"pushed_at":"2018-07-23T08:34:38.000Z","size":1191,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-01-11T02:18:16.903Z","etag":null,"topics":["containerization","containers","docker","dockercompose","dockerfile","dockerswarm"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MediaComem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-03T09:48:51.000Z","updated_at":"2023-05-03T09:28:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"47a7e0ca-f6ba-4938-b169-ae36cb590acd","html_url":"https://github.com/MediaComem/docker-brownbag","commit_stats":{"total_commits":35,"total_committers":2,"mean_commits":17.5,"dds":0.05714285714285716,"last_synced_commit":"e7c08655896984934a0f549c95a7797b332477c4"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fdocker-brownbag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fdocker-brownbag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fdocker-brownbag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fdocker-brownbag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MediaComem","download_url":"https://codeload.github.com/MediaComem/docker-brownbag/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241110819,"owners_count":19911398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["containerization","containers","docker","dockercompose","dockerfile","dockerswarm"],"created_at":"2024-09-24T13:52:49.842Z","updated_at":"2025-11-22T23:05:10.758Z","avatar_url":"https://github.com/MediaComem.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Docker: From Hello World to Swarm\n\nYou can follow this tutorial to learn the basics of Docker, from running a hello world container to\nrunning a multi-machine swarm. It's a condensed version of some of Docker's own documentation and\nother articles (see the [references](#references) at the bottom).\n\nNote: some names and IDs shown in sample command outputs are randomly generated or\ncontext-dependent, and will differ on your machine. Pay attention to the instructions and do not\nblindly copy-paste commands.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Requirements](#requirements)\n- [What is Docker?](#what-is-docker)\n  - [What is a container?](#what-is-a-container)\n- [Containers \u0026 images](#containers--images)\n  - [Make sure Docker is working](#make-sure-docker-is-working)\n  - [Run a container from an image](#run-a-container-from-an-image)\n  - [Container isolation](#container-isolation)\n  - [Run multiple commands in a container](#run-multiple-commands-in-a-container)\n  - [Commit a container's state to an image manually](#commit-a-containers-state-to-an-image-manually)\n  - [Run containers in the background](#run-containers-in-the-background)\n  - [Access container logs](#access-container-logs)\n  - [Stop and restart containers](#stop-and-restart-containers)\n  - [Run multiple containers](#run-multiple-containers)\n  - [Image layers](#image-layers)\n    - [The top writable layer of containers](#the-top-writable-layer-of-containers)\n    - [Total image size](#total-image-size)\n- [Dockerfile](#dockerfile)\n  - [The `docker build` command](#the-docker-build-command)\n  - [Format](#format)\n  - [Build an image from a Dockerfile](#build-an-image-from-a-dockerfile)\n  - [Build cache](#build-cache)\n  - [A Dockerfile for a Node.js application](#a-dockerfile-for-a-nodejs-application)\n- [Connecting containers](#connecting-containers)\n  - [Exposing container ports on the host machine](#exposing-container-ports-on-the-host-machine)\n  - [Docker networks](#docker-networks)\n    - [Running a container in a network](#running-a-container-in-a-network)\n- [Persistent storage](#persistent-storage)\n  - [Bind mounts](#bind-mounts)\n  - [Volumes](#volumes)\n- [Debugging containers](#debugging-containers)\n  - [Ephemeral containers](#ephemeral-containers)\n- [Docker Compose](#docker-compose)\n  - [The `docker-compose.yml` file](#the-docker-composeyml-file)\n  - [Running Docker Compose services](#running-docker-compose-services)\n  - [Rebuilding Docker Compose services](#rebuilding-docker-compose-services)\n  - [Starting containers automatically](#starting-containers-automatically)\n  - [Horizontal scaling](#horizontal-scaling)\n- [Docker Swarm](#docker-swarm)\n  - [Setup](#setup)\n  - [Create a swarm](#create-a-swarm)\n  - [Run a private registry](#run-a-private-registry)\n    - [Push an image to a private registry](#push-an-image-to-a-private-registry)\n  - [Service stacks](#service-stacks)\n  - [Service replication \u0026 placement](#service-replication--placement)\n  - [Overlay network](#overlay-network)\n  - [Deploy a service stack to a swarm](#deploy-a-service-stack-to-a-swarm)\n  - [Swarm mode routing mesh](#swarm-mode-routing-mesh)\n- [Appendices](#appendices)\n  - [Squashing image layers](#squashing-image-layers)\n    - [Using the `--squash` option](#using-the---squash-option)\n  - [Dockerfile tips](#dockerfile-tips)\n    - [Using smaller base images](#using-smaller-base-images)\n    - [Labeling images](#labeling-images)\n    - [Environment variables](#environment-variables)\n    - [Non-root users](#non-root-users)\n    - [Speeding up builds](#speeding-up-builds)\n    - [Documenting exposed ports](#documenting-exposed-ports)\n    - [Using an entrypoint script](#using-an-entrypoint-script)\n      - [Waiting for other containers](#waiting-for-other-containers)\n  - [Multi-stage builds](#multi-stage-builds)\n- [TODO](#todo)\n- [References](#references)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Requirements\n\n* [Docker Community Edition (CE)][docker-ce] (the latest version is `18.03.0-ce` at the time of\n  writing).\n* [Docker Compose][docker-compose-install] (installed with Docker by default if you're using Docker\n  for Mac or Docker for Windows; the latest version is `1.21.0` at the time of writing).\n* A UNIX command line (on Windows, use [Git Bash][git-bash] or the [Windows Subsystem for\n  Linux][wsl]).\n\nOptionally, to quickly set up the infrastructure for the [Docker Swarm](#docker-swarm) chapter, you\ncan use:\n\n* [Vagrant][vagrant] 2+\n* [VirtualBox][virtualbox] 5+\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## What is Docker?\n\n[Docker][what-is-docker] is the company driving the **container movement**.  Today's businesses are\nunder pressure to digitally transform but are constrained by existing applications and\ninfrastructure while rationalizing an **increasingly diverse portfolio of clouds, datacenters and\napplication architectures**.  Docker enables true **independence between applications and\ninfrastructure** and developers and IT ops to unlock their potential and creates a model for better\ncollaboration and innovation.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### What is a container?\n\nA [container][what-is-container] image is a **lightweight, stand-alone, executable package of a\npiece of software** that includes everything needed to run it: code, runtime, system tools, system\nlibraries, settings.  Available for both Linux and Windows based apps, containerized software will\nalways run the same, regardless of the environment. **Containers isolate software from its\nsurroundings**, for example differences between development and staging environments and help reduce\nconflicts between teams running different software on the same infrastructure.\n\n![Docker containers](images/containers.png)\n\n**Containers and virtual machines are similar** when it comes to resource isolation and allocation\nbenefits, **but function differently** because containers virtualize the operating system instead of\nhardware.  Containers are more portable and efficient.\n\nVirtual machines (VMs) are an abstraction of physical hardware turning one server into many servers.\nThe hypervisor allows multiple VMs to run on a single machine. Each VM includes a full copy of an\noperating system, one or more apps, necessary binaries and libraries - taking up tens of GBs. VMs\ncan also be slow to boot.\n\n![Docker virtual machine versus container](images/vm-vs-container.png)\n\nContainers are an abstraction at the app layer that packages code and dependencies together.\nMultiple containers can run on the same machine and share the OS kernel with other containers, each\nrunning as isolated processes in user space. Containers take up less space than VMs (container\nimages are typically tens of MBs in size), and start almost instantly.\n\n![Docker container versus virtual machine](images/container-vs-vm.png)\n\nIn a nutshell, containers are:\n\n* **Lightweight** - Docker containers running on a single machine **share that machine's operating\n  system kernel**; they start instantly and use less CPU and RAM. Images are constructed from file\n  system layers and share common files. This minimizes disk usage and image downloads are much\n  faster.\n* **Standard** - Docker containers are based on [**open standards**][open-container-initiative] and\n  run on all major Linux distributions, Microsoft Windows, and on any infrastructure including VMs,\n  bare-metal and in the cloud.\n* **Secure** - Docker containers **isolate applications** from one another and from the underlying\n  infrastructure. Docker provides the strongest default isolation to limit app issues to a single\n  container instead of the entire machine.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Containers \u0026 images\n\n\u003cbr\u003e\n\n### Make sure Docker is working\n\nRun a `hello-world` container to make sure everything is installed correctly:\n\n```bash\n$\u003e docker run hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\n9bb5a5d4561a: Pull complete\nDigest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n\nIf your output is similar, you can move on to the next section.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Run a container from an image\n\nThere are many official and community images available on the [Docker Hub][hub-explore]. For this\ntutorial, we'll start by pulling the [official `ubuntu` image][hub-ubuntu] from the hub:\n\n```bash\n$\u003e docker pull ubuntu\nUsing default tag: latest\nlatest: Pulling from library/ubuntu\nd3938036b19c: Pull complete\na9b30c108bda: Pull complete\n67de21feec18: Pull complete\n817da545be2b: Pull complete\nd967c497ce23: Pull complete\nDigest: sha256:9ee3b83bcaa383e5e3b657f042f4034c92cdd50c03f73166c145c9ceaea9ba7c\nStatus: Downloaded newer image for ubuntu:latest\n```\n\nAn **image** is a **blueprint** which forms the basis of containers. It's basically a snapshot of a\nfile system in a given state. This `ubuntu` image contains a headless [Ubuntu operating\nsystem][ubuntu] with only minimal packages installed.\n\nYou can list available images with `docker images`:\n\n```bash\n$\u003e docker images\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nubuntu              latest              c9d990395902        7 days ago          113MB\nhello-world         latest              e38bc07ac18e        8 days ago          1.85kB\n```\n\nRun a **container** based on that image with `docker run \u003cimage\u003e [command...]`.  The following\ncommand runs an Ubuntu container:\n\n```bash\n$\u003e docker run ubuntu echo \"hello from ubuntu\"\nhello from ubuntu\n```\n\nRunning a container means **executing the specified command**, in this case `echo \"hello from\nubuntu\"`, **in an isolated container started from an image**, in this case the Ubuntu image.  The\n`echo` binary that is executed is the one provided by the Ubuntu OS in the image, not your machine.\n\nIf you list running containers with `docker ps`, you will see that the container we just ran is\n**not running**.  A container **stops as soon as the process started by its command is done**.\nSince `echo` is not a long-running command, the container stopped right away:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n```\n\nYou can see the stopped container with `docker ps -a`, which lists all containers regardless of their status:\n\n```bash\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES\nd042ed58ef63        ubuntu              \"echo 'hello from ub…\"   10 seconds ago      Exited (0) 11 seconds ago                       relaxed_galileo\n02bbe5e66c15        hello-world         \"/hello\"                 42 seconds ago      Exited (0) 42 seconds ago                       jovial_jones\n```\n\nYou can remove a stopped container or containers with `docker rm`, using either its ID or its name:\n\n```bash\n$\u003e docker rm relaxed_galileo 02bbe5e66c15\nrelaxed_galileo\n02bbe5e66c15\n\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n```\n\nYou can also add the `--rm` option to `docker run` to run a container and automatically remove it\nwhen it stops:\n\n```bash\n$\u003e docker run --rm ubuntu echo \"hello from ubuntu\"\nhello from ubuntu\n```\n\nNo new container should appear in the list:\n\n```bash\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Container isolation\n\nDocker containers are very similar to [LXC containers][lxc] which provide many\n[security][docker-security] features.  When you start a container with `docker run`, you get:\n\n* **Process isolation:** processes running within a container cannot see, and even less\n  affect, processes running in another container, or in the host system.\n\n  See the difference between running `ps -e` and `docker run --rm ubuntu ps -e`, which will show you\n  all running processes on your machine, and the same as seen from within a container, respectively.\n* **File system isolation:** a container has its own file system separate from your machine's. See\n  the difference between running the following commands:\n\n  * `ls -la /` and `docker run --rm ubuntu ls -la /`, which will show you all files at the root of\n    your file system, and all files at the root of the container's file system, respectively.\n  * `bash --version` and `docker run --rm ubuntu bash --version`, which will show you that the Bash\n    shell on your machine is (probably) not the exact same version as the one in the image.\n  * `uname -a` and `docker run --rm ubuntu uname -a`, which will show you your machine's operating\n    system and the container's, respectively.\n* **Network isolation:** a container doesn't get privileged access to the sockets or interfaces of\n  another container. Of course, containers can interact with each other through their respective\n  network interface, just like they can interact with external hosts. We will see examples of this\n  later.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Run multiple commands in a container\n\nYou can run commands more complicated than `echo`.\nFor example, let's run a [Bash shell][bash].\n\nSince this is an interactive command, add the `-i` (interactive) and `-t` (pseudo-TTY) options to `docker run`:\n\n```bash\n$\u003e docker run -it ubuntu bash\nroot@e07f81d7941d:/#\n```\n\nThis time you are running a Bash shell, which is a **long running comman**d. The process running the\nshell will not stop until you manually type `exit` in the shell, so the **container is not\nstopping** either.\n\nYou should have a new command line prompt (`root@e07f81d7941d:/#` in this example), indicating that you are\nwithin the container:\n\n```bash\nroot@e07f81d7941d:/#\n```\n\nYou can now run any command you want within the running container:\n\n```bash\nroot@e07f81d7941d:/# date\nFri Apr 20 13:20:32 UTC 2018\n```\n\nYou can make changes to the container. Since this is an Ubuntu container, you can install packages.\nUpdate of the package lists first with `apt-get update`:\n\n```bash\nroot@e07f81d7941d:/# apt-get update\nGet:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]\n...\nFetched 25.1 MB in 1s (12.9 MB/s)\nReading package lists... Done\n```\n\nInstall the `fortune` package:\n\n```bash\nroot@e07f81d7941d:/# apt-get install -y fortune\nReading package lists... Done\nBuilding dependency tree\nReading state information... Done\nNote, selecting 'fortune-mod' instead of 'fortune'\nThe following additional packages will be installed:\n  fortunes-min librecode0\nSuggested packages:\n  fortunes x11-utils bsdmainutils\nThe following NEW packages will be installed:\n  fortune-mod fortunes-min librecode0\n0 upgraded, 3 newly installed, 0 to remove and 5 not upgraded.\nNeed to get 625 kB of archives.\nAfter this operation, 2184 kB of additional disk space will be used.\nGet:1 http://archive.ubuntu.com/ubuntu xenial/main amd64 librecode0 amd64 3.6-22 [523 kB]\n...\nFetched 625 kB in 0s (3250 kB/s)\n...\n```\n\nThe [`fortune`][fortune] command prints a quotation/joke such as the ones found in fortune cookies\n(hence the name):\n\n```bash\nroot@e07f81d7941d:/# /usr/games/fortune\nYour motives for doing whatever good deed you may have in mind will be\nmisinterpreted by somebody.\n```\n\nLet's create a **fortune clock script** that tells the time and a fortune every 5 seconds.\n\nThe `ubuntu` container image is very minimal, as most images are, and doesn't provide any editor\nsuch as `nano` or `vim`. You could install it as well, but for the purposes of this tutorial, we'll\ncreate the script with the basic commands available to us in the container.\n\nRun the following multiline command to save a bash script to `/usr/local/bin/clock.sh` (copy-paste\nthe entire 10 lines of the command, starting with `cat` and ending with the second `EOF` 9 lines\nbelow, then press enter to execute the whole thing):\n\n```bash\nroot@e07f81d7941d:/# cat \u003c\u003c EOF \u003e /usr/local/bin/clock.sh\n#!/bin/bash\ntrap \"exit\" SIGKILL SIGTERM SIGHUP SIGINT EXIT\nwhile true; do\n  echo It is \\$(date)\n  /usr/games/fortune\n  echo\n  sleep 5\ndone\nEOF\n```\n\nMake the script executable:\n\n```bash\nroot@e07f81d7941d:/# chmod +x /usr/local/bin/clock.sh\n```\n\nMake sure it works. Since the `/usr/local/bin` directory is in the PATH by default on Linux, you can\nsimply execute `clock.sh` without using its absolute path:\n\n```bash\nroot@e07f81d7941d:/# clock.sh\nIt is Mon Apr 23 08:47:37 UTC 2018\nYou have no real enemies.\n\nIt is Mon Apr 23 08:47:42 UTC 2018\nBeware of a dark-haired man with a loud tie.\n\nIt is Mon Apr 23 08:47:47 UTC 2018\nIf you sow your wild oats, hope for a crop failure.\n```\n\nUse Ctrl-C to stop the clock script. Then use `exit` to stop the Bash shell:\n\n```bash\nroot@e07f81d7941d:/# exit\n```\n\nSince the Bash process has exited, the container has stopped as well:\n\n```bash\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                       PORTS               NAMES\nf6b9fa680789        ubuntu              \"bash\"              13 minutes ago      Exited (130) 4 seconds ago                       goofy_shirley\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Commit a container's state to an image manually\n\nRetrieve the name or ID of the previous container, in this case `goofy_shirley`.  You can **create a\nnew image based on that container's state** with the `docker commit \u003ccontainer\u003e \u003crepository:tag\u003e`\ncommand:\n\n```bash\n$\u003e docker commit goofy_shirley fortune-clock:1.0\nsha256:407daed1a864b14a4ab071f274d3058591d2b94f061006e88b7fc821baf8232e\n```\n\nYou can see the new image in the list of images:\n\n```bash\n$\u003e docker images\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nfortune-clock       1.0                 407daed1a864        9 seconds ago       156MB\nubuntu              latest              c9d990395902        10 days ago         113MB\nhello-world         latest              e38bc07ac18e        11 days ago         1.85kB\n```\n\nThat image contains the `/usr/local/bin/clock.sh` script we created, so we can run it directly with\n`docker run \u003cimage\u003e [command...]`:\n\n```bash\n$\u003e docker run --rm fortune-clock:1.0 clock.sh\nIt is Mon Apr 23 08:55:54 UTC 2018\nYou will have good luck and overcome many hardships.\n\nIt is Mon Apr 23 08:55:59 UTC 2018\nWhile you recently had your problems on the run, they've regrouped and\nare making another attack.\n```\n\nAgain, our `clock.sh` script is a long-running command (due to the `while` loop). The container will\nkeep running until the script is stopped.  Use Ctrl-C to stop it (the container will stop and be\nremoved automatically thanks to the `--rm` option).\n\nThat's nice, but let's create a fancier version of our clock. Run a new Bash shell based on our\n`fortune-clock:1.0` image:\n\n```bash\n$\u003e docker run -it fortune-clock:1.0 bash\nroot@4b38e523336c:/#\n```\n\nThis new container is **based off of our `fortune-clock:1.0` image**, so it already contains what\nwe've done done so far, i.e. the `fortune` command is already installed and the `clock.sh` script is\nwhere we put it:\n\n```bash\nroot@4b38e523336c:/# /usr/games/fortune\nYou're working under a slight handicap.  You happen to be human.\n\nroot@4b38e523336c:/# cat /usr/local/bin/clock.sh\n#!/bin/bash\ntrap \"exit\" SIGKILL SIGTERM SIGHUP SIGINT EXIT\nwhile true; do\n  echo It is $(date)\n  /usr/games/fortune\n  echo\n  sleep 5\ndone\n```\n\nInstall the `cowsay` package:\n\n```bash\nroot@4b38e523336c:/# apt-get install -y cowsay\nReading package lists... Done\nBuilding dependency tree\nReading state information... Done\nThe following additional packages will be installed:\n  cowsay-off ifupdown iproute2 isc-dhcp-client isc-dhcp-common libatm1 libdns-export162 libgdbm3 libisc-export160 libmnl0 libperl5.22 libtext-charwidth-perl libxtables11 netbase perl perl-base\n  perl-modules-5.22 rename\nSuggested packages:\n  filters ppp rdnssd iproute2-doc resolvconf avahi-autoipd isc-dhcp-client-ddns apparmor perl-doc libterm-readline-gnu-perl | libterm-readline-perl-perl make\nThe following NEW packages will be installed:\n  cowsay cowsay-off ifupdown iproute2 isc-dhcp-client isc-dhcp-common libatm1 libdns-export162 libgdbm3 libisc-export160 libmnl0 libperl5.22 libtext-charwidth-perl libxtables11 netbase perl perl-modules-5.22\n  rename\nThe following packages will be upgraded:\n  perl-base\n1 upgraded, 18 newly installed, 0 to remove and 4 not upgraded.\nNeed to get 9432 kB of archives.\nAfter this operation, 44.8 MB of additional disk space will be used.\nGet:1 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 perl-base amd64 5.22.1-9ubuntu0.3 [1286 kB]\n...\nFetched 9432 kB in 0s (17.5 MB/s)\n...\n```\n\nOverwrite the clock script with this multiline command. The output of the `fortune` command is now\npiped into the `cowsay` command:\n\n```bash\nroot@4b38e523336c:/# cat \u003c\u003c EOF \u003e /usr/local/bin/clock.sh\n#!/bin/bash\ntrap \"exit\" SIGKILL SIGTERM SIGHUP SIGINT EXIT\nwhile true; do\n  echo It is \\$(date)\n  /usr/games/fortune | /usr/games/cowsay\n  echo\n  sleep 5\ndone\nEOF\n```\n\nTest our improved clock script:\n\n```bash\nroot@4b38e523336c:/# clock.sh\nIt is Mon Apr 23 09:02:21 UTC 2018\n ____________________________________\n/ Look afar and see the end from the \\\n\\ beginning.                         /\n ------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n\nIt is Mon Apr 23 09:02:26 UTC 2018\n _______________________________________\n/ One of the most striking differences  \\\n| between a cat and a lie is that a cat |\n| has only nine lives.                  |\n|                                       |\n| -- Mark Twain, \"Pudd'nhead Wilson's   |\n\\ Calendar\"                             /\n ---------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n\nIt is Tue Jun 14 21:58:18 UTC 2018\n _______________________________________\n/ I just broke a chair, did you know?   \\\n|                                       |\n\\ -- Anonymous Iranian girl in Sweden   /\n ---------------------------------------\n        \\   ^__^\n         \\  (^^)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n```\n\nMuch better. Exit Bash to stop the container:\n\n```bash\nroot@4b38e523336c:/# exit\n```\n\nYou should now have two stopped containers. The one in which we created the original clock script, and\nthe newest one we just stopped:\n\n```bash\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES\n4b38e523336c        fortune-clock:1.0   \"bash\"              4 minutes ago       Exited (130) 21 seconds ago                       peaceful_turing\nf6b9fa680789        ubuntu              \"bash\"              27 minutes ago      Exited (130) 13 minutes ago                       goofy_shirley\n```\n\nLet's create an image from that latest container, in this case `peaceful_turing`:\n\n```bash\n$\u003e docker commit peaceful_turing fortune-clock:2.0\nsha256:92bfbc9e4c4c68a8427a9c00f26aadb6f7112b41db19a53d4b29d1d6f68de25f\n```\n\nAs before, the image is available in the list of images:\n\n```bash\n$\u003e docker images\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nfortune-clock       2.0                 92bfbc9e4c4c        24 seconds ago      205MB\nfortune-clock       1.0                 407daed1a864        11 minutes ago      156MB\nubuntu              latest              c9d990395902        10 days ago         113MB\nhello-world         latest              e38bc07ac18e        11 days ago         1.85kB\n```\n\nYou can run it:\n\n```bash\n$\u003e docker run --rm fortune-clock:2.0 clock.sh\nIt is Mon Apr 23 09:06:21 UTC 2018\n ________________________________________\n/ Living your life is a task so          \\\n| difficult, it has never been attempted |\n\\ before.                                /\n ----------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n```\n\nUse Ctrl-C to stop the script (and the container).\n\nYour **previous image is still available** under the `1.0` tag.  You can run it again:\n\n```bash\n$\u003e docker run --rm fortune-clock:1.0 clock.sh\nIt is Mon Apr 23 09:08:04 UTC 2018\nYou attempt things that you do not even plan because of your extreme stupidity.\n```\n\nUse Ctrl-C to stop the script (and the container).\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Run containers in the background\n\nUntil now we've only run a container command **in the foreground**, meaning that Docker takes\ncontrol of our console and forwards the script's output to it.\n\nYou can run a container command **in the background** by adding the `-d` or `--detach` option.\nLet's also use the `--name` option to give it a specific name instead of using the default randomly\ngenerated one:\n\n```bash\n$\u003e docker run -d --name clock fortune-clock:2.0 clock.sh\n06eb72c218051c77148a95268a2be45a57379c330ac75a7260c16f89040279e6\n```\n\nThis time, the `docker run` command simply prints the ID of the container it has launched, and exits\nimmediately.  But you can see that the container is indeed running with `docker ps`, and that it has\nthe correct name:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n06eb72c21805        fortune-clock:2.0   \"clock.sh\"          6 seconds ago       Up 9 seconds                            clock\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Access container logs\n\nYou can use the `docker logs \u003ccontainer\u003e` command to see the **output of a container running in the\nbackground**:\n\n```bash\n$\u003e docker logs clock\nIt is Mon Apr 23 09:12:06 UTC 2018\n _____________________________________\n\u003c Excellent day to have a rotten day. \u003e\n -------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n...\n```\n\nAdd the `-f` option to keep following the log **output in real time**:\n\n```bash\n$\u003e docker logs -f clock\nIt is Mon Apr 23 09:13:36 UTC 2018\n _________________________________________\n/ I have never let my schooling interfere \\\n| with my education.                      |\n|                                         |\n\\ -- Mark Twain                           /\n -----------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n```\n\nUse Ctrl-C to stop following the logs.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Stop and restart containers\n\nYou may **stop a container running in the background** with the `docker stop \u003ccontainer\u003e` command:\n\n```bash\n$\u003e docker stop clock\nclock\n```\n\nYou can check that is has indeed stopped:\n\n```bash\n$\u003e docker ps -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES\n06eb72c21805        fortune-clock:2.0   \"clock.sh\"          2 minutes ago       Exited (0) 1 second ago                           clock\n4b38e523336c        fortune-clock:1.0   \"bash\"              17 minutes ago      Exited (130) 13 minutes ago                       peaceful_turing\nf6b9fa680789        ubuntu              \"bash\"              40 minutes ago      Exited (130) 27 minutes ago                       goofy_shirley\n```\n\nYou can **restart** it with the `docker start \u003ccontainer\u003e` command. This will **re-execute the\ncommand** that was originally given to `docker run \u003ccontainer\u003e [command...]`, in this case\n`clock.sh`:\n\n```bash\n$\u003e docker start clock\nclock\n```\n\nIt's running again:\n\n```bash\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n06eb72c21805        fortune-clock:2.0   \"clock.sh\"          3 minutes ago       Up 1 second                             clock\n```\n\nYou can follow its logs again:\n\n```bash\n$\u003e docker logs -f clock\nIt is Mon Apr 23 09:14:50 UTC 2018\n _________________________________\n\u003c So you're back... about time... \u003e\n ---------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n```\n\nStop following the logs with Ctrl-C.\n\nYou can **stop and remove** a container in one command by adding the `-f` or `--force` option to\n`docker rm`. Beware that it will *not ask for confirmation*:\n\n```bash\n$\u003e docker rm -f clock\nclock\n```\n\nNo containers should be running any more:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Run multiple containers\n\nSince containers have isolated processes, networks and file systems, you can of course run more than\none at the same time:\n\n```bash\n$\u003e docker run -d --name old-clock fortune-clock:1.0 clock.sh\n25c9016ce01f93c3e073b568e256ae7f70223f6abd47bb6f4b31606e16a9c11e\n\n$\u003e docker run -d --name new-clock fortune-clock:2.0 clock.sh\n4e367ffdda9829482734038d3eb71136d38320b6171dda31a5b287a66ee4b023\n```\n\nYou can see that both are indeed running:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n4e367ffdda98        fortune-clock:2.0   \"clock.sh\"          20 seconds ago      Up 24 seconds                           new-clock\n25c9016ce01f        fortune-clock:1.0   \"clock.sh\"          23 seconds ago      Up 27 seconds                           old-clock\n```\n\nEach container is running based on the correct image, as you can see by their output:\n\n```bash\n$\u003e docker logs old-clock\nIt is Mon Apr 23 09:39:18 UTC 2018\nToo much is just enough.\n                -- Mark Twain, on whiskey\n...\n\n$\u003e docker logs new-clock\nIt is Mon Apr 23 09:40:36 UTC 2018\n ____________________________________\n/ You have many friends and very few \\\n\\ living enemies.                    /\n ------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n...\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Image layers\n\nA Docker **image** is built up from a series of **layers**. Each layer contains a set of differences\nfrom the layer before it:\n\n![Docker: Image Layers](images/layers.jpg)\n\nYou can list those layers by using the `docker inspect` command with an image name or ID. Let's see\nwhat layers the `ubuntu` image has:\n\n```bash\n$\u003e docker inspect ubuntu\n...\n        \"RootFS\": {\n            \"Type\": \"layers\",\n            \"Layers\": [\n                \"sha256:fccbfa2912f0cd6b9d13f91f288f112a2b825f3f758a4443aacb45bfc108cc74\",\n                \"sha256:e1a9a6284d0d24d8194ac84b372619e75cd35a46866b74925b7274c7056561e4\",\n                \"sha256:ac7299292f8b2f710d3b911c6a4e02ae8f06792e39822e097f9c4e9c2672b32d\",\n                \"sha256:a5e66470b2812e91798db36eb103c1f1e135bbe167e4b2ad5ba425b8db98ee8d\",\n                \"sha256:a8de0e025d94b33db3542e1e8ce58829144b30c6cd1fff057eec55b1491933c3\"\n            ]\n        }\n...\n```\n\nEach layer is identified by a hash based on previous layer's hash and the state when the layer was\ncreated. This is similar to commit hashes in a Git repository.\n\nLet's check the layers of our first `fortune-clock:1.0` image:\n\n```bash\n$\u003e docker inspect fortune-clock:1.0\n...\n        \"RootFS\": {\n            \"Type\": \"layers\",\n            \"Layers\": [\n                \"sha256:fccbfa2912f0cd6b9d13f91f288f112a2b825f3f758a4443aacb45bfc108cc74\",\n                \"sha256:e1a9a6284d0d24d8194ac84b372619e75cd35a46866b74925b7274c7056561e4\",\n                \"sha256:ac7299292f8b2f710d3b911c6a4e02ae8f06792e39822e097f9c4e9c2672b32d\",\n                \"sha256:a5e66470b2812e91798db36eb103c1f1e135bbe167e4b2ad5ba425b8db98ee8d\",\n                \"sha256:a8de0e025d94b33db3542e1e8ce58829144b30c6cd1fff057eec55b1491933c3\",\n                \"sha256:3539c048e45e973cf477148af3c9c91885950e77d10e77e1db1097bd20b16129\"\n            ]\n        },\n...\n```\n\nNote that the layers are the same as the `ubuntu` image, with an additional one at the end (starting\nwith `3539c048e`). This additional layer contains the changes we made compared to the original\n`ubuntu` image, i.e.:\n\n* Update the package lists with `apt-get update`\n* Install the `fortune` package with `apt-get install`\n* Create the `/usr/local/bin/clock.sh` script\n\nThe new hash (starting with `3539c048e`) is based both on these changes and the previous hash\n(starting with `a8de0e025`), and it **uniquely identifies this layer**.\n\nTake a look at the layers of our second `fortune-clock:2.0` image:\n\n```bash\n$\u003e docker inspect fortune-clock:2.0\n...\n        \"RootFS\": {\n            \"Type\": \"layers\",\n            \"Layers\": [\n                \"sha256:fccbfa2912f0cd6b9d13f91f288f112a2b825f3f758a4443aacb45bfc108cc74\",\n                \"sha256:e1a9a6284d0d24d8194ac84b372619e75cd35a46866b74925b7274c7056561e4\",\n                \"sha256:ac7299292f8b2f710d3b911c6a4e02ae8f06792e39822e097f9c4e9c2672b32d\",\n                \"sha256:a5e66470b2812e91798db36eb103c1f1e135bbe167e4b2ad5ba425b8db98ee8d\",\n                \"sha256:a8de0e025d94b33db3542e1e8ce58829144b30c6cd1fff057eec55b1491933c3\",\n                \"sha256:3539c048e45e973cf477148af3c9c91885950e77d10e77e1db1097bd20b16129\",\n                \"sha256:5dbae8ae43cba9e34980fd6076156503814c916453a5582646a6a34c02c68546\"\n            ]\n        },\n...\n```\n\nAgain, we see the same layers, including the `3539c048e` layer from the `fortune-clock:1.0` image,\nand an additional layer (starting with `5dbae8ae4`). This layer contains the following changes we\nmade based on the `fortune-clock:1.0` image:\n\n* Install the `cowsay` package with `apt-get install`\n* Overwrite the `/usr/local/bin/clock.sh` script\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n#### The top writable layer of containers\n\nWhen you create a new container, you add a new **writable layer** on top of the image's underlying\nlayers. All changes made to the running container (i.e. creating, modifying, deleting files) are\nwritten to this thin writable container layer.  When the container is deleted, the writable layer is\nalso deleted, unless it was committed to an image.\n\nThe layers belonging to the image used as a base for your container are never modified–they are\n**read-only**. Docker uses a [union file system][union-fs] and a [copy-on-write strategy][cow] to\nmake it work:\n\n* When you read a file, the union file system will look in all layers, from newest to oldest, and\n  return the first version it finds.\n* When you write to a file, the union file system will look for an older version, copy it to the top\n  writable layer, and modify that copied version. Previous version(s) of the file in older layers\n  still exist, but are \"hidden\" by the file system; only the most recent version is seen.\n\nMultiple containers can therefore use the same read-only image layers, as they only modify their own\nwritable top layer:\n\n![Docker: Sharing Layers](images/sharing-layers.jpg)\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n#### Total image size\n\nWhat we've just learned about layers has several implications:\n\n* You **cannot delete files from previous layers to reduce total image size**. Assume that an\n  image's last layer contains a 1GB file. Creating a new container from that image, deleting that\n  file, and saving that state as a new image will not reclaim that gigabyte. The total image size\n  will still be the same, as the file is still present in the previous layer.\n\n  This is also similar to a Git repository, where committing a file deletion does not reclaim its\n  space from the repository's object database (as the file is still referenced by previous commits\n  in the history).\n* Since layers are read-only and incrementally built based on previous layers, **the size of common\n  layers shared by several images is only taken up once**.\n\n  If you take a look at the output of `docker images`, naively adding the displayed sizes adds up to\n  447MB, but that is **not** the size that is actually occupied by these images on your file system.\n\n  ```bash\n  $\u003e docker images\n  REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\n  fortune-clock       2.0                 92bfbc9e4c4c        2 hours ago         205MB\n  fortune-clock       1.0                 407daed1a864        2 hours ago         156MB\n  ubuntu              latest              c9d990395902        10 days ago         113MB\n  hello-world         latest              e38bc07ac18e        11 days ago         1.85kB\n  ```\n\n  Let's add the `-s` or `--size` option to `docker ps` to display the size of our containers' file\n  systems:\n\n  ```bash\n  $\u003e docker ps -as\n  CONTAINER ID   IMAGE               COMMAND      CREATED             STATUS                     PORTS   NAMES                    SIZE\n  4e367ffdda98   fortune-clock:2.0   \"clock.sh\"   About an hour ago   Up About an hour                   new-clock                0B (virtual 205MB)\n  25c9016ce01f   fortune-clock:1.0   \"clock.sh\"   About an hour ago   Up About an hour                   old-clock                0B (virtual 156MB)\n  4b38e523336c   fortune-clock:1.0   \"bash\"       2 hours ago         Exited (130) 2 hours ago           peaceful_turing          48.7MB (virtual 205MB)\n  f6b9fa680789   ubuntu              \"bash\"       2 hours ago         Exited (130) 2 hours ago           goofy_shirley            43MB (virtual 156MB)\n  ```\n\n  The `SIZE` column shows the size of the top writable container layer, and the total virtual size\n  of all the layers (including the top one) in parentheses.  If you look at the virual sizes, you\n  can see that:\n\n  * The virtual size of the `goofy_shirley` container is 156MB, which corresponds to the size of the\n    `fortune-clock:1.0` image, since we committed that image based on that container's state.\n  * Similarly, the virtual size of the `peaceful_turing` container is 205MB, which corresponds to\n    the size of the `fortune-clock:2.0` image.\n  * The `old-clock` and `new-clock` containers also have the same virtual sizes since they are based\n    on the same images.\n\n  Taking a look at the sizes of the top writable container layers, we can see that:\n\n  * The size of the `goofy_shirley` container's top layer is 43MB. This corresponds to the space\n    taken up by the package lists, the `fortune` package and its dependencies, and the `clock.sh`\n    script.\n\n    The virtual size of 156MB corresponds to the 113MB of the `ubuntu` base image, plus the 43MB of\n    the top layer. As we've seen above, this is also the size of the `fortune-clock:1.0` image.\n  * The size of the `peaceful_turing` container's top layer is 48.7MB. This corresponds to the space\n    taken up by the `cowsay` package and its dependencies, and the new version of the `clock.sh`\n    script.\n\n    The virtual size of 205MB corresponds to the 156MB of the `fortune-clock:1.0` base image, plus\n    the 48.7MB of the top layer. As we've seen above, this is also the size of the\n    `fortune-clock:2.0` image.\n  * The size of the `old-clock` and `new-clock` containers' top layers is 0 bytes, since no file was\n    modified in these containers. Their virtual size correspond to their base images' size.\n\n  Using all that we've learned, we can determine the total size taken up on your machine's file\n  system:\n\n  * The 113MB of the `ubuntu` image's layers, even though they are used by 3 images (the `ubuntu`\n    image itself and the `fortune-clock:1.0` and `fortune-clock:2.0` images), are taken up only\n    once.\n  * Similarly, the 43MB of the `fortune-clock:1.0` image's additional layer are taken up only once,\n    even though the layer is used by 2 images (the `fortune-clock:1.0` image itself and the\n    `fortune-clock:2.0` image).\n  * Finally, the 48.7MB of the `fortune-clock:2.0` image's additional layer are also taken up once.\n\n  Therefore, the `ubuntu`, `fortune-clock:1.0` and `fortune-clock:2.0` images take up only 205MB of\n  space on your file system, not 447MB. Basically, it's the same size as the `fortune-clock:2.0`\n  image, since it re-uses the `fortune-clock:1.0` and `ubuntu` images' layers, and the\n  `fortune-clock:1.0` image also re-uses the `ubuntu` image's layers.\n\n  The `hello-world` image takes up an additional 1.85kB on your file system, since it has no layers\n  in common with any of the other images.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Dockerfile\n\nManually starting containers, making changes and committing images is all well and good, but is\n**prone to errors and not reproducible**.\n\nDocker can build images automatically by reading the instructions from a [Dockerfile][dockerfile]. A\nDockerfile is a text document that contains all the commands a user could call on the command line\nto assemble an image. Using the `docker build` command, users can create an **automated build** that\nexecutes several command line instructions in succession.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n### The `docker build` command\n\nThis `docker build \u003ccontext\u003e` command builds an image from a **Dockerfile** and a **context**. The\nbuild's context is the set of files at a specified path on your file system (or Git repository URL).\nFor example, running `docker build /foo` would expect to find a Dockerfile at the path\n`/foo/Dockerfile`, and would use the entire contents of the `/foo` directory as the build context.\n\nThe build is run by the Docker daemon, not by the CLI. The first thing a build process does is\n**send the entire context (recursively) to the daemon**. In most cases, it's best to start with an\nempty directory as context and keep your Dockerfile in that directory. Add only the files needed for\nbuilding the Dockerfile.\n\n**Warning:** do not use your root directory, `/`, as the build context as it causes the build to\ntransfer the entire contents of your hard drive to the Docker daemon.\n\nTo ignore some files in the build context, use a [`.dockerignore` file][docker-ignore] (similar to a\n`.gitignore` file).\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Format\n\nThe format of a Dockerfile is:\n\n```\n# Comment\nINSTRUCTION arguments...\nINSTRUCTION arguments...\n```\n\nYou can find all available instructions, such as `FROM` and `RUN`, in the [Dockerfile\nreference][dockerfile]. Many correspond to arguments or options of the Docker commands that we've\nused. For example, the `FROM` instruction corresponds to the `\u003cimage\u003e` argument of the `docker run\n\u003cimage\u003e [command...]` command, and specifies what base image to use.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Build an image from a Dockerfile\n\nClone or download this repository, then move to its `fortune-clock` directory in your console. You\nwill see that it contains the same clock script we used in the `fortune-clock:2.0` image, and a\nDockerfile:\n\n```bash\n$\u003e cd fortune-clock\n\n$\u003e ls\nDockerfile clock.sh\n```\n\nThe Dockerfile looks like this:\n\n```\nFROM ubuntu\n\nRUN apt-get update\nRUN apt-get install -y fortune\nRUN apt-get install -y cowsay\nCOPY clock.sh /usr/local/bin/clock.sh\nRUN chmod +x /usr/local/bin/clock.sh\n```\n\nIt basically **replicates what we have done manually**:\n\n* The `FROM ubuntu` instruction starts the build process from the `ubuntu` base image.\n* The `RUN apt-get update` instruction executes the `apt-get update` command like we did before.\n* The next two `RUN` instructions install the `fortune` and `cowsay` packages, also like we did\n  before.\n* The `COPY \u003csrc\u003e \u003cdest\u003e` instruction copies a file from the build context into the file system of\n  the container.  In this case, we copy the `clock.sh` file in the build context to the\n  `/usr/local/bin/clock.sh` path in the container. When we run the build command, we will specify\n  the `fortune-clock` directory of this repository as the build context, so that its `clock.sh` file\n  is copied to the container.\n* The final `RUN` instruction makes the script executable.\n\nRemain in the `fortune-clock` directory and run the following build command.  The `-t` or `--tag\n\u003crepo:tag\u003e` option indicates that we want to tag the image like we did when we were using the\n`docker commit \u003ccontainer\u003e \u003crepo:tag\u003e` command. The last argument, `.`, indicates that the build\ncontext is the current directory:\n\n```bash\n$\u003e docker build -t fortune-clock:3.0 .\nSending build context to Docker daemon  3.072kB\nStep 1/6 : FROM ubuntu\n ---\u003e c9d990395902\nStep 2/6 : RUN apt-get update\n ---\u003e Running in 6763a261b156\n...\nRemoving intermediate container 6763a261b156\n ---\u003e e131accf3a09\nStep 3/6 : RUN apt-get install -y fortune\n ---\u003e Running in a77bf3a72ded\n...\nRemoving intermediate container a77bf3a72ded\n ---\u003e 56969308655b\nStep 4/6 : RUN apt-get install -y cowsay\n ---\u003e Running in b7ab94de15e3\n...\nRemoving intermediate container b7ab94de15e3\n ---\u003e 844fbb19235c\nStep 5/6 : COPY clock.sh /usr/local/bin/clock.sh\n ---\u003e 9ff5a0b9ba2b\nStep 6/6 : RUN chmod +x /usr/local/bin/clock.sh\n ---\u003e Running in 6066a5e6a121\nRemoving intermediate container 6066a5e6a121\n ---\u003e bcf88ef22f4c\nSuccessfully built bcf88ef22f4c\nSuccessfully tagged fortune-clock:3.0\n```\n\nAs you can see, Docker:\n\n* **Uploaded to build context** (i.e. the contents of the `fortune-clock` directory) to the Docker\n  deamon.\n* **Ran each instruction** in the Dockerfile **one by one**, creating an intermediate container each\n  time, based on the previous state.\n* **Created an image** with the final state, and the specified tag (i.e. `fortune-clock:3.0`).\n\nYou can see that new image in the list of images:\n\n```bash\n$\u003e docker images\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nfortune-clock       3.0                 bcf88ef22f4c        6 minutes ago       205MB\nfortune-clock       2.0                 92bfbc9e4c4c        3 hours ago         205MB\nfortune-clock       1.0                 407daed1a864        3 hours ago         156MB\nubuntu              latest              c9d990395902        10 days ago         113MB\nhello-world         latest              e38bc07ac18e        11 days ago         1.85kB\n```\n\nYou can also run a container based on it like we did before:\n\n```bash\n$\u003e docker run --rm fortune-clock:3.0 clock.sh\nIt is Mon Apr 23 12:10:16 UTC 2018\n _____________________________________\n/ Today is National Existential Ennui \\\n\\ Awareness Day.                      /\n -------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n```\n\nUse Ctrl-C to stop it.\n\nLet's take a look at that new image's layers:\n\n```bash\n$\u003e docker inspect fortune-clock:3.0\n...\n        \"RootFS\": {\n            \"Type\": \"layers\",\n            \"Layers\": [\n                \"sha256:fccbfa2912f0cd6b9d13f91f288f112a2b825f3f758a4443aacb45bfc108cc74\",\n                \"sha256:e1a9a6284d0d24d8194ac84b372619e75cd35a46866b74925b7274c7056561e4\",\n                \"sha256:ac7299292f8b2f710d3b911c6a4e02ae8f06792e39822e097f9c4e9c2672b32d\",\n                \"sha256:a5e66470b2812e91798db36eb103c1f1e135bbe167e4b2ad5ba425b8db98ee8d\",\n                \"sha256:a8de0e025d94b33db3542e1e8ce58829144b30c6cd1fff057eec55b1491933c3\",\n                \"sha256:89d694b3a7a4bd236c161894c5aae7d6f86ec3e42c6b4a71032774e60d2d8e4a\",\n                \"sha256:a96e0ea28068686e2bafd181958407d722e179ea5cc3138be5b130424f4925f1\",\n                \"sha256:d57cb59abf79bbe2e4150450574110b862379ecf44d3a23956a965477a0a1848\",\n                \"sha256:0b34384d1bf850620090e95b07cfcf2d552ec869b8f0ea574d8ae81acc2334d2\",\n                \"sha256:a9b1e603de7dd53e2d6adcf3538d454a350e1384a4311b6d686151fabfa450fb\"\n            ]\n        },\n...\n```\n\nThe first few layers (up to the one starting with `a8de0e025`) are the same as before, since they\nare the `ubuntu` image's base layers. The last 5 layers, however, are new.\n\nBasically, Docker created **one layer for each instruction in the Dockerfile**. Since we have 4\n`RUN` instructions and 1 `COPY` instruction in the Dockerfile we used, there are 5 additional\nlayers.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Build cache\n\nRe-run the same build command:\n\n```bash\n$\u003e docker build -t fortune-clock:3.0 .\nSending build context to Docker daemon  3.072kB\nStep 1/6 : FROM ubuntu\n ---\u003e c9d990395902\nStep 2/6 : RUN apt-get update\n ---\u003e Using cache\n ---\u003e e131accf3a09\nStep 3/6 : RUN apt-get install -y fortune\n ---\u003e Using cache\n ---\u003e 56969308655b\nStep 4/6 : RUN apt-get install -y cowsay\n ---\u003e Using cache\n ---\u003e 844fbb19235c\nStep 5/6 : COPY clock.sh /usr/local/bin/clock.sh\n ---\u003e Using cache\n ---\u003e 9ff5a0b9ba2b\nStep 6/6 : RUN chmod +x /usr/local/bin/clock.sh\n ---\u003e Using cache\n ---\u003e bcf88ef22f4c\nSuccessfully built bcf88ef22f4c\nSuccessfully tagged fortune-clock:3.0\n```\n\nIt was much faster this time. As you can see by the `Using cache` indications in the output, Docker\nis keeping a **cache of the previously built layers**. Since you have not changed the instructions\nin the Dockerfile or the build context, it assumes that the result will be the same and reuses the\nsame layer.\n\nMake a change to the `clock.sh` script in the `fortune-clock` directory. For example, add a new line\nor a comment:\n\n```bash\n#!/bin/bash\ntrap \"exit\" SIGKILL SIGTERM SIGHUP SIGINT EXIT\n\n# TODO: add new lines or a comment here!\n\n# Print the date and a fortune every 5 seconds.\nwhile true; do\n  echo It is $(date)\n  /usr/games/fortune | /usr/games/cowsay\n  echo\n  sleep 5\ndone\n```\n\nRe-run the same build command:\n\n```bash\n$\u003e docker build -t fortune-clock:3.0 .\nSending build context to Docker daemon  3.072kB\nStep 1/6 : FROM ubuntu\n ---\u003e c9d990395902\nStep 2/6 : RUN apt-get update\n ---\u003e Using cache\n ---\u003e e131accf3a09\nStep 3/6 : RUN apt-get install -y fortune\n ---\u003e Using cache\n ---\u003e 56969308655b\nStep 4/6 : RUN apt-get install -y cowsay\n ---\u003e Using cache\n ---\u003e 844fbb19235c\nStep 5/6 : COPY clock.sh /usr/local/bin/clock.sh\n ---\u003e 8575c9e05b3c\nStep 6/6 : RUN chmod +x /usr/local/bin/clock.sh\n ---\u003e Running in 037dc123faaa\nRemoving intermediate container 037dc123faaa\n ---\u003e 99c24c7e3c1c\nSuccessfully built 99c24c7e3c1c\nSuccessfully tagged fortune-clock:3.0\n```\n\nDocker is still using its cache for the first 3 commands (the `apt-get update` and the installation\nof the `fortune` and `cowsay` packages), since they are executed before the `clock.sh` script is\ncopied, and are therefore not affected by the change.\n\nThe `COPY` instruction is executed **without cache**, however, since Docker detects that **the\n`clock.sh` script has changed**.\n\nConsequently, **all further instructions** after that `COPY` **cannot use the cache**, since the\nstate upon which they are based has changed. Therefore, the last `RUN` instruction also does not use\nthe cache.\n\nSee [Squashing Image Layers][squashing-layers] for tips on how to minimize build time and the number\nof layers.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### A Dockerfile for a Node.js application\n\nThe `todo` directory contains a sample Node.js application to manage to-do notes.\n\nIf you wanted to run this application on your machine or on a server, you would need to:\n\n* Install and run a [MongoDB][mongo] database (version 3).\n* Install [Node.js][node] (version 8).\n* Run `npm install` in the application's directory to install its dependencies.\n* Run `npm start` in the application's directory to start it.\n* (Additionally in a production environment: set up a process manager to keep it running if it\n  crashes.)\n\nThat takes **some effort** if you don't already have the database or Node.js, or if you're not used\nto installing and managing them. It could also be problematic if you already have **different,\nnon-compatible versions** of Node.js or MongoDB installed.\n\nRunning the application in a container can solve these problems. With what we've learned so far, you\ncould write a Dockerfile that installs Node.js, installs the application's dependencies and starts\nit.\n\nHowever, don't forget that images can be shared on the [Docker hub][hub] and reused. Popular\nframeworks and languages already have official (or non-official) images you can use. For example,\nthe [`node` image][hub-node] already has Node.js installed: you only need to base your own image off\nof it and add your application's code.\n\nYou will find a minimal Dockerfile to do that in the `Dockerfile` file in the `todo` directory of\nthis repository. This is what it contains:\n\n```\nFROM node:8\n\nWORKDIR /usr/src/app\nCOPY . /usr/src/app/\nRUN npm install\n\nCMD [ \"npm\", \"start\" ]\n```\n\nHere's what the instructions are for:\n\n* `FROM node:8` instructs Docker to build from the official `node:8` image (which contains the\n  latest Node.js 8 version).\n* `WORKDIR /usr/src/app` indicates the working directory in which commands are run (e.g. when using\n  a `RUN` instruction). Why use the `/usr/src/app` directory? It's simply a popular convention for\n  the directory of the application in Docker containers.\n* `COPY . /usr/src/app` copies the entire contents of the build context to the `/usr/src/app`\n  directory in the container.\n* `RUN npm install` executes an `npm install` command to install the application's dependencies. Due\n  to the previous `WORKDIR` instruction, this is executed in the `/usr/src/app` directory of the\n  container.\n* `CMD [ \"npm\", \"start\" ]` indicates the default command that will be executed when running this\n  container. This is equivalent to the arguments we passed to `docker run \u003cimage\u003e [command...]`. If\n  a default command is specified in the image with `CMD`, you can simply use `docker run \u003cimage\u003e` to\n  run that command.\n\nMove to the `todo` directory and build a new image based on that Dockerfile:\n\n```bash\n$\u003e cd todo\n\n$\u003e docker build -t todo .\nSending build context to Docker daemon  60.93kB\nStep 1/5 : FROM node:8\n ---\u003e 4635bc7d130c\nStep 2/5 : WORKDIR /usr/src/app\n ---\u003e Using cache\n ---\u003e 9e5af697d233\nStep 3/5 : COPY . /usr/src/app/\n ---\u003e 0c7a80e4fedc\nStep 4/5 : RUN npm install\n ---\u003e Running in 0122811036db\nadded 142 packages in 2.822s\nRemoving intermediate container 0122811036db\n ---\u003e 13546c3ce198\nStep 5/5 : CMD [ \"npm\", \"start\" ]\n ---\u003e Running in 8becf1bd710d\nRemoving intermediate container 8becf1bd710d\n ---\u003e a8dc25bf2972\nSuccessfully built a8dc25bf2972\nSuccessfully tagged todo:latest\n```\n\nYou can now attempt to run the application:\n\n```bash\n$\u003e docker run --rm todo\n\n\u003e todo@0.0.0 start /usr/src/app\n\u003e node ./bin/www\n\n{ MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]\n    ... }\nnpm ERR! code ELIFECYCLE\nnpm ERR! errno 1\nnpm ERR! todo@0.0.0 start: `node ./bin/www`\nnpm ERR! Exit status 1\nnpm ERR!\nnpm ERR! Failed at the todo@0.0.0 start script.\nnpm ERR! This is probably not a problem with npm. There is likely additional logging output above.\n\nnpm ERR! A complete log of this run can be found in:\nnpm ERR!     /root/.npm/_logs/2018-04-23T15_27_29_784Z-debug.log\n```\n\nIt doesn't work because it attempts to connect to a MongoDB database on `localhost:27017` and there\nis no such thing. Even if you do actually have a MongoDB database running on your machine on that\nport for development, remember that each container has its own **isolated network stack**, so it\ncan't reach services listening on your host machine's ports.\n\nWe will run a database in the next section.\n\nSee [Dockerfile Tips][dockerfile-tips] for more information and good practices concerning\nDockerfiles.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Connecting containers\n\nAs we've seen, containers are **isolated by default**. Our Node.js application cannot run because it\ncannot reach a database. If your application's environment was a virtual machine, your next step may\nbe to install and run a MongoDB server on that same virtual machine. You could add instructions to\nthe Dockerfile to do the same in the container we've built so far, but that would not be \"the Docker\nway\".\n\nDocker best practices suggest that **each container should have only one concern**.  Decoupling\napplications into multiple containers makes it much easier to **scale horizontally** and **reuse\ncontainers**. For instance, a web application stack might consist of three separate containers, each\nwith its own unique image, to manage the web application, database, and an in-memory cache in a\ndecoupled manner.\n\n(You may have heard that there should be \"one process per container\". While this mantra has good\nintentions, it is not necessarily true that there should be only one operating system process per\ncontainer. In addition to the fact that containers can now be spawned with an init process, some\nprograms might spawn additional processes of their own accord. For instance, Celery can spawn\nmultiple worker processes, or Apache might create a process per request. While \"one process per\ncontainer\" is frequently a good rule of thumb, it is not a hard and fast rule. Use your best\njudgment to keep containers as clean and modular as possible.)\n\nFor our Node.js application, we will therefore run **2 containers**:\n\n* 1 container to run a MongoDB server.\n* 1 container to run the Node.js application.\n\nThis will make it easy to, for example, horizontally scale our application by running more than 1\nNode.js application container, while keeping only 1 MongoDB server container.\n\n(It would also be possible to run more than 1 MongoDB server container, but that would be more\ncomplicated as it would require the configuration of [MongoDB replication][mongo-replication]. That\nis outside the scope of this tutorial. Multiple application containers, however, can easily talk to\nthe same database with no additional configuration.)\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Exposing container ports on the host machine\n\nWe will use the [official `mongo` image][hub-mongo] on Docker hub to **run a MongoDB database\ncontainer** for our application. [Its Dockerfile][hub-mongo-dockerfile] is more complex than the one\nwe've used as an example, but you should now easily understand what it does on principle (i.e.\ninstall and configure what is needed to run a MongoDB server and run the appropriate command to\nstart it).\n\nRun a MongoDB 3+ container named `db` with the following command:\n\n```bash\n$\u003e docker run --name db --rm mongo:3\nUnable to find image 'mongo:3' locally\n3: Pulling from library/mongo\n...\nDigest: sha256:670f9ea4f85f7e188cb0f572261feb1f2e170ee593ff3981474395e145a0c062\nStatus: Downloaded newer image for mongo:3\n2018-04-25T08:41:21.614+0000 I CONTROL  [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=608a8a274da2\n...\n2018-04-25T08:41:22.096+0000 I NETWORK  [initandlisten] waiting for connections on port 27017\n```\n\nIf you open another command line console, you can inspect that container and find its IP address:\n\n```bash\n$\u003e docker inspect db\n...\n    \"Networks\": {\n        \"bridge\": {\n            \"IPAMConfig\": null,\n            \"Links\": null,\n            \"Aliases\": null,\n            \"NetworkID\": \"4156a5216eed6ee7715a18fed1586620fc6ec87fb7ddc6c7f15032f640816850\",\n            \"EndpointID\": \"ffd645200b204475441262772f4b4cbb2d7a2209aaa39df19e7a966931e915e2\",\n            \"Gateway\": \"172.17.0.1\",\n            \"IPAddress\": \"172.17.0.2\",\n            \"IPPrefixLen\": 16,\n            \"IPv6Gateway\": \"\",\n            \"GlobalIPv6Address\": \"\",\n            \"GlobalIPv6PrefixLen\": 0,\n            \"MacAddress\": \"02:42:ac:11:00:02\",\n            \"DriverOpts\": null\n        }\n    }\n...\n```\n\nOn a Linux host machine, you could connect to that IP address directly on port 27017 to reach the\ndatabase. (It might not work on macOS \u0026 Windows machines because some Docker installations use an\nintermediate Linux virtual machine to run the containers, so your machine might not actually be the\nhost Docker machine.)\n\nHowever, **that IP address is not predictable** so that's not a good solution. You can **expose a\ncontainer's port on your host machine** by adding the `-p` or `--publish \u003chostPort:containerPort\u003e`\noption to the `docker run` command.\n\nStop the MongoDB container with Ctrl-C and start another one with this command:\n\n```bash\n$\u003e docker run --name db -p 5000:27017 --rm mongo:3\n...\n2018-04-25T08:41:22.096+0000 I NETWORK  [initandlisten] waiting for connections on port 27017\n```\n\nThe above command publishes the container's 27017 port to your host machine's 5000 port. If you have\na MongoDB client on your host machine, you can now connect to the database with the following\ncommand:\n\n```bash\n$\u003e mongo localhost:5000\nMongoDB shell version v3.6.3\nconnecting to: mongodb://localhost:5000/test\nMongoDB server version: 3.6.4\n...\n\u003e\n```\n\nExit the MongoDB shell with `exit`. Stop the MongoDB container with Ctrl-C.\n\nThis works, but we can't use this method to connect our Node.js application container to our MongoDB\nserver container. You can reach any container from the host machine, but **containers cannot\nreach your host machine's ports**.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Docker networks\n\nYou can create **networks** to break the isolation between containers and connect them together.\nList available networks with the `docker network ls` command:\n\n```bash\n$\u003e docker network ls\nNETWORK ID          NAME                DRIVER              SCOPE\n4156a5216eed        bridge              bridge              local\nc7777321c9e3        host                host                local\ncd79f5b6d678        none                null                local\n```\n\nRead the [Docker Networking Overview][docker-networking] to learn about the different network\ndrivers such as `bridge`, `host` and `none`. The links in that article will also give more\ninformation on how Docker networks work at the OS level.\n\nFor now, know that a [**bridge network**][docker-bridge-networks] uses a software bridge which\n**allows containers connected to the same network to communicate**, while providing isolation from\ncontainers which are not connected to that network. The Docker bridge driver automatically installs\nrules in the host machine so that **containers on different bridge networks cannot communicate\ndirectly with each other**.\n\nA bridge network named `bridge` exists by default. New containers connect to it unless otherwise\nspecified. However, we will not use this default network, as **user-defined bridge networks are\nsuperior** to the default bridge network. We will see why shortly.\n\nLet's create a network for our application with the `docker network create \u003cname\u003e` command:\n\n```bash\n$\u003e docker network create todo\n15109ea2a697b8be45b02511fdc217f3707c3489cbcfdf4cebf49e968d2bc1e3\n```\n\nWe can see it in the list now:\n\n```bash\n$\u003e docker network ls\nNETWORK ID          NAME                DRIVER              SCOPE\n4156a5216eed        bridge              bridge              local\nc7777321c9e3        host                host                local\ncd79f5b6d678        none                null                local\n15109ea2a697        todo                bridge              local\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n#### Running a container in a network\n\nTo run the MongoDB server container in our user-defined `todo` network, add the `--network \u003cname\u003e`\noption to the `docker run` command. This time, we'll also run the container in the background with\nthe `-d` option:\n\n```bash\n$\u003e docker run -d --name db --network todo mongo:3\n6f6066b97321a4f333f7dc8cb4364b719bba795e6deea79e9fd796946de623cd\n```\n\nMake sure the container is running:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED                  STATUS              PORTS               NAMES\n6f6066b97321        mongo:3             \"docker-entrypoint.s…\"   Less than a second ago   Up 4 seconds        27017/tcp           db\n```\n\nIf you inspect it, you will see that it is indeed connected to the `todo` network instead of the\n`bridge` network:\n\n```bash\n$\u003e docker inspect db\n...\n    \"Networks\": {\n        \"todo\": {\n            \"IPAMConfig\": null,\n            \"Links\": null,\n            \"Aliases\": [\n                \"6f6066b97321\"\n            ],\n            \"NetworkID\": \"15109ea2a697b8be45b02511fdc217f3707c3489cbcfdf4cebf49e968d2bc1e3\",\n            \"EndpointID\": \"c4428534c06e83751a3fe10d530481888b3101645b666f75aafad21634cedc10\",\n            \"Gateway\": \"172.18.0.1\",\n            \"IPAddress\": \"172.18.0.2\",\n            \"IPPrefixLen\": 16,\n            \"IPv6Gateway\": \"\",\n            \"GlobalIPv6Address\": \"\",\n            \"GlobalIPv6PrefixLen\": 0,\n            \"MacAddress\": \"02:42:ac:12:00:02\",\n            \"DriverOpts\": null\n        }\n    }\n...\n```\n\nNow that our MongoDB server container is running, we can attempt to connect our Node.js application\nto it. Attempt to run a container based on our `todo` image as before, but this time add the\n`--network` option like you did for the other container:\n\n```bash\n$\u003e docker run --name app --network todo --rm todo\n\n\u003e todo@0.0.0 start /usr/src/app\n\u003e node ./bin/www\n\n{ MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]\n    ... }\nnpm ERR! code ELIFECYCLE\nnpm ERR! errno 1\nnpm ERR! todo@0.0.0 start: `node ./bin/www`\nnpm ERR! Exit status 1\n...\n```\n\nIt still does not work because the Node.js application still attempts to connect to its default\ndatabase address of `localhost:27017`. Fortunately, a custom database URL can be provided to the\napplication by setting the `$DATABASE_URL` environment variable. But **what address should we use?**\n\nThis is where Docker's networking magic comes into play. We mentioned earlier that user-defined\nbridge networks are superior to Docker's default bridge network. **User-defined bridges provide\nautomatic DNS resolution between containers**, i.e. containers can resolve each other by name or\nalias.\n\nSince we added the `--name db` option when running our MongoDB container, any container on the same\nnetwork can reach it at the `db` address (which is simply a host like `localhost` or `example.com`).\n\nSimply add the `-e` or `--env \u003cVARIABLE=value\u003e` option to the `docker run` command to define a new\nenvironment variable. We'll also add the `-d` option to run it in the background, and a `-p` option\nto publish its 3000 port on our host machine:\n\n```bash\n$\u003e docker run -d -e \"DATABASE_URL=mongodb://db:27017\" --name app --network todo -p 3000:3000 todo\n8e7b4d08691fa46c93afa80c6ec76a9be7bb768b699e0bdd2353df682568fe18\n```\n\nBoth our containers are now running:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES\n8e7b4d08691f        todo                \"npm start\"              3 minutes ago       Up 3 minutes        0.0.0.0:3000-\u003e3000/tcp   app\n6f6066b97321        mongo:3             \"docker-entrypoint.s…\"   16 minutes ago      Up 16 minutes       27017/tcp                db\n```\n\nThe application should be working and accessible at\n[`http://localhost:3000`](http://localhost:3000)!\n\n![To-do application](images/todo.png)\n\nPlay with it and use `docker logs app` to see that the Node.js application is indeed processing your\nrequests.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Persistent storage\n\nCreate a few to-do notes in the running application.\n\nStop both containers and restart them:\n\n```bash\n$\u003e docker stop app db\napp\ndb\n\n$\u003e docker start db\ndb\n\n$\u003e docker start app\napp\n```\n\n(Wait a few seconds after starting the `db` container before starting the `app` container. The\nNode.js application cannot start unless it successfully connects to the database. If the `app`\ncontainer doesn't start the first time, re-run the `docker start app` command. See [Dockerfile\nTips][dockerfile-tips] and [Waiting for other containers][dockerfile-tips-waiting] for a solution to\nthis problem.)\n\nThe application should again be running on [`http://localhost:3000`](http://localhost:3000), and\nyour to-do notes should still be there.\n\nNow let's see what happens if you stop *and remove* both containers:\n\n```bash\n$\u003e docker rm -f app db\napp\ndb\n```\n\nNow re-run both containers with the same commands as before:\n\n```bash\n$\u003e docker run -d --name db --network todo mongo:3\nf8afad7282fad05fb16230ef4c56a96bef969b7f3dad9c312b4a6b8c4024cd43\n\n$\u003e docker run -d -e \"DATABASE_URL=mongodb://db:27017\" --name app --network todo -p 3000:3000 todo\nb4dfaf7d0d9f4669b13dac759c4b7858b0c23537a31183c7c757b8c9d48825b8\n```\n\nNew container instances should be running:\n\n```bash\n$\u003e docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES\nb4dfaf7d0d9f        todo                \"npm start\"              7 seconds ago       Up 14 seconds       0.0.0.0:3000-\u003e3000/tcp   app\nf8afad7282fa        mongo:3             \"docker-entrypoint.s…\"   20 seconds ago      Up 28 seconds       27017/tcp                db\n```\n\nHowever, your to-do notes are gone!\n\nThis is because, as we've seen, container data is written to a **top writable layer** in the union\nfile system, and **that layer is deleted when the container is removed**.\n\nLet's see how to [manage data in Docker][docker-storage]. There are 3 solutions:\n\n* [Volumes][docker-storage-volumes] - Store data in a volume created and managed by Docker.\n* [Bind mounts][docker-storage-bind] - Mount a file/directory of the host machine into a container.\n* [tmpfs mounts][docker-storage-tmpfs] - Mount an in-memory directory into a container to store\n  non-persistent state or sensitive information.\n\n![Docker storage types](images/docker-storage.png)\n\nWe'll talk about the first 2.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Bind mounts\n\n[Bind mounts][docker-storage-bind] have been around since the early days of Docker. When you use a\nbind mount, a **file or directory on the host machine is mounted into a container**. The file or\ndirectory is referenced by its full or relative path on the host machine.\n\nBind mounts are very performant, but they rely on the host machine's filesystem having a specific\ndirectory structure available, making the container **less portable**. If you are developing new\nDocker applications, consider using [named volumes][docker-storage-volumes] instead (which we'll see\nin the next section). You can't use Docker commands to directly manage bind mounts.\n\nLet's stop the `app` and `db` containers and remove the `db` container. As before, this will clear\nall our data:\n\n```bash\n$\u003e docker stop app db\napp\ndb\n\n$\u003e docker rm db\n```\n\nIf you read the [official `mongo` image][hub-mongo]'s documentation, under its *Where to Store Data*\nheading, you will see that it stores the MongoDB server's data under the `/data/db` path in the\ncontainer's file system. Let's bind mount a directory on your host machine to that directory, so\nthat the data persists even after the container is removed.\n\nRun the database again, but add the `-v` or `--volume \u003chostDir:containerDir\u003e` option to the `docker\nrun` command:\n\n```bash\n$\u003e docker run -d --name db --network todo -v ~/docker-brownbag-db:/data/db mongo:3\n0678bd8cafde87a6d0a6c29022d567d316d3a735229c08ab5387737644143283\n```\n\nThis instructs Docker to create the `~/docker-brownbag-db` on your host machine, and to **mount it\ninto the running container** at the `/data/db` path (overwriting anything that was already there).\n\nGive the MongoDB server a second or two to initialize, then list the contents of the\n`~/docker-brownbag-db` on your machine:\n\n```bash\n$\u003e ls -1 ~/docker-brownbag-db\nWiredTiger\nWiredTiger.lock\nWiredTiger.turtle\nWiredTiger.wt\nWiredTigerLAS.wt\n_mdb_catalog.wt\ncollection-0--8103351089522900052.wt\ncollection-2--8103351089522900052.wt\ndiagnostic.data\nindex-1--8103351089522900052.wt\nindex-3--8103351089522900052.wt\njournal\nmongod.lock\nsizeStorer.wt\nstorage.bson\n```\n\nAs you can see, the MongoDB server's data is indeed being stored into that directory. Note that the\nMongoDB server itself doesn't know about your `~/docker-brownbag-db` directory. From its point of\nview, it's simply writing files under `/data/db` in the container's file system. The Docker bind\nmount makes these writes go to your host machine instead of the container's top writable layer.\n\nStart the `app` container again:\n\n```bash\n$\u003e docker start app\napp\n```\n\nNow play with the application–create a few todo-notes.\n\nStop both containers again and remove the `db` container. As before, the top writable layer of the\n`db` container is deleted:\n\n```bash\n$\u003e docker stop app db\napp\ndb\n\n$\u003e docker rm db\ndb\n```\n\nNow re-run the `db` container, giving the same volume option as before, and restart the `app`\ncontainer as well:\n\n```bash\n$\u003e docker run -d --name db --network todo -v ~/docker-brownbag-db:/data/db mongo:3\n12b985e23b4bea3b25595e0a2c52cf85693cdc1e2851a7197e93929da3aeecf7\n\n$\u003e docker start app\napp\n```\n\nThis time, the persisted MongoDB server's data was mounted into the new container. Instead of\ninitializing from scratch, **the MongoDB server loaded the existing data** (it's as if it was simply\nrestarted). Your to-do notes are still here!\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Volumes\n\nAs mentioned, bind mounts are not ideal for container portability.\n\n[Volumes][docker-storage-volumes] are the preferred mechanism for persisting data generated by and\nused by Docker containers.  While bind mounts are dependent on the directory structure of the host\nmachine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts:\n\n* Volumes are easier to back up or migrate than bind mounts.\n* You can manage volumes using Docker CLI commands or the Docker API.\n* Volumes work on both Linux and Windows containers.\n* Volumes can be more safely shared among multiple containers.\n* Volume drivers allow you to store volumes on remote hosts or cloud providers, to encrypt the\n  contents of volumes, or to add other functionality.\n* A new volume's contents can be pre-populated by a container.\n\n\nIn addition, volumes are often a better choice than persisting data in a container's writable layer,\nbecause using a volume does not increase the size of containers using it, and the volume's contents\nexist outside the lifecycle of a given container.\n\nStop both containers again, remove the `db` container and the `~/docker-brownbag-db` directory on\nyour machine:\n\n```bash\n$\u003e docker stop app db\napp\ndb\n\n$\u003e docker rm db\ndb\n\n$\u003e rm -fr ~/docker-brownbag-db\n```\n\nTo run a MongoDB server container with a volume instead of a bind mount, use the same volume option,\nbut use an arbitrary name instead of a host machine directory, i.e. `todo_data` instead of\n`~/docker-brownbag-db`. Because it's not an absolute path (it doesn't start with `~` or `/`), Docker\ninterprets it as the name of a Docker volume, which it will automatically create if it doesnt exist.\n\n```bash\n$\u003e docker run -d --name db --network todo -v todo_data:/data/db mongo:3\nb53c96764916b3531909171aeade9be2d86acfeccbf23a4128e55a04709d25d7\n```\n\nYou can list volumes with the `docker volume ls` command:\n\n```bash\n$\u003e docker volume ls\nDRIVER              VOLUME NAME\n...\nlocal               todo_data\n```\n\nStart the `app` container again, and you should be good to go:\n\n```bash\n$\u003e docker start app\napp\n```\n\nCreate a few todo-notes, then stop both containers and remove the `db` container again, and finally\nre-run the `db` container with the same command:\n\n```bash\n$\u003e docker stop app db\napp\ndb\n\n$\u003e docker rm db\ndb\n\n$\u003e docker run -d --name db --network todo -v todo_data:/data/db mongo:3\n3ac1a6846a805d7821f8b80c230fe318fcb626b956b5b447e756cf1da1e221ae\n```\n\nAgain, the MongoDB server's data persisted in the Docker volume, and **a volume is not deleted when\ncontainers using it are removed**, so your to-do notes are still here!\n\nIf you inspect the volume, you can see where its data is actually stored:\n\n```bash\n$\u003e docker inspect todo_data\n[\n    {\n        \"CreatedAt\": \"2018-04-25T11:31:09Z\",\n        \"Driver\": \"local\",\n        \"Labels\": null,\n        \"Mountpoint\": \"/var/lib/docker/volumes/todo_data/_data\",\n        \"Name\": \"todo_data\",\n        \"Options\": {},\n        \"Scope\": \"local\"\n    }\n]\n```\n\n(Note that this represents the path of the volume on the Docker host. When using Docker on macOS or\nWindows, this directory might not exist on your machine due to the intermediate virtual machine that\nis sometimes used. In that case, it exists on the virtual machine's file system.)\n\n[Volume drivers][docker-storage-volume-drivers] allow very flexible management of your data, such as\nstoring it on external services (e.g. cloud providers), transparently encrypting content, etc.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Debugging containers\n\nA very useful command to debug containers is `docker exec \u003ccommand...\u003e`. It executes a command **in\na running container**.\n\nFor example, let's say you want to check what's in the `/usr/src/app` directory in the `app`\ncontainer:\n\n```bash\n$\u003e docker exec app ls -la /usr/src/app\ntotal 104\ndrwxr-xr-x    1 todo     todo          4096 Apr 25 13:55 .\ndrwxr-xr-x    1 root     root          4096 Apr 23 15:22 ..\n-rw-r--r--    1 todo     todo            13 Apr 23 14:41 .dockerignore\n-rw-r--r--    1 todo     todo            95 Apr 23 15:05 Dockerfile\n-rw-r--r--    1 todo     todo           518 Apr 25 07:15 Dockerfile.full\n-rw-r--r--    1 todo     todo          1242 Apr 25 13:37 app.js\ndrwxr-xr-x    2 todo     todo          4096 Apr 23 13:21 bin\n-rw-r--r--    1 todo     todo           458 Apr 25 13:55 docker-compose-full.yml\n-rw-r--r--    1 todo     todo           305 Apr 25 13:53 docker-compose.yml\n-rwxr-xr-x    1 todo     todo           550 Apr 25 06:58 entrypoint.sh\ndrwxr-xr-x    2 todo     todo          4096 Apr 23 13:31 models\n-rw-r--r--    1 todo     todo           746 Apr 25 13:36 nginx.conf\ndrwxr-xr-x  131 todo     todo          4096 Apr 25 12:42 node_modules\n-rw-r--r--    1 todo     todo         36450 Apr 25 12:30 package-lock.json\n-rw-r--r--    1 todo     todo           293 Apr 25 12:30 package.json\ndrwxr-xr-x    3 todo     todo          4096 Apr 23 14:33 public\ndrwxr-xr-x    2 todo     todo          4096 Apr 23 13:28 routes\ndrwxr-xr-x    2 todo     todo          4096 Apr 23 13:21 views\n```\n\nOr what's in the `/data/db` directory in the `db` container:\n\n```bash\n$\u003e docker exec db ls -1 /data/db\nWiredTiger\nWiredTiger.lock\nWiredTiger.turtle\nWiredTiger.wt\nWiredTigerLAS.wt\n_mdb_catalog.wt\ncollection-0--7247392160342363554.wt\ncollection-2--7247392160342363554.wt\ncollection-4--7247392160342363554.wt\ncollection-7--7247392160342363554.wt\ndiagnostic.data\nindex-1--7247392160342363554.wt\nindex-3--7247392160342363554.wt\nindex-5--7247392160342363554.wt\nindex-6--7247392160342363554.wt\nindex-8--7247392160342363554.wt\njournal\nmongod.lock\nsizeStorer.wt\nstorage.bson\n```\n\nYou can execute any available command, including a full shell (if there is one in your container's\nfile system). For example, let's run a shell in the `app` container:\n\n```bash\n$\u003e docker exec -it app sh\n/usr/src/app $\n```\n\nYou're now in the container! You can run any command you want:\n\n```bash\n/usr/src/app $ echo hello from $(hostname)\nhello from 5914ad5835e4\n\n/usr/src/app $ ls\nDockerfile               docker-compose.yml       package-lock.json\nDockerfile.full          entrypoint.sh            package.json\napp.js                   models                   public\nbin                      nginx.conf               routes\ndocker-compose-full.yml  node_modules             views\n```\n\nRun `exit` once you're done:\n\n```bash\n/usr/src/app $ exit\n```\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Ephemeral containers\n\nYou could make changes to a running container using `docker exec`, but that's considered a bad\npractice.\n\nContainers produced by your Dockerfiles should be as **ephemeral** as possible. By \"ephemeral\", we\nmean that they can be **stopped and destroyed** and a **new one built and put in place** with an\n**absolute minimum of setup and configuration**. You shouldn't have to perform additional manual\nchanges in a container once it's started.\n\nYou may want to take a look at the [Processes][12factor-processes] section of the [12 Factor app\nmethodology][12factor] to get a feel for the motivations of running containers in such a stateless\nfashion.\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Docker Compose\n\n[Docker Compose][docker-compose] is a tool for defining and running multi-container Docker\napplications. With Compose, you use a **[YAML][yaml] file to configure your application's\nservices**.  Then, with a single command, you **create and start all the services from your\nconfiguration**. It enables:\n\n* Multiple isolated environments on a single host.\n* Preserve volume data when containers are created.\n* Only recreate containers that have changed.\n* Variables and moving a composition between environments.\n\nUsing Compose is basically a three-step process:\n\n* Define your app's environment with a Dockerfile so it can be reproduced anywhere (we've already\n  done that in the previous examples).\n* Define the services that make up your app in `docker-compose.yml` so they can be run together in\n  an isolated environment.\n* Run `docker-compose up` and Compose starts and runs your entire app.\n\nCompose has commands for managing the whole lifecycle of your application:\n\n* Start, stop, and rebuild services.\n* View the status of running services.\n* Stream the log output of running services.\n* Run a one-off command on a service.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### The `docker-compose.yml` file\n\nIf the containers from the previous steps are still running, remove them and also remove the Docker\nimage, network and data volume we created for the to-do application, so that we start from scratch:\n\n```bash\n$\u003e docker rm -f app db\napp\ndb\n\n$\u003e docker network rm todo\ntodo\n\n$\u003e docker volume rm todo_data\ntodo_data\n\n$\u003e docker rmi todo\n...\n```\n\nIt's a pain to remember the full commands we used before with all the correct options to build and\nstart our 2-container infrastructure in a bridge network with a persistent data volume. If only\nthere was a more reproducible solution.\n\nEnter the `docker-compose.yml` file:\n\n```\nversion: '3'\n\nservices:\n\n  app:\n    image: docker-brownbag/todo\n    build:\n      context: .\n      dockerfile: Dockerfile.full\n    depends_on:\n      - db\n    environment:\n      DATABASE_URL: \"mongodb://db:27017\"\n    ports:\n      - \"3000:3000\"\n\n  db:\n    image: mongo:3\n    volumes:\n      - data:/data/db\n\nvolumes:\n  data:\n```\n\nThis file defines 2 `app` and `db` **services** that can be managed with Docker Compose.\n\nLet's go into more details, looking at the `app` service first:\n\n```\napp:\n  image: docker-brownbag/todo\n  build:\n    context: .\n    dockerfile: Dockerfile.full\n  depends_on:\n    - db\n  environment:\n    DATABASE_URL: \"mongodb://db:27017\"\n  ports:\n    - \"3000:3000\"\n  restart: always\n```\n\nIt's basically another way to indicate all the options and arguments we've passed to Docker commands\nso far::\n\n* The `context: .` option under `build:` indicates that the `app` service should be built with\n  `docker build` with the current directory as the build context (where we will run the\n  `docker-compose` command).\n* The `dockerfile: Dockerfile.full` option indicates that the Dockerfile that should be used is the\n  file named `Dockerfile.full` instead of the default `Dockerfile`. (For the purposes of this\n  example, `Dockerfile.full` implements all suggested tips under [Dockerfile\n  Tips][dockerfile-tips].)\n* The `image: docker-brownbag/todo` option indicates that the resulting image should be tagged as\n  `todo`, exactly like `-t todo` option of the `docker build` command.\n* The `depends_on: [ db ]` option indicates that the `app` service depends on the `db` service, and\n  therefore the `db` service should be started first.\n* The `environment: ...` option is a map of environment variables to add to the service's\n  containers, exactly like the `--env` option of the `docker run` command.\n* The `ports: [ \"3000:3000\" ]` option indicates that the container's 3000 port should be published\n  to the host machine's 3000 port, exactly like the `--publish` option of the `docker run` command.\n\nLet's look at the `db` service:\n\n```\ndb:\n  image: mongo:3\n  volumes:\n    - data:/data/db\n```\n\n* The `image: mongo:3` option indicates that a container should be launched from the `mongo:3` image\n  from the Docker hub, exactly like the first argument to the `docker run \u003cimage\u003e` command.\n* The `volumes: [ data:/data/db ]` option indicates that a named Docker data volume\n  should be mounted into the container's file system at the `/data/db` path.\n\nAdditionally, because we will run Docker Compose in the `todo` directory of the repository, Compose\nwill assume that the project is named `todo`, and will use that to do a few things:\n\n* Container, network and volume names will be prefixed with the project name. For example, the data\n  volume is defined as `data` in the `db` service, so its full name will be `todo_data`.\n* Compose will automatically create a bridge network for our 2 services, which it will name\n  `default` with the project name as a prefix, so `todo_default` in our case.\n\n(You may specify another project name with the `-p` or `--project-name \u003cname\u003e` option of the\n`docker-compose` command.)\n\nFinally, named data volumes must be declared, which is what the last section is for:\n\n```\nvolumes:\n  data:\n```\n\n(There is nothing under `data:` because the default options are appropriate for this example.)\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Running Docker Compose services\n\nTo redo everything we have done so far manually, this time with Docker Compose, all you have to do\nis go into the `todo` directory, and run the `docker-compose up` command:\n\n```bash\n$\u003e docker-compose up --build -d\nCreating network \"todo_default\" with the default driver\nCreating volume \"todo_data\" with default driver\nBuilding app\n...\nSuccessfully built ab624a535392\nSuccessfully tagged docker-brownbag/todo:latest\nCreating todo_db_1 ... done\nCreating todo_app_1 ... done\n```\n\nAs you can see, it has:\n\n* Created a network.\n* Created a data volume.\n* Built the `todo` image.\n* Run 2 containers named `todo_app_1` and `todo_db_1` for the Node.js application and MongoDB\n  server, respectively.\n\nList networks, running containers and volumes to make sure:\n\n```bash\n$\u003e docker network ls\nNETWORK ID          NAME                DRIVER              SCOPE\n...\nfc5505683b61        todo_default        bridge              local\n\n$\u003e docker ps\nCONTAINER ID        IMAGE     COMMAND                  CREATED                  STATUS              PORTS                    NAMES\na45ab4f341ba        todo      \"npm start\"              Less than a second ago   Up 2 seconds        0.0.0.0:3000-\u003e3000/tcp   todo_app_1\n4ea3b100947d        mongo:3   \"docker-entrypoint.s…\"   Less than a second ago   Up 2 seconds        27017/tcp                todo_db_1\n\n$\u003e docker volume ls\nDRIVER              VOLUME NAME\n...\nlocal               todo_data\n```\n\nYou can test the to-do application at [`http://localhost:3000`](http://localhost:3000) to make sure\nit runs as before.\n\nTo test data persistence, stop and remove all services defined in the `docker-compose.yml` file with\nthe `docker-compose stop` and `docker-compose rm` commands:\n\n```bash\n$\u003e docker-compose stop\nStopping todo_app_1 ... done\nStopping todo_db_1  ... done\n\n$\u003e docker-compose rm\nGoing to remove todo_app_1, todo_db_1\nAre you sure? [yN] y\nRemoving todo_app_1 ... done\nRemoving todo_db_1  ... done\n```\n\nThe network and data volume remain.  (You could also use `docker-compose down` which would do the\nsame thing and also remove the network.)\n\nTo restart both containers, simply use `docker-compose up` again:\n\n```bash\n$\u003e docker-compose up -d\nCreating todo_db_1 ... done\nCreating todo_app_1 ... done\n```\n\nDocker Compose has many utility commands which **simplify working with a multi-container\napplication**.  For example, the `docker-compose ps` command **lists services' containers**:\n\n```bash\n$\u003e docker-compose ps\n   Name                Command             State           Ports\n-------------------------------------------------------------------------\ntodo_app_1   npm start                     Up      0.0.0.0:3000-\u003e3000/tcp\ntodo_db_1    docker-entrypoint.sh mongod   Up      27017/tcp\n```\n\nThe `docker-compose logs \u003cservice\u003e` command allows you to **check a service's logs** without knowing\nthe exact name or ID of the containers:\n\n```bash\n$\u003e docker-compose logs app\nAttaching to todo_app_1\napp_1  |\napp_1  | \u003e todo@0.0.0 start /usr/src/app\napp_1  | \u003e node ./bin/www\napp_1  |\napp_1  | GET / 304 277.099 ms - -\napp_1  | GET /javascripts/client.js 200 4.445 ms - 656\napp_1  | Mongoose: todos.find({}, { sort: { createdAt: 1 }, fields: {} })\napp_1  | GET /todos 304 16.767 ms - -\n```\n\nAnd [much more][docker-compose-cli].\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Rebuilding Docker Compose services\n\nIf you attempt to run `docker-compose up` again with the `--build` option, note that it does\nnothing:\n\n```bash\n$\u003e docker-compose up --build -d\nBuilding app\n...\n ---\u003e Using cache\n...\nSuccessfully built 8215f1cda8b6\nSuccessfully tagged docker-brownbag/todo:latest\ntodo_db_1 is up-to-date\ntodo_app_1 is up-to-date\n```\n\nIt indicates that both containers are \"up-to-date\", meaning that since the images have not changed,\nit left the existing containers running and did not attempt to restart them.\n\nMake a change to the application. For example, open `todo/public/javascripts/client.js` and change\nthe `title` property of the application. Then rebuild it:\n\n```bash\n$\u003e docker-compose up --build -d\nBuilding app\n...\n ---\u003e Using cache\n ---\u003e 1ad095898f72\nStep 9/13 : COPY --chown=todo:todo . /usr/src/app/\n ---\u003e 4d6d681d4444\n...\nSuccessfully built e67c21b4e1ab\nSuccessfully tagged docker-brownbag/todo:latest\ntodo_db_1 is up-to-date\nRecreating todo_app_1 ... done\n```\n\nAs expected, the build cache was invalidated since the application changed. Docker Compose therefore\n**recreated the `todo_app_1` container**. But it still **left `todo_db_1` intact** since the change\ndid not affect the database, so no recreation was necessary.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Starting containers automatically\n\nThere are several reasons you might want to **start your containers automatically** rather than\nmanually:\n\n* You want them to **restart automatically if the host machine reboots**.\n* You want them to **restart automatically if the process in the container crashes** due to a bug,\n  causing the container to stop.\n\nDocker can be instructed to automatically start containers by using a **restart policy** either when\nrunning containers manually or when using Docker Compose:\n\n* Add a `--restart \u003cpolicy\u003e` option to your `docker run` command.\n* Add a `restart: \u003cpolicy\u003e` option to your `docker-compose.yml` file.\n\nFor example, here's what the services in the previous `docker-compose.yml` example look like with an\nadditional restart policy:\n\n```\napp:\n  image: docker-brownbag/todo\n  build:\n    context: .\n    dockerfile: Dockerfile.full\n  depends_on:\n    - db\n  environment:\n    DATABASE_URL: \"mongodb://db:27017\"\n  restart: always\n  ports:\n    - \"3000:3000\"\n\ndb:\n  image: mongo:3\n  restart: always\n  volumes:\n    - data:/data/db\n```\n\nThat way, Docker can also fulfill the role of process manager.\n\nThe available restart policies are:\n\n* `no` - Do not automatically restart the container. (the default).\n* `on-failure` - Restart the container if it exits due to an error, which manifests as a non-zero\n  exit code.\n* `unless-stopped` - Restart the container unless it is explicitly stopped or Docker itself is\n  stopped or restarted.\n* `always` - Always restart the container if it stops.\n\n[Back to top](#readme)\n\n\u003cbr\u003e\n\n### Horizontal scaling\n\nLet's say we expect many users to use our to-do app, and we're worried that our new infrastructure\nwon't be able to handle the load. We're going to [**scale horizontally**][horizontal-scaling] by\ndeploying **multiple containers for our Node.js application**.\n\n(As mentioned before, we'll keep only 1 MongoDB server container running, because setting up MongoDB\nreplication takes a bit more work.)\n\nIf you've followed the previous examples, run the following command to bring down the entire\ninfrastructure (except persistent data volumes):\n\n```bash\n$\u003e docker-compose down\nStopping todo_app_1 ... done\nStopping todo_db_1  ... done\nRemoving todo_app_1 ... done\nRemoving todo_db_1  ... done\nRemoving network todo_default\n```\n\nSo far, our `app` container has its 3000 port published on your machine's 3000 port. That won't work\nif you deploy more than 1 container, as they **can't all listen on the same port**. You'll have to\nput a **load balancer** in front of your `app` containers, so that it can listen on the port, and\nredirect requests to the other containers.\n\nMake the following changes to the `docker-compose.yml` in the `todo` directory:\n\n* Remove the `ports: [ \"3000:3000\" ]` option from the `app` service.\n* Add a new `lb` (load balancer) service with the following configuration:\n\n  ```\n  lb:\n    depends_on:\n      - app\n    image: nginx:1.13-alpine\n    ports:\n      - \"3000:80\"\n    restart: always\n    volumes:\n      - ./nginx.conf:/etc/nginx/nginx.conf:ro\n  ```\n\n(You will find the expected result in the `docker-compose-full.yml` file in the `todo` directory.)\n\nWe're using the [official `nginx` image][hub-nginx] from the Docker hub, which will launch an\n[nginx][nginx] web server that will act as our [load balancer][nginx-lb]. Its 80 port will be\npublished to your machine's 3000 port so that you'll be able to access `http://localhost:3000` like\nbefore.\n\nThe `volumes` option defines a bind mount that will mount the `nginx.conf` file in the `todo`\ndirectory to `/etc/nginx/nginx.conf` in the container's file system. That is the default location\nfor nginx's configuration file. The final `:ro` option defines the mount as read-only (nginx only\nneeds to read from that file, not write to it).\n\nLook at the interesting parts of the `nginx.conf` file, the `upstream` and `server` directives:\n\n```\nupstream todo_cluster {\n  server todo_app_1:3000;\n  server todo_app_2:3000;\n  server todo_app_3:3000;\n}\n\nserver {\n  listen 80;\n  server_name localhost;\n\n  location / {\n    proxy_pass http://todo_cluster;\n  }\n}\n```\n\nThe `upstream` directive defines a group of 3 addresses that nginx can redirect traffic to. By\ndefault, nginx will use a round-robin strategy, meaning that each request will be redirected to the\nnext address. The `proxy_pass` directive instructs nginx to redirect all requests to the upstream\ngroup.\n\nYou can **run this entire infrastructure** like before with the `docker-compose up` command. Note\nthe use of the `--scale \u003cservice=num\u003e` option to define the number of containers to create for the\n`app` service:\n\n```bash\n$\u003e docker-compose up --build -d --scale app=3\nCreating network \"todo_default\" with the default driver\nBuilding app\n...\nSuccessfully built 416a49a3a498\nSuccessfully tagged todo:latest\nCreating todo_lb_1 ... done\nCreating todo_db_1 ... done\nCreating todo_app_1 ... done\nCreating todo_app_2 ... done\nCreating todo_app_3 ... done\n```\n\nYou now have 1 database container, 3 application containers and 1 load balancer containers running:\n\n```bash\n$\u003e docker-compose ps\n   Name                 Command               State          Ports\n--------------------------------------------------------------------------\ntodo_app_1   /usr/local/bin/entrypoint. ...   Up      3000/tcp\ntodo_app_2   /usr/local/bin/entrypoint. ...   Up      3000/tcp\ntodo_app_3   /usr/local/bin/entrypoint. ...   Up      3000/tcp\ntodo_db_1    docker-entrypoint.sh mongod      Up      27017/tcp\ntodo_lb_1    nginx -g daemon off;             Up      0.0.0.0:3000-\u003e80/tcp\n```\n\nThe to-do application should run as before on [`http://localhost:3000`](http://localhost:3000),\nexcept that the \"Running on host ...\" message should change every time you reload the page, proving\nthat you are in fact communicating with each container of the `app` service in turn.\n\nThis is nice, but the `upstream` block in the `nginx.conf` configuration file is **hardcoded** to\nload balance over exactly 3 containers. Incidentally, nginx won't start if any of the containers is\nunreachable. This is not as flexible as we might want.\n\nThere are several solutions to this problem. Here's two:\n\n* Use a [service discovery][service-discovery] tool like [Consul][consul], [Serf][serf] or\n  [ZooKeeper][zookeeper]. Setting up such a solution can enable the `lb` container to be aware of\n  what other containers are deployed at a given time, and to automatically update its configuration\n  when containers are started or stopped.\n* Use [Docker in swarm mode][docker-swarm], because it can manage services and load balancing for\n  you. You'll see in action if you [read on](#docker-swarm).\n\n[Back to top](#readme)\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n\n\n\n## Docker Swarm\n\nDocker comes with [SwarmKit][swarmkit], a cluster management and orchestration tool.\n\n![Docker Swarm](images/swarm.png)\n\nA **swarm** consists of **multiple Docker nodes**, i.e. separate computers or cloud servers, which\nrun in swarm mode and act as **managers** (to manage membership and delegation) and/or **workers**\n(which run swarm services).\n\nA **service** is a definition of the tasks to execute on the manager or worker nodes. It is the\ncentral structure of the swarm system and the primary root of user interaction with the swarm.  When\nyou create a service, you specify **which container image to use** and **which commands** to execute\ninside running containers.\n\nWhen you define a service, you describe its optimal state (number of replicas, network and storage\nresources available to it, ports the service exposes to the outside world, and more). Docker works\nto maintain that desired state. For instance, if a worker node becomes unavailable, Docker schedules\nthat node's tasks on other nodes.\n\nA **task** carries a Docker **container** and the **commands** to run inside the container. It is\nthe **atomic scheduling unit** of swarm. Manager nodes assign tasks to worker nodes according to the\nnumber of **replicas** set in the service scale.\n\nThe advantages of Docker Swarm over standalone containers and Docker Compose are:\n\n* SwarmKit uses the [Raft Consensus Algorithm][raft] in order to coordinate and **does not rely on a\n  single point of failure** to perform decisions.\n* Node communication and membership within a Swarm are **secure out of the box**. SwarmKit uses\n  mutual TLS for node authentication, role authorization and transport encryption, automating both\n  certificate issuance and rotation.\n* SwarmKit is **operationally simple** and minimizes infrastructure dependencies. It does not need\n  an external database to operate.\n* You can modify a service's configuration, including the networks and volumes it is connected to,\n  **without the need to manually restart the service**. Docker will update the configuration, stop\n  the service tasks with the out of date configuration, and create new ones matching the desired\n  configuration.\n* The swarm manager uses **ingress load balancing** to expose the services you want to make\n  available externally to the swarm. Once you configure a published port for the service, extern","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Fdocker-brownbag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmediacomem%2Fdocker-brownbag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Fdocker-brownbag/lists"}