# Lab 11: Docker

## Task 1: Install Docker on Ubuntu

- Update the list of packages:

      $ sudo apt update

- Install Docker:

      $ sudo apt install docker.io

- Docker should now be installed, the daemon started, and the process enabled to start on boot. Check that it’s running:

      $ sudo systemctl status docker

It is recommended that you do not run docker as the root user. If you currently cannot run `$ docker` with your non-root account, then take the following steps:

- Add your username to the docker group:

      $ sudo usermod -aG docker ${USER}

- To apply the new group membership, log out of the server and back in, or type the following:

      $ su - ${USER}

- Confirm that your user is now added to the docker group by typing `$ groups`.

      $ groups
      user2 adm cdrom sudo dip plugdev lpadmin lxd sambashare docker

> If you need to add a user to the docker group that you’re not logged in as, declare that username explicitly using:

>     $ sudo usermod -aG docker <username>

## Task 2: Pull images and run containers

- Locate the application you want to run on Docker hub. Let’s run the Docker `hello-world` application https://hub.docker.com/_/hello-world .

      $ docker run hello-world

  Docker will first search locally for the `hello-world` image. It will then download the image from Docker Hub if it is not found locally.

      Unable to find image 'hello-world:latest' locally
      latest: Pulling from library/hello-world
      2db29710123e: Pull complete
      Digest: sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
      Status: Downloaded newer image for hello-world:latest

      Hello from Docker!
      This message shows that your installation appears to be working correctly.
      ...

  > Docker Hub is the default repository for Docker installations.

- You can search for images available on the Docker Hub by using the `search` option. Let’s search for “nginx”:

      $ docker search nginx

- Let’s download the `nginx` image to our machine without running it. Use the `pull` option:

      $ docker pull nginx

- To see all images downloaded to your machine run:

      $ docker images
      REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
      nginx         latest    76c69feac34e   2 weeks ago     142MB
      hello-world   latest    feb5d9fea6a5   13 months ago   13.3kB

- Unlike the `hello-world` container that ran once and exited, some containers run endlessly and their logs are written to the terminal. We can run these containers in the background or in detached mode with the `-d` flag. Let’s run `nginx` in the background

      $ docker run -d nginx

- Run `$ docker ps` to view active containers.

      $ docker ps
      CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
      f2e1f7608e47   nginx     "/docker-entrypoint.…"   6 seconds ago   Up 5 seconds   80/tcp    sweet_clarke

  > The `hello-world` application we ran earlier does not run continuously. It simply prints the message and exits.

- To view all containers (active and inactive), run `$ docker ps -a`

      $ docker ps -a
      CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS                          PORTS     NAMES
      f2e1f7608e47   nginx         "/docker-entrypoint.…"   33 seconds ago       Up 32 seconds                   80/tcp    sweet_clarke
      6e4d35a225ec   hello-world   "/hello"                 About a minute ago   Exited (0) About a minute ago             gallant_edison

  > You can interact with a container by using the container ID or the unique container name. For example, the container ID and container name of the container created from the `nginx` image is `f2e1f7608e47` and `sweet_clarke` respectively.

- To enter the shell of a running container and execute commands, run `$ docker exec -it <container-name> bash`:

      $ docker exec -it sweet_clarke bash
      root@f2e1f7608e47:/#

- While in the container, view nginx default index file:

      root@f2e1f7608e47:/# cat /usr/share/nginx/html/index.html
      <!DOCTYPE html>
      <html>
      <head>
      <title>Welcome to nginx!</title>
      <style>
      html { color-scheme: light dark; }
      body { width: 35em; margin: 0 auto;
      font-family: Tahoma, Verdana, Arial, sans-serif; }
      </style>
      </head>
      <body>
      <h1>Welcome to nginx!</h1>
      <p>If you see this page, the nginx web server is successfully installed and
      working. Further configuration is required.</p>

      <p>For online documentation and support please refer to
      <a href="http://nginx.org/">nginx.org</a>.<br/>
      Commercial support is available at
      <a href="http://nginx.com/">nginx.com</a>.</p>

      <p><em>Thank you for using nginx.</em></p>
      </body>
      </html>

  Navigate the shell to see its capabilities. Administrators can make changes to the Docker applications they deploy by entering the container shell.
- When done exploring the container shell, run `$ exit` to close the shell and return to your host machine.
- To stop a container, run `$ docker stop <cotainer-name>`. Let’s stop the `sweet_clarke` container:

      $ docker stop sweet_clarke

- View a list of all containers again. The status should show that it exited.

      $ docker ps -a

- You can restart a stopped container by running: `$ docker start <cotainer-name>`.

  > Note that `$ docker run <image-name>` creates a new container, therefore it’s not necessary to use `docker run` when the container is already created from the image, unless your intention is to create multiple containers.

- You can remove a container with `$ docker rm <container-name>`.

      $ docker rm sweet_clarke
      $ docker rm 6e4d35a225ec # container ID for hello-world

- Let’s also remove the `hello-world` and the `nginx` images from our local machine.
  First, view available images:

      $ docker images -a
      REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
      hello-world   latest    feb5d9fea6a5   13 months ago   13.3kB
      nginx         latest    f2e1f7608e47   2 years ago     100.2MB

  Use the `rmi` option to remove the images:

      $ docker rmi hello-world nginx
      Untagged: hello-world:latest
      Untagged: hello-world@sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
      Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
      Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359
      Untagged: nginx:latest
      Untagged: nginx@sha256:943c25b4b66b332184d5ba6bb18234273551593016c0e0ae906bab111548239f
      Deleted: sha256:76c69feac34e85768b284f84416c3546b240e8cb4f68acbbe5ad261a8b36f39f
      Deleted: sha256:8b811a30cb94c227fb2ae61a2a1ec1e93381dbef06f9ea6b5c06df4f27651fed
      Deleted: sha256:470cc6f5d954afeb2695504eff0eda9f8da0e1b3b8bde30e74d9c48dbcb99906
      Deleted: sha256:1c277c746c47ba650267f58cc6e1ea430ce726065c79691ce04235d90209caff
      Deleted: sha256:953e18d40076df9e65564241b34cfba85ab1a6c0634c67e92d6a1f633c5b97d9
      Deleted: sha256:f0a780360f49b2b6afc28882ed2399799e6615862e7dc64451fb3688a33fe712
      Deleted: sha256:a12586ed027fafddcddcc63b31671f406c25e43342479fc92a330e7e30d65f2e

## Task 3: Create a custom Docker image

Let’s create a static page website and run it on a Python web server.

> Note: There are more effective ways to set up a web server. We use the methods in this lab simply to explore the process of creating a Docker image.

- Create a directory for the Docker application and navigate to it:

      $ mkdir ~/pythonweb && cd ~/pythonweb

- Create a `Dockerfile` in the directory:

      $ touch Dockerfile

- Add the following lines to `Dockerfile`:
  1. To pull the docker image for Ubuntu 22.04:

         FROM ubuntu:jammy

  2. To update the sources list and install python3:

         RUN apt-get update && apt-get install -y python3 --no-install-recommends

  > The python web server listens on port `8000` by default. Network access to container services is disabled by default.

  4. To expose port `8000` to devices outside the container

         EXPOSE 8000

  5. To start the python web server when the contaner is executed:

         ENTRYPOINT ["python3", "-m", "http.server"]

  The `Dockerfile` should look like this:

      FROM ubuntu:jammy
      RUN apt-get update && apt-get install -y python3 --no-install-recommends
      EXPOSE 8000
      ENTRYPOINT ["python3", "-m", "http.server"]

  > `RUN` is used to specify commands that should be run when building the image.
  > `ENTRYPOINT` and `CMD` define commands the container executes when it launches.

- Build the image:

      docker build -t pythonweb:latest .

  > The `.` at the end represents the location of the `Dockerfile`. In this case it is in the CWD. Otherwise, we would have specified the directory where the `Dockerfile` is located.

- Check your docker images and you should see something similar to the following:

      $ docker images
      REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
      pythonweb    latest    60e7915677a4   About a minute ago   147MB
      ubuntu       jammy     a8780b506fa4   10 days ago          77.8MB

- Run the image `pythonweb` and map port 80 on the host machine to port 8000 on the container. This will allow users interact with the web server in the container via the host machine.

      $ docker run -p 80:8000 -d pythonweb

- View the contents of the web server in your browser:
  The Python web server works, but there is no index page, and it shows system files. Let’s proceed to create an index web page, add a working directory, and update the image with the new configuration.
- Create a directory to contain the web files.

      $ mkdir ~/pythonweb/webfiles/

- Create `index.html` in `~/pythonweb/webfiles/index.html`:

      $ vi ~/pythonweb/webfiles/index.html

  Add the following lines to the file:

      <html>
      <h1>SNA Lab</h1>
        <p>SNA rocks :)</p>
      </html>

- Modify the `Dockerfile` and add the following lines:
  1. To copy the contents of `webfiles` directory to the image:

         COPY webfiles/ /usr/share/webfiles

  2. To change the container working directory to the `/usr/share/webfiles` directory

         WORKDIR /usr/share/webfiles

  The final `Dockerfile` after the modification is shown below:

      FROM ubuntu:jammy
      RUN apt-get update && apt-get install -y python3 --no-install-recommends
      COPY webfiles/ /usr/share/webfiles
      WORKDIR /usr/share/webfiles
      EXPOSE 8000
      ENTRYPOINT ["python3", "-m", "http.server"]

- Check running containers, stop the container running `pythonweb`, and remove the container:

      $ docker ps -a
      $ docker stop <container-name>
      $ docker rm <container-name>

- Remove the `pythonweb` image.

      $ docker rmi pythonweb

- Build the image

      docker build -t pythonweb:latest .

- Run the updated `pythonweb` image. This time, let’s specify a container name with the `--name` flag. This is better than relying on the random names generated by Docker:

      $ docker run -p 80:8000 -d --name snaweb-container pythonweb

  Run `docker ps` to view the running container:

      $ docker ps
      CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                                   NAMES
      ecfb00c4039f   pythonweb   "python3 -m http.ser…"   6 seconds ago   Up 5 seconds   0.0.0.0:80->8000/tcp, :::80->8000/tcp   snaweb-container

- Visit the page again and you should have results similar to the following:

## Task 4: Docker `inspect` and container logs

Docker `inspect` is used to view low-level information on Docker objects.

- Inspect the `snaweb-container` container:

      $ docker inspect snaweb-container

  The result is JSON data that contains some useful information if analyzed properly.
  You can filter the results from `inspect` without manually reading everything in the large volume of output it generates.
- Get the full container ID for `snaweb-container`:

      docker container inspect -f '{{.Id}}' snaweb-container

- View the containers main process:

      $ docker container inspect -f '{{printf "%s " .Path}}{{range .Args}}{{printf "%s " .}}{{end}}' snaweb-container

- List all port bindings:

      $ docker container inspect -f '{{range $target, $published := .NetworkSettings.Ports}}{{range $published}}{{printf "%s -> %s:%s\n" $target .HostIp .HostPort}}{{end}}{{end}}' snaweb-container

- View the network your container is connected to:

      $ docker inspect -f "{{json .NetworkSettings.Networks }}" snaweb-container

  The sample output shows that my container is connected to the `bridge` network:

      {"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"ece6245e2975ad3a709dbf7d993a59454c703f09c68b892fd3922581ed967a8e","EndpointID":"40fd7af8985dba6f7f654bffef4df2de363db00b360f630ee19d2d78704df5a1","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02","DriverOpts":null}}

- List the containers connected to a network along with their IP addresses:

  > Replace `bridge` in the command with your network name.

      $ docker network inspect -f '{{range .Containers}}{{printf "%s -> %s\n" .Name .IPv4Address}}{{end}}' bridge

Container logging helps developers keep track of patterns, troubleshoot issues, and fix bugs.

- View the container logs with `$ docker logs <container-name>`:

      $ docker logs snaweb-container

- Container logs are stored in the `/var/lib/docker/containers/ID/ID-json.log`.
- Use `inspect` to find the full log path for `snaweb-container`:

      $ docker container inspect -f '{{.LogPath}}' snaweb-container

  View the log file with tail:

      $ sudo tail /var/lib/docker/containers/ecfb00c4039fddb81a5185fd2287a908cefd140e8f1d832ab65a71759aac39ce/ecfb00c4039fddb81a5185fd2287a908cefd140e8f1d832ab65a71759aac39ce-json.log
      {"log":"172.17.0.1 - - [13/Nov/2022 16:34:00] \"GET / HTTP/1.1\" 200 -\n","stream":"stderr","time":"2022-11-13T16:34:00.810777628Z"}
      {"log":"172.17.0.1 - - [13/Nov/2022 16:48:49] \"GET / HTTP/1.1\" 304 -\n","stream":"stderr","time":"2022-11-13T16:48:49.9306387Z"}
      {"log":"192.168.132.1 - - [13/Nov/2022 16:49:11] \"GET / HTTP/1.1\" 200 -\n","stream":"stderr","time":"2022-11-13T16:49:11.494390274Z"}
      {"log":"192.168.132.1 - - [13/Nov/2022 16:49:11] code 404, message File not found\n","stream":"stderr","time":"2022-11-13T16:49:11.584257708Z"}
      {"log":"192.168.132.1 - - [13/Nov/2022 16:49:11] \"GET /favicon.ico HTTP/1.1\" 404 -\n","stream":"stderr","time":"2022-11-13T16:49:11.584310308Z"}

## Questions to answer

1. Compare and contrast `ENTRYPOINT` and `CMD` in Dockerfile. In what situation would you use each of them?
2. List five security precautions you will take when building or deploying a Docker resource (image or container).
3. Show a single line command that will remove all exited Docker containers. Do not use any text filtering editor. Show test results.
4. Show how you can copy files to a running container without entering the container’s interactive shell.
5. Create a dockerized web application running on nginx. The web index page `index.html` should be located on your host machine. The directory containing the index page should be mounted to the container and served from there.

   > This means that you should be able to modify the web index page on your host machine without interacting with the container.
   > Show all steps taken for the configuration including the test results.

6. Setup rsyslog on your host machine as a central logging server. Create a Docker container and configure it to forward its log to your central logging server.

   > Show steps and test results.

### Bonus

7. Dockerize any open source application of your choice, and host it on Docker hub. Share link to the repository.
8. Find and fix the problems in the following Dockerfile. There are some issues building the image and also running the container:

       FROM alpine
       RUN apt-get update && apt-get install -y python3 --no-install-recommends
       RUN touch index.html
       RUN echo "<html><h1>Testing web</h1></html>" >> index.html
       CMD ["python", "-m", "http.server"]

   > Show all steps taken to fix it, and a working solution.