[
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/csharp\n\n### Csharp ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n**/Properties/launchSettings.json\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Uncomment the next line to ignore your web deploy settings.\n# By default, sensitive information, such as encrypted password\n# should be stored in the .pubxml.user file.\n#*.pubxml\n*.pubxml.user\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Typescript v1 declaration files\ntypings/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# End of https://www.gitignore.io/api/csharp\n.vscode/settings.json\n"
  },
  {
    "path": ".phlow",
    "content": "[default]\nremote                 = origin\nservice                = github\nintegration_branch     = master\nissue_url              = https://api.github.com\ndelivery_branch_prefix = ready\n\n"
  },
  {
    "path": "CHEATSHEET.md",
    "content": "Source: https://docs.docker.com/get-started/docker_cheatsheet.pdf\n\n## General\n\n- [Docker cli](https://docs.docker.com/engine/reference/commandline/cli/) - **Docker CLI** is the command line interface for Docker\n- [Docker Desktop](https://docs.docker.com/desktop) - **Docker Desktop** is available for Mac, Linux, and Windows\n- `docker --help` - Get help with Docker. You can use `--help` on all subcommands\n- `docker info` - Display system-wide information\n- [Docker Docs](https://docs.docker.com) - Check out our docs for information on using Docker\n\n## Containers\n\n- `docker run --name <container_name> <image_name>` - Create and run a container from an image, with a custom name\n- `docker run -p <host_port>:<container_port> <image_name>` - Run a container and publish a container’s port(s) to the host\n- `docker run -d <image_name>` - Run a container in the background\n- `docker start|stop <container_name> (or <container-id>)` - Start or stop an existing container\n- `docker rm <container_name>` - Remove a stopped container\n- `docker exec -it <container_name> sh` - Open a shell inside a running container\n- `docker logs -f <container_name>` - Fetch and follow the logs of a container\n- `docker inspect <container_name> (or <container_id>)` - Inspect a running container\n- `docker ps` - List currently running containers\n- `docker ps --all` - List all docker containers (running and stopped)\n- `docker container stats` - View resource usage stats\n\n## Images\n\n- `docker build -t <image_name>` - Build an Image from a Dockerfile\n- `docker build -t <image_name> . --no-cache` - Build an Image from a Dockerfile without the cache\n- `docker images` - List local images\n- `docker rmi <image_name>` - Delete an Image\n- `docker image prune` - Remove all unused images\n\n\n## Docker Registries\n\nThe default registry is [Docker Hub](https://hub.docker.com), but you can add more registries.\n\n\n- `docker login -u <username>` - Login into Docker\n- `docker push <username>/<image_name>` - Publish an image to Docker Hub\n- `docker search <image_name>` - Search Hub for an image\n- `docker pull <image_name>` - Pull an image from Docker Hub\n- `docker tag <image_name>:<tag> <username>/<image_name>:<tag>` - Tag an image for a registry"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Praqma\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Docker katas\r\n\r\n[![Open the exercises in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/eficode-academy/docker-katas.git)\r\n\r\nThis workshop will take you from \"Hello Docker\" to deploying a containerized web application to a server.\r\n\r\nIt's going to be a lot of fun!\r\n\r\n\r\n## Prerequisites\r\n\r\nYou need to have access to a machine with docker installed.\r\nThere are many ways of getting that:\r\n* Click the link above to get a Cloud shell from Google (require login)\r\n* Docker installed on a linux box. \r\n* Docker desktop installed on a Mac or Windows machine.\r\n\r\n## Philosophy\r\n\r\n\r\nThere are a few things you should know about this tutorial before we begin.\r\n\r\nThis tutorial is designed to be **self-paced** to make the most of your time.\r\n\r\nThe exercises won't always tell you exactly what you need to do.\r\n\r\nInstead, it will point you to the right resources (like documentation and blog posts) to find the answer.\r\n\r\nReady to begin?\r\n---------------\r\n\r\nHead over to [the first lab](labs/00-getting-started.md) to begin.\r\n\r\n## Cheat sheet\r\n\r\nFor a quick reference of the most common docker commands, see [CHEATSHEET.md](CHEATSHEET.md).\r\n\r\n\r\n"
  },
  {
    "path": "labs/00-getting-started.md",
    "content": "# Getting started\n\nIn this section you will install docker.\n\n## Terminology\n\nThroughout these labs, you will see a lot of Docker-specific jargon which might be confusing to some. So before you go further, let's clarify some terminology that is used frequently in the Docker ecosystem.\n\n*Docker Container*: An isolated, runnable environment that holds everything needed to run an application.\n*Docker Image*: A lightweight, standalone package that contains all necessary code, libraries, and dependencies to run an application.\n- *Docker daemon* - The background service running on the host that manages building, running and distributing Docker containers.\n- *Docker client* - The command line tool that allows the user to interact with the Docker daemon.\n- *Docker Hub* - A [docker registry](https://hub.docker.com/explore/) of Docker images. You can think of the registry as a directory of all available Docker images. You'll be using this later in this tutorial.\n\n## Installing Docker\n\nDepending on what OS you are running, installation is different, but head over to the [Get started](https://www.docker.com/get-started/) website and follow the instructions there.\n"
  },
  {
    "path": "labs/01-hello-world.md",
    "content": "# hello-world\n\n## Learning Goals\n\nThe goal of this scenario is to make you run your first Docker container.\n\n## Terminology\n\n*Docker Container*: An isolated, runnable environment that holds everything needed to run an application.\n*Docker Image*: A lightweight, standalone package that contains all necessary code, libraries, and dependencies to run an application.\n\n## Exercise\n\nTry running a command with Docker:\n\n```\ndocker run hello-world\n```\n\nYour terminal output should look like this:\n\n```\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\n78445dd45222: Pull complete\nDigest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7\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 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 container run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://cloud.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n\nThis message shows that your installation appears to be working correctly.\n\n_*Q: So what did this do?*_\n\nTry to run `docker run hello-world` again.\n\nDocker has now already downloaded the image locally, and can therefore execute the container straight away.\n"
  },
  {
    "path": "labs/02-running-images.md",
    "content": "# Running your first container from image\n\n## Learning Goals\n\n\n- Run an [Alpine Linux](http://www.alpinelinux.org/) container (a lightweight linux distribution) on your system and get a taste of the `docker run` command.\n\n## Introduction\n\nTo get started with running your first container from an image, you'll first pull the Alpine Linux image, a lightweight Linux distribution, and then explore various commands to interact with it.\n\n## Exercise\n\n### Overview\n\n- Pull the Alpine Linux image.\n- List all images on your system.\n- Run a Docker container based on the Alpine image.\n- Explore various commands inside the container.\n- Understand container naming and IDs.\n\n### Step by step instructions\n\n\nTo get started, let's run the following in our terminal:\n\n* `docker pull alpine`\n\nThe `pull` command fetches the alpine **image** from the **Docker registry** and saves it in your system. You can use the `docker image ls` command to see a list of all images on your system.\n\n* `docker image ls`\n\nExpected output (your list of images will look different):\n\n``` bash\nREPOSITORY              TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\nalpine                  latest              c51f86c28340        4 weeks ago         1.109 MB\nhello-world             latest              690ed74de00f        5 months ago        960 B\n```\n\n## 1.1 docker run\n\nLet's run a Docker **container** based on this image.\n\n* `docker run alpine ls -l`\n\nExpected output:\n\n```bash\ntotal 48\ndrwxr-xr-x    2 root     root          4096 Mar  2 16:20 bin\ndrwxr-xr-x    5 root     root           360 Mar 18 09:47 dev\ndrwxr-xr-x   13 root     root          4096 Mar 18 09:47 etc\ndrwxr-xr-x    2 root     root          4096 Mar  2 16:20 home\ndrwxr-xr-x    5 root     root          4096 Mar  2 16:20 lib\n......\n......\n```\n\nWhen you run `docker run alpine`, you provided a command (`ls -l`), so Docker started the command specified and you saw the listing.\n\nTry run the following:\n\n* `docker run alpine echo \"hello from alpine\"`\n\nExpected output:\n\n``` bash\nhello from alpine\n```\n\n<details>\n<summary>More Details</summary>\nIn this case, the Docker client ran the `echo` command in our alpine container and then exited it. If you've noticed, all of that happened pretty quickly. Imagine booting up a virtual machine, running a command and then killing it. Now you know why they say containers are fast!\n\n</details>\n\nTry another command:\n\n* `docker run alpine /bin/sh`\n\nWait, nothing happened! Is that a bug? \n\nWell, no. \n\nThese interactive shells will exit after running any scripted commands, unless they are run in an interactive terminal - so for this example to not exit, you need to add the parameters `i` and `t`.\n\n> :bulb: The flags `-it` are short for `-i -t` which again are the short forms of `--interactive` (Keep STDIN open) and  `--tty` (Allocate a terminal).\n\n* `docker run -it alpine /bin/sh`\n\nYou are inside the container shell and you can try out a few commands like `ls -l`, `uname -a` and others. \n\n* Exit out of the container by giving the `exit` command.\n\nOk, now it's time to list our containers. \n\nThe `docker ps` command shows you all containers that are currently running.\n\n* `docker ps`\n\nExpected output:\n\n```\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n```\n\nNotice that you have no running containers. When you wrote `exit` in the shell, the primary process (`/bin/sh`) stopped. No containers are running, you see a blank line. Let's try a more useful variant, listing all containers, both stopped and started.\n\n* `docker ps -a`\n\nExpected output:\n\n```\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES\n36171a5da744        alpine              \"/bin/sh\"                5 minutes ago       Exited (0) 2 minutes ago                        fervent_newton\na6a9d46d0b2f        alpine              \"echo 'hello from alp\"   6 minutes ago       Exited (0) 6 minutes ago                        lonely_kilby\nff0a5c3750b9        alpine              \"ls -l\"                  8 minutes ago       Exited (0) 8 minutes ago                        elated_ramanujan\nc317d0a9e3d2        hello-world         \"/hello\"                 34 seconds ago      Exited (0) 12 minutes ago                       stupefied_mcclintock\n```\n\nWhat you see above is a list of all containers that you ran. Notice that the `STATUS` column shows that these containers exited a few minutes ago.\n\n## Naming your container\n\nTake a look again at the output of the `docker ps -a`:\n\n* `docker ps -a`\n\nExpected output:\n\n```\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES\n36171a5da744        alpine              \"/bin/sh\"                5 minutes ago       Exited (0) 2 minutes ago                        fervent_newton\na6a9d46d0b2f        alpine              \"echo 'hello from alp\"   6 minutes ago       Exited (0) 6 minutes ago                        lonely_kilby\nff0a5c3750b9        alpine              \"ls -l\"                  8 minutes ago       Exited (0) 8 minutes ago                        elated_ramanujan\nc317d0a9e3d2        hello-world         \"/hello\"                 34 seconds ago      Exited (0) 12 minutes ago                       stupefied_mcclintock\n```\n\nAll containers have an **ID** and a **name**. \n\nBoth the ID and name is generated every time a new container spins up with a random seed for uniqueness.\n\n> :bulb: Tip: If you want to assign a specific name to a container then you can use the `--name` option. That can make it easier for you to reference the container going forward.\n\n## Summary\n\nThat concludes a whirlwind tour of the `docker run` command which would most likely be the command you'll use most often. It makes sense to spend some time getting comfortable with it. To find out more about `run`, use `docker run --help` to see a list of all flags it supports. As you proceed further, we'll see a few more variants of `docker run`.\n"
  },
  {
    "path": "labs/03-deletion.md",
    "content": "# Throw your container away\n\nAs containers are just a thin base layer on top of the host kernel, it is really fast to spin up a new instance if you crashed your old one.\n\nLet's try to run an alpine container and delete the file system.\n\nSpin up the container with `docker run -ti alpine`\n\nand then list all the folders on the root level to see the whole distribution:\n\n```\nls /\n```\n\nExpected output:\n\n```\nbin    etc    lib    mnt    root   sbin   sys    usr\ndev    home   media  proc   run    srv    tmp    var\n```\n\nList the current user:\n``` bash\nwhoami\n```\nExpected output:\n\n```\nroot\n```\n\nList the current date:\n``` bash\ndate\n```\n\nExpected output:\n\n```\nWed Nov\n```\n\n\n> **Warning:** Make sure that you are inside your container. most likely you can see that by your command promt showing `/ #` instead of `ubuntu@inst1:~/docker-katas/labs$`\n\nNow, delete the binaries that the system is build up of with:\n\n```\nrm -rf /bin\n```\n\nTry to navigate around to see how much of the OS is gone: try to run the `ls`, `whoami` and `date` commands.\nThey should all echo back that the binary is not found.\n\nExit out by pressing `Ctrl+d` and create a new instance of the Alpine image and look a bit around:\n\n```\ndocker run -it alpine\n```\n\nIn the container run:\n\n```\nls /\n```\n\nExpected output:\n\n```\nbin    etc    lib    mnt    root   sbin   sys    usr\ndev    home   media  proc   run    srv    tmp    var\n```\n\nTry to perform the same tasks as displayed above to see that you have a fresh new instance ready to go.\n\n## Auto-remove a container after use\n\nEvery time you create a new container, it will take up some space, even though it usually is minimal.\nTo see what your containers are taking up of space try to run the `docker container ls -as` command.\n\n```bash\nCONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                      PORTS                                                          NAMES               SIZE\n4b09b2fe1d8c        alpine                    \"/bin/sh\"                7 seconds ago       Exited (1) 1 second ago                                                                    silly_jones         0B (virtual 3.97MB)\n```\n\nHere you can see that the alpine image itself takes 3.97MB, and the container itself takes 0B. When you begin to manipulate files in your container, the size of the container will rise.\n\nIf you are creating a lot of new containers eg. to test something, you can tell the Docker daemon to remove the container once stopped with the `--rm` option:\n`docker run --rm -it alpine`\n\nThis will remove the container immediately after it is stopped.\n\n## Cleaning up containers you do not use anymore\n\nContainers are still persisted, even though they are stopped.\nIf you want to delete them from your server you can use the `docker rm` command.\n`docker rm` can take either the `CONTAINER ID` or `NAME` as seen above. \n\nTry to remove the `hello-world` container:\n\n```\n docker container ls -a\n```\n\nExpected output:\n\n```\nCONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                      PORTS                                                          NAMES\n6a9246ff53cb        hello-world               \"/hello\"                 18 seconds ago      Exited (0) 16 seconds ago                                                                  ecstatic_cray\n```\n\nDelete the container:\n\n```\ndocker container rm ecstatic_cray\n```\n\nThe name or ID specified is echoed back:\n\n```\necstatic_cray\n```\n\nThe container is now gone when you execute a `ls -a` command.\n\n> :bulb: **Tip:** As with Git, you can use any unique part of the container ID to refer to it.\n\n### Deleting images\n\nYou deleted the container instance above, but not the image of hello-world itself. And as you are now on the verge to become a docker expert, you do not need the hello-world image anymore so let us delete it.\n\nFirst off, list all the images you have downloaded to your computer:\n\n```\ndocker image ls\n```\n\nExpected output:\n\n```\nREPOSITORY                              TAG                   IMAGE ID            CREATED             SIZE\nalpine                                  latest                053cde6e8953        9 days ago          3.97MB\nhello-world                             latest                48b5124b2768        10 months ago       1.84kB\n```\n\nHere you can see the images downloaded as well as their size.\nTo remove the hello-world image use the `docker image rm` command together with the id of the docker image.\n\n```\ndocker image rm 48b5124b2768\n```\n\nExpected output:\n\n```\nUntagged: hello-world:latest\nUntagged: hello-world@sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7\nDeleted: sha256:48b5124b2768d2b917edcb640435044a97967015485e812545546cbed5cf0233\nDeleted: sha256:98c944e98de8d35097100ff70a31083ec57704be0991a92c51700465e4544d08\n```\n\nWhat docker did here was to `untag` the image removing the references to the sha of the image. After the image has no references, it deletes the two layers the image itself is comprised of.\n\n### Cleaning up\n\nWhen building, running and rebuilding images, you download and store a lot of layers. These layers will not be deleted, as docker takes a very conservative approach to clean up.\n\nDocker provides a `prune` command, taking all dangling containers/images/networks/volumes.\n\n- `docker container prune`\n- `docker image prune`\n- `docker network prune`\n- `docker volume prune`\n\nThe docker image prune command allows you to clean up unused images. By default, docker image prune only cleans up dangling images. A dangling image is one that is not tagged and is not referenced by any container. To remove all _unused_ resources, resources that are not directly used by any existing containers, use the `-a` switch as well.\n\nIf you want a general cleanup, then `docker system prune` is your friend.\n\n## Summary\n\nYou have now seen the swiftness of creating a new container from an image, trash it, and create a new one on top of it.\nYou have learned to use `container rm` for deleting containers, `image rm` for images, `image ls` for listing the images and `container ls -a` to look at all the containers on your host.\n"
  },
  {
    "path": "labs/04-port-forward.md",
    "content": "# A basic webserver\n\nRunning arbitrary Linux commands inside a Docker container is fun, but let's do something more useful.\n\nPull down the `nginx` Docker image from the Docker Hub. This Docker image uses the [Nginx](http://nginx.org/) webserver to serve a static HTML website.\n\nStart a new container from the `nginx` image that exposes port 80 from the container to port 8080 on your host. You will need to use the `-p` flag with the docker container run command.\n\n> :bulb: Mapping ports between your host machine and your containers can get confusing.\n> Here is the syntax you will use:\n>\n> ```\n> docker run -p 8080:80 nginx\n> ```\n>\n> The trick is to remember that **the host port always goes to the left**,\n> and **the container port always goes to the right.**\n> Remember it as traffic coming _from_ the host, _to_ the container.\n\nOpen a web browser and go to port 8080 on your host. The exact address will depend on how you're running Docker today:\n\n- **Native Linux** - [http://localhost:8080](http://localhost:8080)\n- **Cloud server** - Make sure firewall rules are configured to allow traffic on port 8080. Open browser and use the hostname (or IP) for your server.\n  Ex: [http://inst1.prefix.eficode.academy:8080](http://inst1.prefix.eficode.academy:8080) -\n  Alternatively open a new shell and issue `curl localhost:8080`\n- **Google Cloud Shell** - Open Web Preview (upper right corner)\n\nIf you see a webpage saying \"Welcome to nginx!\" then you're done!\n\nIf you look at the console output from docker, you see nginx producing a line of text for each time a browser hits the webpage:\n\n```\ndocker run -p 8080:80 nginx\n```\n\nExpected output:\n\n```\n172.17.0.1 - - [31/May/2017:11:52:48 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0\" \"-\n```\n\nPress **control + c** in your terminal window to stop your container.\n\n## Running the webserver container in the background\n\nWhen running a webserver like nginx, it is very useful to not run the container in the foreground of our terminal.\nInstead we should make it run in the background, freeing up our terminal for other things.\nDocker enables this with the `-d` parameter for the `run` command.\nFor example: `docker run -d -p 8080:80 nginx`\n\n```\ndocker run -p 8080:80 -d nginx\n```\n\nDocker prints out the container ID and returns to the terminal.\n\nCongratulations! You have just started a container in the background. :tada:\n\n## Cleanup\n\nStop the container you just started.\nRemember that your container ID is different from the one in the example.\n\n```bash\ndocker stop 78c943461b49584ebdf841f36d113567540ae460387bbd7b2f885343e7ad7554\n```\nDocker prints out the ID of the stopped container.\n\n```\n78c943461b49584ebdf841f36d113567540ae460387bbd7b2f885343e7ad7554\n```\n"
  },
  {
    "path": "labs/05-executing.md",
    "content": "# Executing processes in your container\n\nIt you want to examine a running container, but do not want to disturb the running process you can execute another process inside the container with `exec`.\n\nThis could be a shell, or a script of some sort. In that way you can debug an existing environment before starting a new up.\n\n## Exercise\n\nIn this exercise, we want to change a file in an already running container, by executing a secondary process.\n\n### Step by step\n\n- Spin up a new NGINX container: `docker run -d -p 8080:80 nginx`\n- Visit the webpage to make sure that NGINX have been setup correctly.\n\nStep into a new container by executing a bash shell inside the container:\n\n```\ndocker exec -it CONTAINERNAME bash\n```\n\n> :bulb: note that the CONTAINERNAME is the name of the NGINX container you just started.\n\nInside, we want to edit the `index.html` page, with a cli text editor called [nano](https://www.nano-editor.org/).\nBecause containers only have the bare minimum installed, we need to first install nano, and then use it:\n\n> :bulb: From the [DockerHub description](https://hub.docker.com/_/nginx) we know that the standard place for HTML pages NGINX serves is in /usr/share/nginx/html\n\n- install nano on the container: `apt-get update && apt-get install -y nano`\n- Edit the index html page: `nano /usr/share/nginx/html/index.html`\n- Save and exit nano by pressing: `CTRL + O` and `enter` to save and `CTRL + X` to exit Nano\n- Revisit the page to check that your edition is in effect.\n\n## Summary\n\nYou have tried to start a new process by the `exec` command in order to look around in a container, or to edit something.\nYou have also seen that terminating any of the the processes created with `docker exec` will not make the container stop.\n"
  },
  {
    "path": "labs/06-volumes.md",
    "content": "# Docker volumes\n\n> _Hint: This lab only covers volumes on Docker for Linux. If you are on windows or mac, things can look different._\n\nContainers should be ephemeral.\n\nThe whole idea is that you can start, stop and delete the containers without losing data.\n\nBut how can we do that for persistent workloads like databases?\n\nWe need a way to store data outside of the containers.\n\nYou have two different ways of mounting data from your container `bind mounts` and `volumes`.\n\n### Bind mount\n\nIs the simpler one to understand. It takes a host path, like `/data`, and mounts it inside your container eg. `/opt/app/data`.\n\nThe good thing about bind mount is that they are easy and allow you to connect directly to the host filesystem.\n\nThe downside is that you need to specify it at runtime, and path to mount might vary from host to host, which can be confusing when you want to run your containers on different hosts.\n\nWith bind mount you will also need to deal with backup, migration etc. in an tool outside the Docker ecosystem.\n\nAs an example, let's look at the [Nginx](https://hub.docker.com/_/nginx/) container.\n\nThe server itself is of little use, if it cannot access our web content on the host.\n\nWe need to create a mapping between the host system, and the container with the `-v` command:\n\n```bash\ndocker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx\n```\n\nThat will map whatever files are in the `/some/content` folder on the host to `/usr/share/nginx/html` in the container.\n\n> The `:ro` attribute is making the host volume read-only, making sure the container can not edit the files on the host.\n\n### A docker Volume\n\nThis is where you can use a `named` or `unnamed` volume to store the external data. The data will still be stored locally unless you have configured a storage driver for your system (Ops things, not covered here).\n\nVolumes are entities inside docker, and can be created in three different ways.\n\n- By explicitly creating it with the `docker volume create <volume_name>` command.\n- By creating a named volume at container creation time with `docker container run -d -v DataVolume:/opt/app/data nginx`\n- By creating an anonymous volume at container creation time with `docker container run -d -v /opt/app/data nginx`\n\nIn the next section, you will get to try all of them.\n\n## Step-by-Step Instructions\n\n### Bind mount\n\nTry to do the following:\n\n- `git clone` this repository down. :bulb: If you are at training the repository is already cloned on your training workstation.\n- Navigate to the `labs/volumes/` directory, which contains a file we can try to serve: `index.html`.\n- We need change `/some/content` to the right path, it must be an absolute path, starting from the root of the filesystem, (which in linux is `/`). You can use the command `pwd` (Print working directory) to display the path to where you are.\n- Now try to run the container with the `labs/volumes` directory bind mounted.\n\nThis will give you a nginx server running, serving your static files... _But on which port?_\n\n- Run a `docker ps` command to find out if it has any ports forwarded from the host.\n\nRemember the [past exercise](04-port-forward.md) on port forwarding in Docker.\n\n- Make it host the site on port 8080\n\n<details>\n<summary>How to do this?</summary>\n\nThe parameter `-p 8080:80` will map port 80 in the container to port 8080 on the host.\n\n</details>\n\n- Check that it is running by navigating to the hostname or IP with your browser, and on port 8080.\n- Stop the container with `docker stop <container_name>`.\n\n### Volumes\n\nFirst off, lets try to make a data volume called `data`:\n\n```bash\ndocker volume create data\n```\n\nDocker creates the volume and outputs the name of the volume created.\n\nIf you run `docker volume ls` you will have a list of all the volumes created and their driver:\n\n```outputs\nDRIVER              VOLUME NAME\nlocal               data\n```\n\nUnlike the bind mount, you do not specify where the data is stored on the host.\n\nIn the volume API, like for almost all other of Docker’s APIs, there is an `inspect` command giving you low level details. \n\n- run `docker volume inspect data` to see where the data is stored on the host.\n\n```json\n[\n    {\n        \"Driver\": \"local\",\n        \"Labels\": {},\n        \"Mountpoint\": \"/var/lib/docker/volumes/data/_data\",\n        \"Name\": \"data\",\n        \"Options\": {},\n        \"Scope\": \"local\"\n    }\n]\n```\n\nYou can see that the `data` volumes is mounted at `/var/lib/docker/volumes/data/_data` on the host.\n\n> **Note** we will not go through the different drivers. For more info look at Dockers own [example](https://docs.docker.com/engine/admin/volumes/volumes/#use-a-volume-driver).\n\nYou can now use this data volume in all containers. Try to mount it to an nginx server with the `docker container run --rm --name www -d -p 8080:80 -v data:/usr/share/nginx/html nginx` command.\n\n> **Note:** If the volume refer to is empty and we provide the path to a directory that contains data in the base image, that data will be copied into the volume.\n\nTry now to look at the data stored in `/var/lib/docker/volumes/data/_data` on the host:\n\n```\nsudo ls /var/lib/docker/volumes/data/_data/\n```\n\nExpected output:\n\n```\n50x.html  index.html\n```\n\nThose two files comes from the Nginx image and is the standard files the webserver has.\n\n### Attaching multiple containers to a volume\n\nMultiple containers can attach to the same volume with data. \n\nDocker doesn't handle any file locking, so applications must account for the file locking themselves.\n\n\nLet's try to go in and make a new html page for nginx to serve. \n\nWe do this by making a new ubuntu container that has the `data` volume attached to `/tmp`, and thereafter create a new html file with the `echo` command:\n\nStart the container:\n\n```\ndocker run -it --rm -v data:/tmp ubuntu bash\n```\n\nIn the container run:\n\n```\necho \"<html><h1>hello world</h1></html>\" > /tmp/hello.html\n```\n\nVerify the file was created by running in the container:\n\n```\nls /tmp\n```\n\nExpected output:\n\n```\nhello.html  50x.html  index.html\n```\n\nHead over to your newly created webpage at: `http://workstation-X.Y.eficode.academy:8080/hello.html` where X is your workstation number and Y is the prefix. It should be the same URL as your Workstation.\n\n\n## cleanup\n\nExit out of your ubuntu server and execute a `docker stop www` to stop the nginx container.\n\nRun a `docker ps` to make sure that no other containers are running.\n\n```\ndocker ps\n```\n\nExpected output:\n\n```\nCONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                                          NAMES\n```\n\nThe data volume is still present, and will be there until you remove it with a `docker volume rm data` or make a general cleanup of all the unused volumes by running `docker volume prune`.\n\n## Tips and tricks\n\nAs you have seen, the `-v` flag can both create a bind mount or name a volume depending on the syntax. If the first argument begins with a / or ~/ you're creating a bind mount. Remove that, and you're naming the volume. For example:\n\n- `-v /path:/path/in/container` mounts the host directory, `/path` at the `/path/in/container`\n- `-v path:/path/in/container` creates a volume named path with no relationship to the host.\n\n### Sharing data\n\nIf you want to share volumes or bind mount between two containers, then use the `--volumes-from` option for the second container. The parameter maps the mapped volumes from the source container to the container being launched.\n\n## More advanced docker commands\n\nBefore you go on, use the [Docker command line interface](https://docs.docker.com/engine/reference/commandline/cli/) documentation to try a few more commands:\n\n- While your detached container is running, use the `docker ps` command to see what silly name Docker gave your container. **This is one command you're going to use often!**\n- While your detached container is still running, look at its logs. Try following its logs and refreshing your browser.\n- Stop your detached container, and confirm that it is stopped with the `ps` command.\n- Start it again, wait 10 seconds for it to fire up, and stop it again.\n- Then delete that container from your system.\n\n> **NOTE:** When running most docker commands, you only need to specify the first few characters of a container's ID. For example, if a container has the ID `df4fd19392ba`, you can stop it with `docker stop df4`. You can also use the silly names Docker provides containers by default, such as `boring_bardeen`.\n\nIf you want to read more, I recommend [Digital Oceans](https://www.digitalocean.com/community/tutorials/how-to-share-data-between-docker-containers) guides to sharing data through containers, as well as Dockers own article about [volumes](https://docs.docker.com/engine/admin/volumes).\n\n## summary\n\nNow you have tried to bind volumes to a container to connect the host to the container.\n"
  },
  {
    "path": "labs/07-building-an-image.md",
    "content": "# Constructing a docker image\n\nRunning images others made is useful, but if you want to use docker for your own application, chances are you want to construct an image on your own.\n\nA [Dockerfile](https://docs.docker.com/reference/dockerfile/) is a text file containing a list of commands that the Docker daemon calls while creating an image. The Dockerfile contains all the information that Docker needs to know to run the app; a base Docker image to run from, location of your project code, any dependencies it has, and what commands to run at start-up.\n\nIt is a simple way to automate the image creation process. The best part is that the [commands](https://docs.docker.com/reference/dockerfile/) you write in a Dockerfile are _almost_ identical to their equivalent Linux commands. This means you don't really have to learn new syntax to create your own Dockerfiles.\n\n## Dockerfile commands summary\n\nHere's a quick summary of some basic commands we will use in our Dockerfile.\n\n> As a rule of thumb, all commands in CAPITAL LETTERS are intended for the docker engine. E.g.\n\n- FROM # base image\n- RUN # run a command\n- ADD and COPY # copy files into the image\n- CMD # run a command at start-up, but can be overridden\n- EXPOSE # expose a port\n- ENTRYPOINT # run a command at start-up\n\n<details>\n<summary>Click to see the full list of commands</summary>\n\nDetails:\n\n- `FROM` is always the first item in the Dockerfile. It is a requirement that the Dockerfile starts with the `FROM` command. Images are created in layers, which means you can use another image as the base image for your own. The `FROM` command defines your base layer. As argument, it takes the name of the image. Optionally, you can add the Docker Hub username of the maintainer and image version, in the format `username/imagename:version`.\n\n- `RUN` is used to build up the image you're creating. For each `RUN` command, Docker will run the command then create a new layer of the image. This way you can roll back your image to previous states easily. The syntax for a `RUN` instruction is to place the full text of the shell command after the `RUN` (e.g., `RUN mkdir /user/local/foo`). This will automatically run in a `/bin/sh` shell. You can define a different shell like this: `RUN /bin/bash -c 'mkdir /user/local/foo'`\n\n- `COPY` copies local files into the container. The files need to be in the same folder (or a sub folder) as the Dockerfile itself. An example is copying the requirements for a python app into the container: `COPY requirements.txt /usr/src/app/`.\n\n- `ADD` should only be used if you want to copy and unpack a tar file into the image. In any other case, use `COPY`. `ADD` can also be used to add a file directly from an URL; consider whether this is good practice.\n\n- `CMD` defines the commands that will run on the image at start-up. Unlike a `RUN`, this does not create a new layer for the image, but simply runs the command. There can only be one `CMD` in a Dockerfile. If you need to run multiple commands, the best way to do that is to have the `CMD` run a script. `CMD` requires that you tell it where to run the command, unlike `RUN`. So example `CMD` commands would be:\n\n```dockerfile\n  CMD [\"python3\", \"./app.py\"]\n\n  CMD [\"/bin/bash\", \"echo\", \"Hello World\"]\n```\n\n- `EXPOSE` creates a hint for users of an image that provides services on ports. It is included in the information which can be retrieved via `$ docker inspect <container-id>`.\n\n> **Note:** The `EXPOSE` command does not actually make any ports accessible to the host! Instead, this requires\n> publishing ports by means of the `-p` or `-P` flag when using `$ docker run`.\n\n- `ENTRYPOINT` configures a command that will run no matter what the user specifies at runtime.\n\n> :bulb: this is not the full list of commands, but the ones you will be using in the exercise. For a full list, see the [Dockerfile reference](https://docs.docker.com/engine/reference/builder/).\n\n</details>\n\n## Write a Dockerfile\n\nWe want to create a Docker image with a Python web app.\n\nAs mentioned above, all user images are based on a _base image_. We will build our own Python image based on [Ubuntu](https://hub.docker.com/_/ubuntu/). We'll do that using a **Dockerfile**.\n\n> :bulb: If you want to learn more about Dockerfiles, check out [Best practices for writing Dockerfiles](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/).\n\n1. We have made a small boilerplate file and app for you in the [/building-an-image](./building-an-image/) folder, so head over there and add content to the Dockerfile as described below\n\n   We'll start by specifying our base image, using the `FROM` keyword:\n\n   ```docker\n   FROM ubuntu:22.04\n   ```\n\n1. The next step is usually to write the commands of copying the files and installing the dependencies. But first we will install the Python pip package to the ubuntu linux distribution. This will not just install the pip package but any other dependencies too, which includes the python interpreter. Add the following [RUN](https://docs.docker.com/engine/reference/builder/#run) commands next:\n\n   ```docker\n   RUN apt-get update -y\n   RUN apt-get install -y python3 python3-pip python3-dev build-essential\n   ```\n\n1. Let's add the files that make up the Flask Application.\n\n   Install all Python requirements for our app to run. This will be accomplished by adding the lines:\n\n   ```docker\n   COPY requirements.txt /usr/src/app/\n   RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt\n   ```\n\n   Copy the application app.py into our image by using the [COPY](https://docs.docker.com/engine/reference/builder/#copy) command.\n\n   ```docker\n   COPY app.py /usr/src/app/\n\n   ```\n\n1. Specify the port number which needs to be exposed. Since our flask app is running on `5000` that's what we'll expose.\n\n   ```docker\n   EXPOSE 5000\n   ```\n\n   > :bulb: The `EXPOSE` instruction does not actually publish the port.\n   > It functions as a type of documentation between the person who builds the\n   > image and the person who runs the container,\n   > about which ports are intended to be published.\n   > You need the `-p`/`-P` command to actually open the host ports.\n\n1. The last step is the command for running the application which is simply - `python3 ./app.py`. Use the [CMD](https://docs.docker.com/engine/reference/builder/#cmd) command to do that:\n\n   ```docker\n   CMD [\"python3\", \"/usr/src/app/app.py\"]\n   ```\n\n   The primary purpose of `CMD` is to tell the container which command it should run by default when it is started.\n\n1. Verify your Dockerfile.\n\n   Our `Dockerfile` is now ready. This is how the file should look:\n\n   ```docker\n   # The base image\n   FROM ubuntu:22.04\n\n   # Install python and pip\n   RUN apt-get update -y\n   RUN apt-get install -y python3 python3-pip python3-dev build-essential\n\n   # Install Python modules needed by the Python app\n   COPY requirements.txt /usr/src/app/\n   RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt\n\n   # Copy files required for the app to run\n   COPY app.py /usr/src/app/\n\n   # Declare the port number the container should expose\n   EXPOSE 5000\n\n   # Run the application\n   CMD [\"python3\", \"/usr/src/app/app.py\"]\n   ```\n\n### Build the image\n\nNow that you have your `Dockerfile`, you can build your image. The `docker build` command does the heavy-lifting of creating a docker image from a `Dockerfile`.\n\nThe `docker build` command is quite simple - it takes an optional tag name with the `-t` flag, and the location of the directory containing the `Dockerfile` - the `.` indicates the current directory:\n\n```\ndocker build -t myfirstapp .\n```\n\nExpected output (at the end of the run):\n\n```\n[+] Building 79.5s (11/11) FINISHED                                                                                                      docker:default\n => [internal] load build definition from Dockerfile                                                                                               0.1s\n => => transferring dockerfile: 583B                                                                                                               0.0s\n => [internal] load metadata for docker.io/library/ubuntu:22.04                                                                                    1.6s\n => [internal] load .dockerignore                                                                                                                  0.0s\n => => transferring context: 2B                                                                                                                    0.0s\n => [1/6] FROM docker.io/library/ubuntu:22.04@sha256:0e5e4a57c2499249aafc3b40fcd541e9a456aab7296681a3994d631587203f97                              4.9s\n => => resolve docker.io/library/ubuntu:22.04@sha256:0e5e4a57c2499249aafc3b40fcd541e9a456aab7296681a3994d631587203f97                              0.0s\n => => sha256:6414378b647780fee8fd903ddb9541d134a1947ce092d08bdeb23a54cb3684ac 29.54MB / 29.54MB                                                   1.2s\n => => sha256:0e5e4a57c2499249aafc3b40fcd541e9a456aab7296681a3994d631587203f97 6.69kB / 6.69kB                                                     0.0s\n => => sha256:3d1556a8a18cf5307b121e0a98e93f1ddf1f3f8e092f1fddfd941254785b95d7 424B / 424B                                                         0.0s\n => => sha256:97271d29cb7956f0908cfb1449610a2cd9cb46b004ac8af25f0255663eb364ba 2.30kB / 2.30kB                                                     0.0s\n => => extracting sha256:6414378b647780fee8fd903ddb9541d134a1947ce092d08bdeb23a54cb3684ac                                                          3.3s\n => [internal] load build context                                                                                                                  0.0s\n => => transferring context: 469B                                                                                                                  0.0s\n => [2/6] RUN apt-get update -y                                                                                                                    9.1s\n => [3/6] RUN apt-get install -y python3 python3-pip python3-dev build-essential                                                                  54.2s\n => [4/6] COPY requirements.txt /usr/src/app/                                                                                                      0.1s \n => [5/6] RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt                                                                         4.6s \n => [6/6] COPY app.py /usr/src/app/                                                                                                                0.1s \n => exporting to image                                                                                                                             4.6s \n => => exporting layers                                                                                                                            4.6s \n => => writing image sha256:3c7c10734be5cfd548d5dba13ef5bf788fcc7e2050d1e8ecf4979801fee45ce9                                                       0.0s \n => => naming to docker.io/library/myfirstapp \n\n```\n\nIf you don't have the `ubuntu:22.04` image, the client will first pull the image and then create your image. If you do have it, your output on running the command will look different from mine.\n\nIf everything went well, your image should be ready! Run `docker image ls` and see if your image (`myfirstapp`) shows.\n\n### Run your image\n\nThe next step in this section is to run the image and see if it actually works.\n\n```\ndocker run -p 8080:5000 --name myfirstapp myfirstapp\n```\n\nExpected output:\n\n```\n * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)\n```\n\n> :bulb: remember that the application is listening on port 5000 on the Docker virtual network, but on the host it is listening on port 8080.\n\nHead over to `http://<IP>:8080` or your server's URL and your app should be live.\n\n## EXTRA Images and layers\n\nWhen dealing with docker images, a layer, or image layer, is a change on an image. Every time you run one of the commands RUN, COPY or ADD in your Dockerfile it adds a new layer, causes the image to change to the new layer. You can think of it as staging changes when you're using Git: You add a file's change, then another one, then another one...\n\nConsider the following Dockerfile:\n\n```dockerfile\n  FROM ubuntu:22.04\n  RUN apt-get update -y\n  RUN apt-get install -y python3 python3-pip python3-dev build-essential\n  COPY requirements.txt /usr/src/app/\n  RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt\n  COPY app.py /usr/src/app/\n  EXPOSE 5000\n  CMD [\"python3\", \"/usr/src/app/app.py\"]\n```\n\nFirst, we choose a starting image: `ubuntu:22.04`, which in turn has many layers.\nWe add another layer on top of our starting image, running an update on the system. After that yet another for installing the python ecosystem.\nThen, we tell docker to copy the requirements to the container. That's another layer.\n\nThe concept of layers comes in handy at the time of building images. Because layers are intermediate images, if you make a change to your Dockerfile, docker will build only the layer that was changed and the ones after that. This is called layer caching.\n\nEach layer is build on top of it's parent layer, meaning if the parent layer changes, the next layer does as well.\n\nIf you want to concatenate two layers (e.g. the update and install [which is a good idea](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run)), then do them in the same RUN command:\n\n```dockerfile\nFROM ubuntu:22.04\nRUN apt-get update && apt-get install -y \\\n python3 \\\n python3-pip \\\n python3-dev \\\n build-essential\nCOPY requirements.txt /usr/src/app/\nRUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt\nCOPY app.py /usr/src/app/\nEXPOSE 5000\nCMD [\"python3\", \"/usr/src/app/app.py\"]\n```\n\nIf you want to be able to use any cached layers from last time, they need to be run _before the update command_.\n\n> NOTE:\n> Once we build the layers, Docker will reuse them for new builds. This makes the builds much faster. This is great for continuous integration, where we want to build an image at the end of each successful build (e.g. in Jenkins). But the build is not only faster, the new image layers are also smaller, since intermediate images are shared between images.\n\nTry to move the two `COPY` commands before for the `RUN` and build again to see it taking the cached layers instead of making new ones.\n\n### Delete your image\n\nIf you make a `docker ps -a` command, you can now see a container with the name _myfirstapp_ from the image named _myfirstapp_.\n\n```\ndocker ps -a\n```\n\nExpected output (you might have more containers):\n\n```\nCONTAINER ID        IMAGE                     COMMAND                  CREATED              STATUS                      PORTS                                                          NAMES\nfcfba2dfb8ee        myfirstapp                \"python3 /usr/src/a...\"   About a minute ago   Exited (0) 28 seconds ago                                                                  myfirstapp\n```\n\nMake a `docker image ls` command to see that you have a docker image with the name `myfirstapp`\n\nTry now to first:\n\n- remove the container\n- remove the image file as well with the `image rm` [command](https://docs.docker.com/engine/reference/commandline/image_rm/).\n- make `docker image ls` again to see that it's gone.\n\n## Instructions\n\nThere are constantly getting added new keywords to the Dockerfile. You can find a list of all the keywords [here](https://docs.docker.com/engine/reference/builder/).\n\n## Summary\n\nYou learned how to write your own docker images in a `Dockerfile` with the use of the `FROM` command to choose base-images like Alpine or Ubuntu and keywords like `RUN` for executing commands,`COPY` to add resources to the container, and `CMD` to indicate what to run when starting the container.\nYou also learned that each of the keywords generates an image layer on top of the previous, and that everyone of the layers can be converted to a running container.\n"
  },
  {
    "path": "labs/08-multi-stage-builds.md",
    "content": "# Multi Stage Builds\n\n## Task: create a tiny go-application container\n\nIn [multi-stage-build/hello.go](multi-stage-build/hello.go) we have created a small go application that prints `hello world` to your terminal.\n\nYou want to containerize it - that's easy!\n\nYou don't even have to have go installed, because you can just use a `base image` that has go!\n\nThe [Dockerfile](multi-stage-build/Dockerfile) is already created for you in the same folder.\n\n## Exercise\n\n- Try building the image with `docker build`\n- Try to run it with `docker run`.\n- You should see \"Hello world!\" printed to your terminal.\n\n## Using Multi Stage Builds\n\nThe image we built has both the compiler and the compiled binary - which is too much: we only need the binary to run our application.\n\nBy utilizing multi-stage builds, we can separate the build stage (compiling) from the image we actually want to ship.\n\n## Exercise\n\n- try `docker image ls`.\n\n- Could we make it smaller? We only need the compiler on build-time, since go is a statically compiled language.\n\n- See the `Dockerfile` below, it has two `build stages`, wherein the latter stage is using the compiled artifact (the binary) from the first:\n\n```Dockerfile\n# build stage\nFROM golang:1.19 AS builder\nWORKDIR /app\nCOPY . /app\nRUN go mod download && go mod verify\nRUN cd /app && go build -o goapp\n\n# final stage\nFROM alpine\nWORKDIR /app\nCOPY --from=builder /app/goapp /app/\nENTRYPOINT ./goapp\n```\n\n- Replace the original `Dockerfile` with the one above, and try building it with a different tag.\n\n- When you run it it should still print \"Hello world!\" to your terminal.\n\n- Try inspecting the size with `docker image ls`.\n\n- Compare the size of the two images. The latter image should be much smaller, since it's just containing the go-application using `alpine` as the `base image`, and not the entire `golang`-suite of tools.\n\nYou can read more about this on: [Use multi-stage builds - docs.docker.com](https://docs.docker.com/develop/develop-images/multistage-build/)\n\nYou should see a great reduction in size, like in the example below:\n\n```\nREPOSITORY            TAG           IMAGE ID       CREATED          SIZE\nhello                 golang        5311178b692a   23 seconds ago   805MB\nhello                 multi-stage   ba46dc3143ca   2 minutes ago    7.53MB\n```\n\n## Bonus Exercise\n\nSince go is a statically compiled language, we can actually use `scratch` as the `base image`.\nThe `scratch` image is just an empty file system.\n\nHint: the \"scratch\" image has no shell, so in the Dockerfile you _also_ need to change the `ENTRYPOINT` from `shell form` to `exec form`.\nSee: `ENTRYPOINT` under [Dockerfile reference](https://docs.docker.com/engine/reference/builder/).\n\nAfter building with your new Dockerfile, inspect the size of the images.\nYour new image should be even smaller than the alpine-based image!\n\n\n```Dockerfile\nFROM golang:1.19 AS builder\nWORKDIR /app\nCOPY . /app\nRUN go mod download && go mod verify\nRUN cd /app && go build -o goapp\nFROM scratch\nWORKDIR /app\nCOPY --from=builder /app/goapp /app/\nENTRYPOINT [\"./goapp\"]\n```\n"
  },
  {
    "path": "labs/09-multi-container.md",
    "content": "# Multi container setups\n\n## Task: host a Wordpress site\n\nIn this scenario, we are going to deploy the CMS system called Wordpress.\n\n> WordPress is a free and open source blogging tool and a content management system (CMS) based on PHP and MySQL, which runs on a web hosting service.\n\nSo we need two containers:\n\n- One container that can serve the Wordpress PHP files\n- One container that can serve as a MySQL database for Wordpress.\n\nBoth containers already exists on the dockerhub: [Wordpress](https://hub.docker.com/_/wordpress/) and [Mysql](https://hub.docker.com/_/mysql/).\n\n## Separate containers\n\n> [!NOTE]\n> The following two sections are only an example to show how you can run multi-container setups with `docker run`.\n> Please skip ahead to [Using Docker compose](#using-docker-compose) to continue with the exercise.\n\nTo start a mysql container, issue the following command\n\n```bash\ndocker run --name mysql-container --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=wordpressdb -d mysql:5.7.36\n```\n\nLet's recap what this command does:\n\n- `docker` invokes the docker engine\n- `run` tells docker to run a new container off an image\n- `--name mysql-container` gives the new container a name for better referencing\n- `--rm` tells docker to remove the container after it is stopped\n- `-p 3306:3306` mounts the host port 3306, to the containers port 3306.\n- `-e MYSQL_ROOT_PASSWORD=wordpress` The `-e` option is used to inject environment variables into the container.\n- `-e MYSQL_DATABASE=wordpressdb` denotes the name of the database created when mysql starts up.\n- `-d` runs the container detached, in the background.\n- `mysql` tells what container to actually run, here mysql:latest (:latest is the default if nothing else is specified)\n\nMySQL is now exposing it's port 3306 on the host, and everybody can attach to it **so do not do this in production without proper security settings**.\n\nWe need to connect our wordpress container to the host's IP address.\nYou can either use the external IP address of your server, or the DNS name if you are at a training, e.g. `workstation-<num>.<prefix>.eficode.academy`.\n\nAfter you have noted down the IP, spin up the wordpress container with the host IP as a variable:\n\n```bash\ndocker run --name wordpress-container --rm -e WORDPRESS_DB_HOST=172.17.0.1 -e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_NAME=wordpressdb -p 8080:80 -d wordpress:5.7.2-apache\n```\n\nYou can now browse to the IP:8080 and have your very own wordpress server running. Since port 3306 is the default MySQL port, wordpress will try to connect on that port by itself.\n\n- Stop the two containers again `docker stop wordpress-container mysql-container`\n\n## Making a container network\n\nEven though we in two commands made the setup running in the above scenario, there are some problems here we can fix:\n\n- We need to know the host IP to get them to talk to each other.\n- And we have exposed the database to the outside world.\n\nIn order to connect multiple docker containers without binding them to the hosts network interface we need to create a docker network.\n\nThe `docker network` command securely connect and provide a channel to transfer information from one container to another.\n\nFirst off make a new network for the containers to communicate through:\n\n`docker network create if_wordpress`\n\nDocker will return the `networkID` for the newly created network. You can reference it by name as well as the ID.\n\nNow you need to connect the two containers to the network, by adding the `--network` option:\n\n```bash\ndocker run --name mysql-container --rm --network if_wordpress -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=wordpressdb -d mysql:5.7.36\naf38acac52301a7c9689d708e6c3255704cdffb1972bcc245d67b02840983a50\n\ndocker run --name wordpress-container --rm --network if_wordpress -e WORDPRESS_DB_HOST=mysql-container -e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_NAME=wordpressdb -p 8080:80 -d wordpress:5.7.2-apache\nfd4fd096c064094d7758cefce41d0f1124e78b86623160466973007cf0af8556\n```\n\nNotice the `WORDPRESS_DB_HOST` env variable. When you make a container join a network, it automatically gets the container name as DNS name as well, making it super easy to make containers discover each other. The DNS name is only visible inside the Docker network, which is also true for the `IP` address (usually an address starting with `172`) that is assigned to them. If you do not expose a port for a container, the container is only visible to Docker.\n\nYou have now deployed both containers into the network. Take a deeper look into the container network by issuing: `docker network inspect if_wordpress`.\n\n```bash\ndocker network inspect if_wordpress\n```\n\nExpected output:\n\n```json\n[\n  {\n    \"Name\": \"if_wordpress\",\n    \"Id\": \"04e073137ff8c71b9a040ba452c12517ebe5a520960a78bccb7b242b723b5a21\",\n    \"Created\": \"2017-11-28T17:20:37.83042658+01:00\",\n    \"Scope\": \"local\",\n    \"Driver\": \"bridge\",\n    \"EnableIPv6\": false,\n    \"IPAM\": {\n      \"Driver\": \"default\",\n      \"Options\": {},\n      \"Config\": [\n        {\n          \"Subnet\": \"172.18.0.0/16\",\n          \"Gateway\": \"172.18.0.1\"\n        }\n      ]\n    },\n    \"Internal\": false,\n    \"Attachable\": false,\n    \"Ingress\": false,\n    \"Containers\": {\n      \"af38acac52301a7c9689d708e6c3255704cdffb1972bcc245d67b02840983a50\": {\n        \"Name\": \"mysql-container\",\n        \"EndpointID\": \"96b4befec46c788d1722d61664e68bfcbd29b5d484f1f004349163249d28a03d\",\n        \"MacAddress\": \"02:42:ac:12:00:02\",\n        \"IPv4Address\": \"172.18.0.2/16\",\n        \"IPv6Address\": \"\"\n      },\n      \"fb4dad5cd82b5b40ee4f7f5f0249ff4b7b4116654bab760719261574b2478b52\": {\n        \"Name\": \"wordpress-container\",\n        \"EndpointID\": \"2389930f52893e03a15fdc28ce59316619cb061e716309aa11a2716ef09cde17\",\n        \"MacAddress\": \"02:42:ac:12:00:03\",\n        \"IPv4Address\": \"172.18.0.3/16\",\n        \"IPv6Address\": \"\"\n      }\n    },\n    \"Options\": {},\n    \"Labels\": {}\n  }\n]\n```\n\nAs, we have linked both the container now wordpress container can be accessed from browser using the address [http://localhost:8080](http://localhost:8080) and setup of wordpress can be done easily. MySQL is not accessible from the outside so security is much better than before.\n\n### Cleanup\n\nClose both of the containers down by issuing the following command:\n\n```bash\ndocker stop wordpress-container mysql-container\n```\n\n## Using Docker compose\n\nIf you have started working with Docker and are building container images for your application services, you most likely have noticed that after a while you may end up writing long `docker container run` commands.\nThese commands, while very intuitive, can become cumbersome to write, especially if you are developing a multi-container applications and spinning up containers quickly.\n\n[Docker Compose](https://docs.docker.com/compose/install/) is a “_tool for defining and running your multi-container Docker applications_”.\n\nYour applications can be defined in a YAML file where all the options that you used in `docker run` are defined.\n\nCompose also allows you to manage your application as a single entity rather than dealing with individual containers.\n\nThis file defines all of the containers and settings you need to launch your set of clusters. The properties map onto how you use the docker run commands, however, are now stored in source control and shared along with your code.\n\n## Terminology\n\n- `docker-compose.yml` The YAML file where all your configuration of your docker containers go.\n- `docker compose` The cli tool that enables you to define and run multi-container applications with Docker\n\n  - `up` : creates and starts the services stated in the compose file\n  - `down` : stops and removes containers, networks, images, and volumes\n  - `restart` :\n  - `logs` : streams the acummulated logs from all the containers in the compose file\n  - `ps` : same as `docker ps`; shows you all containers that are currently running.\n  - `rm` : removes all the containers from the given compose file.\n  - `start` : starts the services\n  - `stop` : stops the services\n\nThe docker cli is used when managing individual containers on a docker engine.\nIt is the client command line to access the docker daemon api.\n\nThe docker compose cli together with the yaml files can be used to manage a multi-container application.\n\n## Compose-erizing your wordpress\n\nSo we want to take advantage of docker compose to run our wordpress site.\n\nIn order to to this we need to:\n\n1. Transform our setup into a docker-compose.yaml file\n1. Invoke docker compose and watch the magic happen!\n\nHead over to this labs folder:\n\n`cd labs/multi-container`\n\nOpen the file `docker.compose.yaml` with a text editor:\n\n`nano docker-compose.yaml`\n\nYou should see something like this:\n\n```yaml\nservices:\n  #  wordpress-container:\n\n  mysql-container:\n    image: mysql:5.7\n    ports:\n      - 3306:3306\n    environment:\n      MYSQL_ROOT_PASSWORD: wordpress\n      MYSQL_DATABASE: wordpressdb\n```\n\nThis is the template we are building our compose file upon so let's drill this one down:\n\n- `services` is the section where we put our containers\n  - `wordpress-container` is the section where we define our wordpress container\n  - `mysql-container` is the ditto of MySQL.\n\n> For more information on docker compose yaml files, head over to the [documentation](https://docs.docker.com/compose/overview/).\n\nThe `services` part is equivalent to our `docker container run` command. Likewise there is a `network` and `volumes` section for those as well corresponding to `docker network create` and `docker volume create`.\n\nLet's look the mysql-container part together, making you able to create the other container yourself. Look at the original command we made to spin up the container:\n\n`docker container run --name mysql-container --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=wordpressdb -d mysql:5.7.36`\n\nThe command gives out following information: a `name`, a `port` mapping, two `environment` variables and the `image` we want to run.\n\nNow look at the docker compose example again:\n\n- `mysql-container` defines the name of the container\n- `image:wordpress` describes what image the container spins up from.\n- `ports` defines a list of port mappings from host to container\n- `environment` describes the `-e` variable made before in a yaml list\n\nInstead of keeping sensitive information in the `docker-compose.yml` file, you can also use an [`.env`](https://docs.docker.com/compose/env-file/) file to keep all the environment variables. That way, it's easier to make a development environment and a production environment with the same `docker-compose.yml`.\n\n```conf\nMYSQL_ROOT_PASSWORD=wordpress\n```\n\nTry to spin up the container in detached mode:\n\n```bash\ndocker compose up -d\nCreating network \"multicontainer_default\" with the default driver\nCreating multicontainer_mysql-container_1 ...\nCreating multicontainer_mysql-container_1 ... done\n```\n\nLooking at the output you can see that it made a `docker network` named `multicontainer_default` as well as the MySQL container named `multicontainer_mysql-container_1`.\n\nIssue a `docker container ls` as well as `docker network ls` to see that both the container and network are listed.\n\nTo shut down the container and network, issue a `docker compose down`\n\n> **note**: The command docker compose down removes the containers and default network.\n\n### Creating the wordpress container\n\nYou now have all the pieces of information to make the Wordpress container. We've copied the run command from before if you can't remember it by heart:\n\n```bash\ndocker run --name wordpress-container --rm --network if_wordpress -e WORDPRESS_DB_HOST=mysql-container -e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_NAME=wordpressdb -p 8080:80 -d wordpress:5.7.2-apache\n```\n\nYou must\n\n- uncomment the `wordpress-container` part of the services section\n- map the pieces of information from the docker container run command to the yaml format.\n- remove MySQL port mapping to close that from outside reach.\n\nWhen you made that, run `docker compose up -d` and access your wordpress site from [http://IP:8080](http://IP:8080)\n\n> **Hint**: If you are stuck, look at the file docker-compose_final.yaml in the same folder.\n"
  },
  {
    "path": "labs/README.md",
    "content": "# Docker labs\n\nIn this folder are a lot of exercises. They are numbered in the way we think makes sence to introduce the concepts.\n\nBelow is a cheatsheet for many of the commands we will touch uppon in the lab.\n\n```bash\ndocker build -t friendlyname .              # Create image using this directory's Dockerfile\n\ndocker container run -p 4000:80 friendlyname    # Run \"friendlyname\" mapping port 4000 to 80\n\ndocker container run -d -p 4000:80 friendlyname           # Same thing, but in detached mode\n\ndocker container run -ti friendlyname               # Run \"friendlyname\" in interactive mode\n\ndocker container ls                                            # List all running containers\n\ndocker container ls -a                         # List all containers, even those not running\n\ndocker container exec -it <hash> bash           # Interacts with container and executes bash\n\ndocker container stop <hash>                       # Gracefully stop the specified container\n\ndocker container kill <hash>                     # Force shutdown of the specified container\n\ndocker container rm <hash>                    # Remove specified container from this machine\n\ndocker container prune                                       # Remove all stopped containers\n\ndocker volume create <name>                 # Creates a named volume with the default driver\n\ndocker volume inspect <name>              # prints out details about the given volume entity\n\ndocker volume rm <name>                           # removes the given volume from the system\n\ndocker image ls -a                                         # List all images on this machine\n\ndocker image rm <image id>                        # Remove specified image from this machine\n\ndocker image prune                          # Remove all 'dangling' images from this machine\n\ndocker image prune -a  # Remove all images without at least one container associated to them\n\ndocker system prune # delete all unused data; containers, volumes and images w.o. containers\n\ndocker system df -v       # presents a summary of the space used by different docker objects\n\ndocker login                         # Log in this CLI session using your Docker credentials\n\ndocker tag <image> username/repository:tag              # Tag <image> for upload to registry\n\ndocker push username/repository:tag                        # Upload tagged image to registry\n\ndocker run username/repository:tag                               # Run image from a registry\n\nCtrl + P, Ctrl + Q                    # Detach from container you're in, but keep it running\n\nCtrl + D                        # Detach from container you're in, and stop it, same as exit\n```\n"
  },
  {
    "path": "labs/advanced/containers-on-default-bridge.md",
    "content": "# Exploration - Containers on default bridge network\nIn this exercise, you will explore various characterstics of the default network bridge, and the containers running in that network. \n\n\n## You should investigate:\n* What docker networks exist on the host network? (`docker network ls`)\n* See what network interfaces exist on the host network? Do you see docker0?\n* Inpsect docker0 bridge.\n* What does docker0 network interface on the host look like? (IP/network address, NetMask, MAC address, etc?)\n\n## Run couple of docker containers on the bridge network\n\nNote: `praqma/network-multitool` is a small image with lots of network troubleshooting tools installed in it. It also runs a nginx web server on port 80 by default. Most of our examples will use this image in the *web* role. Just think of it as nginx image, with some extra bells and whistles.\n\n```\n$ docker run --name web \\\n  -d praqma/network-multitool\n\n$ docker run --name db \\\n  -e MYSQL_ROOT_PASSWORD=secret \\\n  -d mysql\n```\n\n### You should investigate:\n* What are the IP addresses of the containers?\n* What DNS resolver do these containers use? \n* Can the containers on the default bridge network access each other by their names? IP addresses?\n* Do you see any **veth** interfaces on the host?\n* Compare MAC addresses of veth interfaces on the host and the **eth0** interfaces on each container.\n* Inspect containers for their IP addresses, MAC addresses, etc.\n* Explore what processes are listening on various network interfaces on the host.\n* Explore what processes are listening on various network interfaces on the container.\n\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n"
  },
  {
    "path": "labs/advanced/containers-on-docker-compose-network.md",
    "content": "# Exploration - Containers on docker compose bridge network\nIn this exercise, you will explore various characterstics of the docker compose network bridge, and the containers running in that network.\n\nSpoiler: Network created by `docker compose` are same as user-defined networks. These networks and the containers running in these networks exihibit similar behavior.\n\n## Create/run a multi-container `docker-compose.yml` application:\n```\n$ vi docker-compose.yml\nservices:\n  apache:\n    image: httpd:alpine\n  postgres:\n    image: postgres\n    environment:\n      - POSTGRES_PASSWORD=secret\n\n$ docker compose up -d\n\n$ docker compose ps\n```\n\nNote: `praqma/network-multitool` is a small image with lots of network troubleshooting tools installed in it. It also runs a nginx web server on port 80 by default. Most of our examples will use this image in the *web* role. Just think of it as nginx image, with some extra bells and whistles. You can  replace Apache `httpd:alpine` image with `praqma/network-multitool` image in the above example, if you want to.\n\n## You should investigate:\n* What docker networks exist on the host network? (`docker network ls`)\n* See what network interfaces exist on the host network? Do you see docker0?\n* Do you see any other/additional network interface? (e.g br-123zbc456xyz)\n* Inpsect docker0 bridge.\n* Inpsect the newly created docker compose bridge (e.g br-123zbc456xyz).\n* What does the docker compose network (bridge) interface on the host look like? (IP/network address, NetMask, MAC address, etc?)\n* What are the IP addresses of the containers?\n* What DNS resolver do these containers use?\n* Can the containers on the docker compose bridge network access each other by their names? IP addresses?\n* Do you see any **veth** interfaces on the host?\n* Compare MAC addresses of veth interfaces on the host and the **eth0** interfaces on each container.\n* Inspect containers for their IP addresses, MAC addresses, etc.\n* Explore what processes are listening on various network interfaces on the host.\n* Explore what processes are listening on various network interfaces on the container.\n\n\n\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n* iptables -L\n* iptables -t nat -L\n* iptables-save (This will not *save* any rules. It will just list them on the screen.)\n"
  },
  {
    "path": "labs/advanced/containers-on-host-network.md",
    "content": "# Exploration - Containers on host network\nIn this exercise, you will explore various characterstics of the **host network**, and the containers running in that network. \n\n## Run a container in \"host network\" mode:\n```\ndocker run --name nginx --network host -d nginx\n```\n\nOptionally, run another container in the  \"host network\" mode.\n```\ndocker run --name mysql --network host -e MYSQL_ROOT_PASSWORD=secret -d  mysql\n```\n\n \n\n## You should investigate:\n* What docker networks exist on the host? (`docker network ls`)\n* See what network interfaces exist on the host? Do you see docker0?\n* Do you see any new network interface(s)? (e.g br-123zbc456xyz)\n* Do you see any new **veth** interfaces on the host?\n* Do you see an **eth0** interface inside the container?\n* What is the IP address of the container(s)?\n* What DNS resolver do this container(s) use? \n* Can a container on this network reach/access any other containers by their names? IP addresses?\n* Inspect container(s) for their IP addresses, MAC addresses, etc.\n* Explore what processes are listening on various network interfaces on the host.\n* Explore what processes are listening on various network interfaces on the container.\n\n\n\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n* ps aux\n* iptables -L \n* iptables -t nat -L\n* iptables-save (This will not *save* any rules. It will just list them on the screen.)\n"
  },
  {
    "path": "labs/advanced/containers-on-none-network.md",
    "content": "# Exploration - Containers on none network\nIn this exercise, you will explore various characterstics of the **none** network, and the containers running in that network. \n\n## Run a container in \"none network\" mode:\n```\ndocker run --name multitool --network none -p 80:80 -d praqma/network-multitool\n```\n\nOptionally, run another container in the  \"none network\" mode.\n```\ndocker run --name mysql --network none -e MYSQL_ROOT_PASSWORD=secret -d  mysql\n```\n\n \n\n## You should investigate:\n* What docker networks exist on the host? (`docker network ls`)\n* See what network interfaces exist on the host network? Do you see docker0?\n* Do you see any new network interface(s)? (e.g br-123zbc456xyz)\n* Do you see any new **veth** interfaces on the host?\n* Do you see an **eth0** interface inside the container?\n* What is the IP address of the container(s)?\n* What DNS resolver do this container(s) use? \n* Can a container on this network reach/access any other containers by their names? IP addresses?\n* Inspect container(s) for their IP addresses, MAC addresses, etc.\n* Explore what processes are listening on various network interfaces on the host.\n* Explore what processes are listening on various network interfaces on the container.\n\n\n\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n* ps aux\n* iptables -L \n* iptables -t nat -L\n* iptables-save (This will not *save* any rules. It will just list them on the screen.)\n"
  },
  {
    "path": "labs/advanced/containers-on-user-defined-bridge.md",
    "content": "# Exploration - Containers on user-defined bridge network\nIn this exercise, you will explore various characterstics of the user-defined network bridge, and the containers running in that network.\n\n## Create a user-defined docker network bridge:\n```\ndocker network create mynet\n```\n\n## You should investigate:\n* What docker networks exist on the host network? (`docker network ls`)\n* See what network interfaces exist on the host network? Do you see docker0?\n* Do you see any other/additional network interface? (e.g br-123zbc456xyz)\n* Inspect docker0 bridge.\n* Inspect the newly created bridge `mynet`.\n* What does the new network (bridge) interface on the host look like? (IP/network address, NetMask, MAC address, etc?)\n\n## Run couple of docker containers on the user-defined bridge network\n\nNote: `praqma/network-multitool` is a small image with lots of network troubleshooting tools installed in it. It also runs a nginx web server on port 80 by default. Most of our examples will use this image in the *web* role. Just think of it as nginx image, with some extra bells and whistles.\n\nNote: You may want to stop/remove container you created in the previous exercises before running the new ones shown below.\n\n```\n$ docker run --name=web --network=mynet \\\n-d praqma/network-multitool\n\n$ docker run --name=db --network=mynet \\\n-e MYSQL_ROOT_PASSWORD=secret \\\n-d mysql\n```\n\n### You should investigate:\n* What are the IP addresses of the containers?\n* What DNS resolver do these containers use?\n* Can the containers on the user-defined bridge network access each other by their names? IP addresses?\n* Do you see any **veth** interfaces on the host?\n* Compare MAC addresses of veth interfaces on the host and the **eth0** interfaces on each container.\n* Inspect containers for their IP addresses, MAC addresses, etc.\n* Explore what processes are listening on various network interfaces on the host.\n* Explore what processes are listening on various network interfaces on the container.\n\n## Explore IPTables magic happening inside the containers:\n* Run another container on the user-defined network, with special privileges, and use that to explore the IPTables rules setup on the container.\n* Optionally, explore the iptables rules on the host as well.\n\n```\n$ docker run --cap-add=NET_ADMIN --cap-add=NET_RAW \\\n  --name multitool --network mynet \\\n  -it praqma/network-multitool /bin/bash\n\n$ iptables-save\n\n$ netstat -ntlp\n```\n\n\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n* iptables -L\n* iptables -t nat -L\n* iptables-save (This will not *save* any rules. It will just list them on the screen.)\n"
  },
  {
    "path": "labs/advanced/joining-network-and-process-namespace-of-existing-containers.md",
    "content": "# Learn how to join containers to network and process namespace of other containers\nIn this exercise, you will learn how to trouble-shoot and extract certain information from containers which are very limited in nature. Nginx and MySQL are good examples. They do not have much tools installed in them, and executing simple commands such as `ping`, `curl` or even `ps` takes a lot of effort. \n\n## Run a mysql container on default network:\n```\n$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=secret -d mysql\n\n```\n\nNote:  This container runs on the default docker bridge network, so most of the things you already explored in previous exercises.\n\n### Things you should explore:\n* Can you `exec` into the container, and run these commands? `ifconfig`, `ip`, `ping`, `curl`, `ps`, `netstat`, `top`\n* If not, what are your options? Would you choose to install these tools from various OS pacakges inside the container?\n\n## Run a **multitool** container and make it join the network of mysql container:\n```\n$ docker run --name multitool --network container:mysql \\\n  --rm -it praqma/network-multitool /bin/bash\n```\n\n\n### Things you should explore:\n* Exec into the multitool container and see what is the IP address of this container?\n* Exec into the multitool container and see what is the IP address of mysql container?\n* Is there a corresponding veth interface on the host computer for this container?\n* Is there a corresponding veth interface on the host computer for mysql container?\n* Explore IP address, MAC, DNS , settings for this container.\n* Explore IP address, MAC, DNS , settings for mysql container.\n* What are similarities and differences between the network settings of the mysql container and the multitool container?\n* Inspect docker network and mysql container - from the host.\n* Can you see what processes are running in multitool container?\n* Can you see what processes are running in mysql container?\n\n## Run a multitool (busybox) container and make it join the process namespace of the mysql container:\n```\n$ docker run --name busybox --pid container:mysql \\\n  --rm -it busybox /bin/sh\n```\n\n### Things you should explore:\n* Exec into the multitool container and see what is the IP address of this container?\n* Exec into the multitool container and see what is the IP address of mysql container?\n* Is there a corresponding veth interface on the host computer for this container?\n* Is there a corresponding veth interface on the host computer for mysql container?\n* Explore IP address, MAC, DNS , settings for this container.\n* Explore IP address, MAC, DNS , settings for mysql container.\n* What are similarities and differences between the network settings of the mysql container and the multitool container?\n* Inspect docker network and mysql container - from the host.\n* Can you see what processes are running in multitool container?\n* Can you see what processes are running in mysql container?\n\n## Run a multitool (busybox) container and make it join the network **and** process namespace of the mysql container:\n```\n$ docker run --name busybox \\\n  --network container:mysql \\\n  --pid container:mysql \\\n  --rm -it busybox /bin/sh\n```\n\n### Things you should explore:\n* Exec into the multitool container and see what is the IP address of this container?\n* Exec into the multitool container and see what is the IP address of mysql container?\n* Is there a corresponding veth interface on the host computer for this container?\n* Is there a corresponding veth interface on the host computer for mysql container?\n* Explore IP address, MAC, DNS , settings for this container.\n* Explore IP address, MAC, DNS , settings for mysql container.\n* What are similarities and differences between the network settings of the mysql container and the multitool container?\n* Inspect docker network and mysql container - from the host.\n* Can you see what processes are running in multitool container?\n* Can you see what processes are running in mysql container?\n\n# Useful commands:\n* docker ps\n* docker ls\n* docker network ls\n* docker inspect\n* docker exec -it <container name|id> </some/shell>\n* ip addr show\n* netstat\n* ps aux\n* iptables -L \n* iptables -t nat -L\n* iptables-save (This will not *save* any rules. It will just list them on the screen.)\n\n"
  },
  {
    "path": "labs/advanced/systemd/Dockerfile",
    "content": "FROM centos:7\n\nCMD [\"/usr/sbin/init\"]\n\n# Delete everything inside `/lib/systemd/system/sysinit.target.wants/`, except `systemd-tmpfiles-setup.service`\nRUN ( cd /lib/systemd/system/sysinit.target.wants/; \\\n      for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done ) \\\n && rm -f /lib/systemd/system/multi-user.target.wants/* \\\n          /etc/systemd/system/*.wants/* \\\n          /lib/systemd/system/local-fs.target.wants/* \\\n          /lib/systemd/system/sockets.target.wants/*udev* \\\n          /lib/systemd/system/sockets.target.wants/*initctl* \\\n          /lib/systemd/system/basic.target.wants/* \\\n          /lib/systemd/system/anaconda.target.wants/* \\\n && yum -y install postfix dovecot httpd \\\n && yum clean all \\\n && systemctl enable postfix dovecot httpd \n\n# Mount the following at container runtime:\n# -v /sys/fs/cgroup:/sys/fs/cgroup:ro\n# --tmpfs /run\n\nVOLUME [ \"/sys/fs/cgroup\" ]\n\n# Expose necessary ports\nEXPOSE 80 443 25 110 143 443 993 995\n\n\n# Build and run instructions:\n#############################\n\n# docker build -t local/centos7:mailserver\n# docker run -p 80:80 -p 25:25 \\\n#  -v /sys/fs/cgroup:/sys/fs/cgroup:ro \\\n#  --tmpfs /run \\\n#  -d local/centos7:mailserver\n\n"
  },
  {
    "path": "labs/advanced/systemd/README.md",
    "content": "# Build and run instructions for systemd based containers:\n\n## Build:\n```\ndocker build -t local/centos7:mailserver\n```\n\n## Run:\n```\ndocker run -p 80:80 -p 25:25 \\\n  -v /sys/fs/cgroup:/sys/fs/cgroup:ro \\\n  --tmpfs /run \\\n  -d local/centos7:mailserver\n```\n\n# Build and run using docker compose:\nThere is a docker-compose.yml, which can be used to build and run this container image. Of-course a Dockerfile is a pre-requisite.\n\n```\ndocker compose build\n\ndocker compose up -d\n```\n\n## Verify:\n\n```\n$ docker ps\nCONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS                                                                                                                                                  NAMES\n604a437cc577        systemd_mailserver   \"/usr/sbin/init\"    2 minutes ago       Up 2 minutes        0.0.0.0:25->25/tcp, 0.0.0.0:80->80/tcp, 0.0.0.0:110->110/tcp, 0.0.0.0:143->143/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:993->993/tcp, 0.0.0.0:995->995/tcp   systemd_mailserver_1\n```\n\n\n"
  },
  {
    "path": "labs/advanced/systemd/docker-compose.yml",
    "content": "services:\n  mailserver:\n    build: .\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n      - \"25:25\"\n      - \"110:110\"\n      - \"143:143\"\n      - \"993:993\"\n      - \"995:995\"\n    volumes:\n      - /sys/fs/cgroup:/sys/fs/cgroup:ro\n    tmpfs:\n      - /run  \n\n"
  },
  {
    "path": "labs/advanced/traefik/example1/README.md",
    "content": "Make sure you set up some sort of DNS / name resolution from your computer to the IP address where reverse proxy is running.\n\nFor example, your VM in the cloud has IP address `1.2.3.4` , and you have your reverse proxy running in that VM as a docker container. In that case, on your local / work computer, setup the following:\n\n```\n$ cat /etc/hosts\n127.0.0.1 localhost \n1.2.3.4   example.com www.example.com\n```\n\n**Note:** For LetsEncrypt to be able to give you certs, your public IP must be reachable through the DNS name that you are using in your labels/traefik rules, .e.g `example.com`.\n"
  },
  {
    "path": "labs/advanced/traefik/example1/docker-compose.yml",
    "content": "services:\n  traefik:\n    image: traefik:1.7\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock\n      - ${PWD}/traefik.toml:/etc/traefik/traefik.toml\n    ports:\n      - 80:80\n\n  multitool:\n    image: praqma/network-multitool\n    labels:\n      - traefik.enable=true\n      - traefik.port=80\n      - traefik.frontend.rule=Host:example.com,www.example.com\n"
  },
  {
    "path": "labs/advanced/traefik/example1/traefik.toml",
    "content": "[docker]\n  endpoint = \"unix:///var/run/docker.sock\"\n\n"
  },
  {
    "path": "labs/advanced/traefik/example2/README.md",
    "content": "# Traefik proxy running as independent docker compose app:\nIn this example, Traefik runs as an independent docker compose application. For other applications requiring it's \"proxy\" services, they need to be on the same network as of the Traefik proxy. The best way is to make a docker bridge network ourselves, and connect all containers on that network.\n\n## Create a docker bridge network - **services-network**:\n```\ndocker network create services-network\n```\n\n## Start Traefik - frond-end / proxy:\nStart the Traefik proxy service by briging up it's docker compose stack - which is compose of just one container!\n```\ncd proxy\ndocker compose up -d\ncd ..\n```\nNote: Please go through the actual `docker-compose.yml` and `traefik.toml` files under the `proxy` directory.\n\n\nVerify:\n```\n$ docker ps\nCONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS                NAMES\n6665c2a9afb2        traefik:1.7                \"/traefik\"               7 minutes ago       Up 7 minutes        0.0.0.0:80->80/tcp   proxy_traefik_1\n```\n\n## Start the  web server - back-end:\n```\ncd web\ndocker compose up -d\ncd ..\n```\nNote: Please go through the actual `docker-compose.yml` file under the `web` directory.\n\n\nVerify:\n```\n$ docker ps\nCONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS                NAMES\n65b42f52017a        praqma/network-multitool   \"/docker-entrypoint.…\"   2 minutes ago       Up 2 minutes        80/tcp, 443/tcp      web_multitool_1\n6665c2a9afb2        traefik:1.7                \"/traefik\"               7 minutes ago       Up 7 minutes        0.0.0.0:80->80/tcp   proxy_traefik_1\n```\n\n## Ensure DNS/name-resolution is in place:\nFor this example, you can setup the following in your `/etc/hosts` file:\n```\n$ cat /etc/hosts\n\n127.0.0.1\tlocalhost\tlocalhost.localdomain\n. . .\n127.0.0.1\texample.com\twww.example.com\n```\n\n## Check with curl:\n```\n$ curl example.com\nPraqma Network MultiTool (with NGINX) - 65b42f52017a - 172.20.0.3/16\n\n$ curl www.example.com\nPraqma Network MultiTool (with NGINX) - 65b42f52017a - 172.20.0.3/16\n```\n\nNotice that `curl localhost` will not work, because the proxy is listening on localhost, on port 80, expecting only the DNS names/URLs, which are configured as it's front-ends. It will ignore all other urls , and will show a `404` .\n\n```\n$ curl localhost\n404 page not found\n```\n"
  },
  {
    "path": "labs/advanced/traefik/example2/proxy/docker-compose.yml",
    "content": "services:\n  traefik:\n    image: traefik:1.7\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock\n      - ${PWD}/traefik.toml:/etc/traefik/traefik.toml\n    ports:\n      - 80:80\n    networks:\n      - services-network\n\nnetworks:\n  services-network:\n    external: true\n\n\n# Note: \"services-network\" is a bridge network created manually on the docker host,\n#         using: `$ docker network create services-network`\n#       This network needs to exist before any of the compose services are able to use it.\n"
  },
  {
    "path": "labs/advanced/traefik/example2/proxy/traefik.toml",
    "content": "[docker]\n  endpoint = \"unix:///var/run/docker.sock\"\n  network = \"services-network\"\n\n"
  },
  {
    "path": "labs/advanced/traefik/example2/web/docker-compose.yml",
    "content": "services:\n  multitool:\n    image: praqma/network-multitool\n    labels:\n      - traefik.enable=true\n      - traefik.port=80\n      - traefik.frontend.rule=Host:example.com,www.example.com\n    networks:\n      - services-network\n\nnetworks:\n  services-network:\n    external: true\n\n"
  },
  {
    "path": "labs/advanced/traefik/example3/acme.json",
    "content": ""
  },
  {
    "path": "labs/advanced/traefik/example3/docker-compose.yml",
    "content": "services:\n  traefik:\n    image: traefik:1.7\n    labels:\n      - traefik.frontend.rule=Host:traefik.example.com\n      - traefik.port=8080\n      - traefik.enable=true\n    environment:\n      # You need to specify credentials for your DNS provider,\n      #   \"if\" you are using DNS challenge in traefik.toml.\n      # You don't need these if you are using HTTP challenge.\n      #\n      AWS_ACCESS_KEY_ID: 'YOUR_AWS_ACCESS_KEY_ID_HERE'\n      AWS_SECRET_ACCESS_KEY: 'YOUR_AWS_SECRET_ACCESS_KEY_HERE'\n      AWS_DEFAULT_REGION: 'eu-central-1'\n      AWS_HOSTED_ZONE_ID: 'ABC123456789XYZ'\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock\n      - ${PWD}/traefik.toml:/etc/traefik/traefik.toml\n      # acme.json need to have 0600 permissions.\n      - ${PWD}/acme.json:/acme.json\n      # The password file is generated with command:- \n      #          \"htpasswd -c -B traefik-dashboard-users.passwd  admin\"\n      - ${PWD}/traefik-dashboard-users.passwd:/traefik-dashboard-users.passwd\n\n    ports:\n      - 80:80\n      - 443:443\n      # The way this works, e.g. traefik dashboard having it's own label as \"traefik.example.com\",\n      #   you do not need to enable port 8080.\n      # You will be able to reach Traefik dashboard through \"https://traefik.example.com\" ,\n      #   without needing to specify 8080 in front of it.\n      # Though, you can enable it if you like to.\n      # - 8080:8080 \n\n  nginx:\n    image: nginx:alpine\n    labels:\n      - traefik.frontend.rule=Host:example.com,www.example.com\n      - traefik.port=80\n      - traefik.enable=true\n\n"
  },
  {
    "path": "labs/advanced/traefik/example3/traefik.toml",
    "content": "defaultEntryPoints = [\"http\", \"https\"]\nlogLevel = \"INFO\"\n\n[docker]\n  endpoint = \"unix:///var/run/docker.sock\"\n  exposedByDefault = false\n\n\n# enabling api is not absolutely necessary, it is needed only if you need dashboard.\n[api]\n  dashboard = true\n  entrypoint = \"dashboard\"\n\n\n[entryPoints]\n  [entryPoints.http]\n  address = \":80\"\n    [entryPoints.http.redirect]\n    entryPoint = \"https\"\n\n  [entryPoints.https]\n  address = \":443\"\n    [entryPoints.https.tls]\n\n\n  [entryPoints.dashboard]\n  address = \":8080\"\n\n    [entryPoints.dashboard.auth.basic]\n    # admin/secret\n    # users = [\"admin:$2y$08$64hQda74gXS80mS63hN3xOFGVB9KA2vUOXtW.NDaBjX9pEHq7qdUa\"]\n    users = [\"kamran:$2y$05$oCAwtNymC1be9.uDq.LbG./1ENktYoGKU.KNo5o7.DL26FJVGCiY2\"]\n\n[acme]\nemail = \"kaz@praqma.net\"\nstorage = \"/acme.json\"\n\n# CA server to use.\n# Uncomment the 'caServer' line to use Let's Encrypt's staging server.\n#   Or, leave caServer line commented to goto/use production certificates.\n#\ncaServer = \"https://acme-staging-v02.api.letsencrypt.org/directory\"\n\n\nentryPoint = \"https\"\n\n\n#########################################################\n#\n# Enable this section if you want to use DNS challenge.\n# You will need to provide DNS credentials in your\n#   as environment variables in your docker-compose.yml.\n#\n#\n#  [acme.dnsChallenge]\n#  provider = \"route53\"\n#  delayBeforeCheck = 0\n#\n#    [[acme.domains]]\n#    main = \"*.example.com\"\n#\n#########################################################\n\n\n#########################################################\n#\n# Enable this section if you want to use HTTP challenge.\n#\n# onHostRule = true\n#   [acme.httpChallenge]\n#   entryPoint = \"http\"\n#\n#########################################################\n"
  },
  {
    "path": "labs/building-an-image/Dockerfile",
    "content": "# Dockerfile for docker-flask web application\n\n# Add a base image to build this image off of\nFROM \n\n# Add a default port containers from this image should expose\nEXPOSE \n\n# Add a default command for this image\nCMD \n"
  },
  {
    "path": "labs/building-an-image/app.py",
    "content": "import socket\nfrom flask import Flask\napp = Flask(__name__)\n\n@app.route('/')\ndef hello_world():\n    return \"Hello! I am a Flask application running on {}\".format(socket.gethostname())\n\n\nif __name__ == '__main__':\n    # Note the extra host argument. If we didn't have it, our Flask app\n    # would only respond to requests from inside our container\n    app.run(host='0.0.0.0')\n"
  },
  {
    "path": "labs/building-an-image/requirements.txt",
    "content": "Flask==2.3.2\n"
  },
  {
    "path": "labs/docker-compose/.gitignore",
    "content": "#Prevent leaking any premade solutions\n*.yml\nweb_content"
  },
  {
    "path": "labs/docker-compose/00-getting-started.md",
    "content": "# Getting started\n\nIn this section you will install Docker and run your container using compose.\n\n## Installing Docker\n\nDepending on what OS you are running, installation is different, but head over to the [docker compose installation instructions](https://docs.docker.com/compose/install/) website and follow the instructions there.\n"
  },
  {
    "path": "labs/docker-compose/01-hello-world.md",
    "content": "# hello-world\n\nTry running a command with docker compose:\n\n`docker compose up`\n\nYour terminal output should look like this:\n\n```bash\nno configuration file provided: not found\n```\n\nNow we need to create docker-compose.yml as the message suggests.\n\nOpen editor and create new file with following content:\n\n```yml\nservices:\n    hello-world:\n        image: hello-world:latest\n```\n\nNow try to run again `docker compose up` and you should see following message:\n\n```bash\nStarting docker-compose_hello-world_1 ... done\nAttaching to docker-compose_hello-world_1\nhello-world_1  |\nhello-world_1  | Hello from Docker!\nhello-world_1  | This message shows that your installation appears to be working correctly.\nhello-world_1  |\nhello-world_1  | To generate this message, Docker took the following steps:\nhello-world_1  |  1. The Docker client contacted the Docker daemon.\nhello-world_1  |  2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\nhello-world_1  |     (amd64)\nhello-world_1  |  3. The Docker daemon created a new container from that image which runs the\nhello-world_1  |     executable that produces the output you are currently reading.\nhello-world_1  |  4. The Docker daemon streamed that output to the Docker client, which sent it\nhello-world_1  |     to your terminal.\nhello-world_1  |\nhello-world_1  | To try something more ambitious, you can run an Ubuntu container with:\nhello-world_1  |  $ docker run -it ubuntu bash\nhello-world_1  |\nhello-world_1  | Share images, automate workflows, and more with a free Docker ID:\nhello-world_1  |  https://hub.docker.com/\nhello-world_1  |\nhello-world_1  | For more examples and ideas, visit:\nhello-world_1  |  https://docs.docker.com/get-started/\nhello-world_1  |\ndocker-compose_hello-world_1 exited with code 0\n```\n\nAs we can see. The hello-world container (tagged with latest) was started using the the compose file and your docker compose installation is working!\n\nCompose file explained:\n\n`services:`:  defines the configuration of the container\n`hello-world:`: is now the name of the service\n`image: hello-world:latest`: defines the container and its version that we're using for `hello-world` service.\n\nNow re-create using hello-world service to use ubuntu container and also add `command:` for the service with value `echo \"Hello from me\"`\n\n_*Q: So what did this do?*_\n"
  },
  {
    "path": "labs/docker-compose/02-port-forward.md",
    "content": "# A basic webserver\n\nRunning arbitrary Linux commands inside a Docker container with docker compose is quite overhead, but let's do something more useful.\n\nCreate docker-compose.yml file with following content:\n```yml\nservices:\n    nginx:\n        image: nginx:latest\n```\n\nNow run the command `docker compose pull` and you should see:\n```bash\nPulling nginx ... done\n```\nThis Docker image uses the [Nginx](http://nginx.org/) webserver to serve a static HTML website.\n\nConfigure you're nginx service to expose port 80 as 8080.\nWith: \n```yml\n        ports: \n          - \"8080:80\"\n```\n\nWhere first one is HOST and second one is CONTAINER.\n\n\nOpen a web browser and go to port 8080 on your host. The exact address will depend on how you're running Docker today:\n\n* **Native Linux** - [http://localhost:8080](http://localhost:8080)\n* **Cloud server** - Make sure firewall rules are configured to allow traffic on port 8080. Open browser and use the hostname (or IP) for your server.\nEx: [http://ec2-54-69-126-146.us-west-2.compute.amazonaws.com:8080](http://ec2-54-69-126-146.us-west-2.compute.amazonaws.com:8080) -\nAlternatively open a new shell and issue `curl localhost:8080`\n\nIf you see a webpage saying \"Welcome to nginx!\" then you're done!\n\nIf you look at the console output from docker, you see nginx producing a line of text for each time a browser hits the webpage:\n\n```bash\n$ docker compose up\nCreating docker-compose_nginx_1 ... done\nAttaching to docker-compose_nginx_1\nnginx_1  | 172.18.0.1 - - [07/Jan/2020:22:28:48 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"\n```\n\nPress **control + c** in your terminal window to stop your services.\n\n## Working with your docker container\n\nWhen running a webserver like nginx, it's pretty useful that you do not have to have an open session into the server at all times to run it.\nWe need to make it run in the background, freeing up our terminal for other things.\nDocker enables this with the `-d` parameter for run.\n`docker compose up -d`\n\n```bash\n$ docker compose up -d\nStarting docker-compose_nginx_1 ... done\n```\n\nDocker prints out the service name and returns to the terminal.\n\nTo see what services you've running from compose file you can use `docker compose ps`\n\n_*Q: What is the status?*_\n\nTo stop services you can run `docker compose down`\n\n_*Q: What is the status after stopping?*_\n"
  },
  {
    "path": "labs/docker-compose/03-volumes.md",
    "content": "# Docker volumes\n\nIn some cases you're going to need data outside of docker containers and to do that you can use volumes\n\n**A docker Volume** is where you can use a named or unnamed volume to store the external data. You would normally use a volume driver for this, but you can get a host mounted path using the default local volume driver.\n\nSo let's look at the [Nginx](https://hub.docker.com/_/nginx/) service from port-forwarding excercise.\nThe server itself is of little use, if it cannot access our web content on the host.\n\nWe can define volumes with docker compose for the service like this:\n\n```yml\nservices:\n    nginx:\n        image: nginx:latest\n        volumes:\n            - \"./web_content:/usr/share/nginx/html:ro\"\n```\n\nWhere web_content folder is mapped to nginx html folder. `:ro` defines that it's read only.\n\nTry to do the following:\n\n1. Create folder `web_content`\n2. Create index.html file under `web_content`\n3. Modify html file\n4. Restart the nginx service with compose\n\n_*Q: What do you see now in the browser?*_\n\nNow modify the index.html again and *do* not restart the container.\n\n_*Q: What do you see after you refresh the browser page?*_\n\n\n"
  },
  {
    "path": "labs/docker-compose/04-build-image.md",
    "content": "# Building docker image using docker-compose\n\nIn this excercise we're going to setup [bottlepy](https://bottlepy.org/docs/dev/) application running from our compose service.\n\nCheckout the bottle folder and you should be seeing 3 files:\n- app.py\n- Dockerfile\n- requirements.txt\n\napp.py contains our bottle web service that is running on port 8080 and it uses python3.\n\nrequirements.txt has the needed requirements for python to be installed before running the service.\n\nDockerfile is partially empty and now it's your job to fill it in in order to make the application run.\n\nApplication can be started with command `python3 /path/to/app.py` and requirements can be installed with `pip3 install -r /path/to/requirements.txt`.\n\nYour `docker-compose.yml` can look something like this:\n```yml\nservices:\n    bottle:\n      build: ./bottle/\n      ports:\n```\n\nWhere `build:` is refering the folder what docker container we should build.\n\nTo make docker compose to build you can use command `docker compose up --build` in order to execute the `build:` configuration.\n\nTry to build your application container and open browser to correct port.\n\n_*Q: What do you see on <domain>:<port>/hello/docker-is-awesome ?*_"
  },
  {
    "path": "labs/docker-compose/bottle/Dockerfile",
    "content": "# Dockerfile for docker-bottle web application\n\n# Add a base image to build this image off of\nFROM \nCOPY \n# Add a default port containers from this image should expose\nEXPOSE \n\n# Add a default command for this image\nCMD \n"
  },
  {
    "path": "labs/docker-compose/bottle/app.py",
    "content": "import sys\nfrom bottle import route, run, template\n\n@route('/')\ndef index():\n    return template(\"<h1>It works!</h1><p>{{version_info}}\", version_info=sys.version_info)\n\n@route('/hello/<name>')\ndef hello(name):\n    return template('<b>Hello {{name}}</b>!', name=name)\n\nrun(host='0.0.0.0', port=8080)\n"
  },
  {
    "path": "labs/docker-compose/bottle/requirements.txt",
    "content": "bottle==0.12.20\n"
  },
  {
    "path": "labs/exercise-template.md",
    "content": "# Template headline\n\n## Learning Goals\n\n- provide a list of goals to learn here\n\n## Introduction\n\nHere you will provide the bare minimum of information people need to solve the exercise.\n\n## Subsections\n\nYou can have several subsections if needed.\n\n<details>\n<summary>:bulb: If an explanaition becomes too long, the more detailed parts can be encapsulated in a drop down section</summary>\n</details>\n\n## Exercise\n\n### Overview\n\n- In bullets, what are you going to solve as a student\n\n### Step by step instructions\n\n<details>\n<summary>More Details</summary>\n\n**take the same bullet names as above and put them in to illustrate how far the student have gone**\n\n- all actions that you believe the student should do, should be in a bullet\n\n> :bulb: Help can be illustrated with bulbs in order to make it easy to distinguish.\n\n</details>\n\n### Clean up\n\nIf anything needs cleaning up, here is the section to do just that.\n"
  },
  {
    "path": "labs/extra-exercises/image-security.md",
    "content": "# Docker image security\n\n\n## 1. Create a new image\n\nPull the latest alpine docker image as a base:\n\n    $ docker pull alpine:latest\n\nYou can find out the Repository Digest for the image with this command:\n\n    $ docker image ls --digests alpine\n    REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE\n    alpine              latest              sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528   196d12cf6ab1        3 weeks ago         4.41MB\n\nCreate a simple Dockerfile image to build upon the fixed\n\n    mkdir myalpine\n    cd myalpine\n    repodigest=$(docker image ls --digests alpine --format \"{{.Digest}}\")\n    cat <<EOF > Dockerfile\n    FROM alpine@${repodigest}\n    MAINTAINER some maintainer <maintainer@example.com>\n\n    EXPOSE 443\n    EOF\n\nPerform a build on this image:\n\n    docker build -t myalpine:1.0 .\n\n### Checking the digests\n\nAt this stage you will note that the image does not have a digest:\n\n    docker image ls --digests myalpine\n    REPOSITORY          TAG                 DIGEST              IMAGE ID            CREATED             SIZE\n    myalpine            1.0                 <none>              85aed7cdf75d        38 minutes ago      4.41MB\n\nThis is because a digest is a sha of the registry manifest and the layers.  This does not exist until the image is pushed to a registry.\n\nTag your image with a pushable-name (i.e. starting with your dockerhub username) and push it to docker hub.  You should be able to then see the image has a digest:\n\n    docker image ls --digests meekrosoft/myalpine\n    REPOSITORY            TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE\n    meekrosoft/myalpine   1.0                 sha256:b609be091e06834208b9d1d39e7e0fbfd60b550ea5d43a5476241d6218a8ee96   85aed7cdf75d        41 minutes ago      4.41MB\n\nAsk your neighbour to pull your image and check the digest.\n\nFint!\n"
  },
  {
    "path": "labs/extra-exercises/nginx/docker-compose.yml",
    "content": "services:\n  one:\n    image: nginx:latest\n    restart: unless-stopped\n    volumes:\n      - \"./html1:/usr/share/nginx/html\"\n    ports:\n      - 9001:80\n\n  two:\n    image: nginx:latest\n    restart: unless-stopped\n    ports:\n      - 9002:80\n    volumes:\n      - \"./html2:/usr/share/nginx/html\"\n\n  nginx:\n    image: nginx:latest\n    restart: unless-stopped\n    ports:\n      - 9090:80\n    volumes:\n      - \"./nginx:/etc/nginx/conf.d\"\n    depends_on:\n      - one\n      - two\n"
  },
  {
    "path": "labs/extra-exercises/nginx/html1/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n</head>\n<body style=\"background-color:red;\">\n\n<h1 style=\"color:white;\">This is a heading</h1>\n<p style=\"color:white;\">This is a paragraph.</p>\n\n</body>\n</html>\n"
  },
  {
    "path": "labs/extra-exercises/nginx/html2/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n</head>\n<body style=\"background-color:green;\">\n\n<h1>This is a heading</h1>\n<p>This is a paragraph.</p>\n\n</body>\n</html>\n"
  },
  {
    "path": "labs/extra-exercises/nginx/network.md",
    "content": "# Docker networks\n\nWhen you spin up several containers, they all share the same internal network.\n\nIn this exercise, we all use this for creating a `nginx` server that serves content from two other containers. For simplicity, the same `nginx` container wil be used, but each serving a page with a different background color, so it's easier to see that it behaves correctly.\n\n## Creating the containers\n\nThere's a `docker-compose.yml` in this folder that contains the necessary configuration to spin up three nginx containers, configured to be a master nginx container. Then do a `docker compose up -d`. When you open a browser window, go to http://localhost:9090/ You'll see the default page from a new `nginx` installation. The configuration file here makes two entry points for the two containers. In the configuration, the host names are used, instead of IP addresses, Docker assigns IP addresses to containers, but the values can vary between runs. If you inspect the docker containers, you'll see a section with `Networks`, where the alias is listed . When docker containers are created, a host name is assigned to them, which is by default the same as the container name. In the `nginx` configuration file, the host names are used without the ports defined in the above example; this is because docker has an internal network, and the `nginx` image by default exposes port 80.\n\n## Running the containers\n\nStart the containers\n\n    $ docker compose up -d\n\nThe port numbers that are exposed were chosen a bit arbitrarily. You can remove the port configuration from container `one` and `two`, and see if the setup still works.\n"
  },
  {
    "path": "labs/extra-exercises/nginx/nginx/nginx.vh.default.conf",
    "content": "server {\n       listen 80 default_server;\n       listen [::]:80 default_server;\n\n       index index.html index.htm;\n\n       server_name _;\n\n       location /one {\n           proxy_pass http://one/index.html;\n       }\n\n       location /two {\n           proxy_pass http://two/index.html;\n       }\n\n       location / {\n           root   /usr/share/nginx/html;\n           index  index.html index.htm;\n       }\n}\n"
  },
  {
    "path": "labs/extra-exercises/secrets.md",
    "content": "# Docker Secrets\n\nApplications often require access to access tokens, passwords, and other sensitive information.  Shipping this configuration in images poses security challenges, not to mention that with containers, applications are now dynamic and portable across multiple environments, making this a poor fit.\n\nDocker secrets provide a means of managing sensitive information required at runtime independently of the build and run process.\n\n> ## Store config in the environment\n> An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes:\n>\n> * Resource handles to the database, Memcached, and other backing services\n> * Credentials to external services such as Amazon S3 or Twitter\n> * Per-deploy values such as the canonical hostname for the deploy\n> [https://12factor.net/config]\n\n## Starting with Docker Secrets\n\nTo get started, we need to be running docker in swarm mode.  Swarm is the distributed orchestration tool that originally shipped with docker, which now is being replaces with kubernetes.\n\nThe first step is to set up swarm mode on your docker host:\n\n    docker swarm init\n\nYou can see initially that there are no secrets being managed with this command:\n\n    $ docker secret ls\n    ID                  NAME                DRIVER              CREATED             UPDATED\n    $\n\nWe can create a secret like this:\n\n    $ printf \"docker1234\" | docker secret create db_pwd -\n    w3yszkcy3ip08cgnfbiggq5e6\n    $ docker secret ls\n    ID                          NAME                DRIVER              CREATED             UPDATED\n    w3yszkcy3ip08cgnfbiggq5e6   db_pwd                                  6 seconds ago       6 seconds ago\n    $\n\n\nNow we want to grant access to our secret:\n\n    $ docker service  create --name redis --secret db_pwd redis:alpine\n\nAnd we can verify that the secret is available like this:\n\n    $ docker ps --filter name=redis -q\n    5cb1c2348a59\n    $ docker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets\n    $ docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/db_pwd\n\nVerify it doesn't commit\n\n    $ docker commit $(docker ps --filter name=redis -q) committed_redis\n    $ docker run --rm -it committed_redis cat /run/secrets/my_secret_data\n\nTry removing the secret. The removal fails because the redis service is running and has access to the secret.\n\n\n    $ docker secret ls\n    ID                          NAME                DRIVER              CREATED             UPDATED\n    w3yszkcy3ip08cgnfbiggq5e6   db_pwd                                  6 seconds ago       6 seconds ago\n    $ docker secret rm db_pwd\n\n    Error response from daemon: rpc error: code = 3 desc = secret\n    'db_pwd' is in use by the following service: redis\n\n\n\nRemove access to the secret from the running redis service by updating the service.\n\n    $ docker service update --secret-rm db_pwd redis\n\nCleanup the service, secret and leave swarm mode:\n\n    $ docker service rm redis\n    $ docker secret rm db_pwd\n    $ docker swarm leave --force\n\n\n## Cheatsheet\n\n| Command | Usage |\n| --- | --- |\n| docker secret create | Create a secret from a file or STDIN as content |\n| docker secret inspect |Display detailed information on one or more secrets |\n| docker secret ls | List secrets |\n| docker secret rm | Remove one or more secrets |\n\n\n## Slides (later)\n\nHigh level overview - why secrets, diagram of how\n\n## Additonal exercises\n\n* [Docker lab on secrets](https://github.com/docker/labs/tree/master/security/secrets)\n"
  },
  {
    "path": "labs/image-best-practices.md",
    "content": "# Best practises\n\n## dockerignore\n\nBefore the docker CLI sends the context to the docker daemon, it looks for a file named `.dockerignore` in the root directory of the context. If this file exists, the CLI modifies the context to exclude files and directories that match patterns in it. This helps to avoid unnecessarily sending large or sensitive files and directories to the daemon and potentially adding them to images using ADD or COPY.\n\n> For more info on dockerignore, head over to the [documentation](https://docs.docker.com/engine/reference/builder/#dockerignore-file).\n\n## Lint your Dockerfile\n\n[Hadolint](https://hadolint.github.io/hadolint/) highlights dubious constraints in your `Dockerfile`.\n\nThe linter uses the principles described in [Docker's documentation on best practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) as the basis for the suggestions.\n\n## Consider security when building images\n\n[Snyk](https://snyk.io/blog/10-docker-image-security-best-practices/) wrote a blog with 10 things that you should consider when building images. They consider adding a label for the security policy of\nthe image, using a linter (as described above), and [signing docker images](https://docs.docker.com/notary/getting_started/).\n"
  },
  {
    "path": "labs/multi-container/docker-compose.yaml",
    "content": "services:\n  #  wordpress_container:\n\n  mysql-container:\n    image: mysql:5.7.36\n    ports:\n      - 3306:3306\n    environment:\n      MYSQL_ROOT_PASSWORD: wordpress\n      MYSQL_DATABASE: wordpressdb\n"
  },
  {
    "path": "labs/multi-container/docker-compose_final.yaml",
    "content": "services:\n  wordpress-container:\n    image: wordpress:5.7.2-apache\n    ports:\n      - 8080:80\n    environment:\n      WORDPRESS_DB_USER: root\n      WORDPRESS_DB_PASSWORD: wordpress\n      WORDPRESS_DB_HOST: mysql-container\n      WORDPRESS_DB_NAME: wordpressdb\n    depends_on:\n      - mysql-container\n\n  mysql-container:\n    image: mysql:5.7.36\n    environment:\n      MYSQL_ROOT_PASSWORD: wordpress\n      MYSQL_DATABASE: wordpressdb\n"
  },
  {
    "path": "labs/multi-stage-build/Dockerfile",
    "content": "FROM golang:1.19 AS builder\nWORKDIR /app\nCOPY . /app\nRUN go mod download && go mod verify\nRUN cd /app && go build -o goapp\nENTRYPOINT ./goapp\n"
  },
  {
    "path": "labs/multi-stage-build/go.mod",
    "content": "module example.com/mod\n"
  },
  {
    "path": "labs/multi-stage-build/hello.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n    fmt.Println(\"Hello world!\")\n}"
  },
  {
    "path": "labs/sharing-images.md",
    "content": "# Sharing images\n\nBefore we can take our dockerized Flask app to another computer, we need to push it up to Docker Hub so that publicly avaliable.\n\nDocker Hub is like GitHub for Docker images. It’s the main place people store their Docker images in the cloud.\n\n> :bulb: In order for you to do this exercise, you need to have an account. If not, create an account on the Docker Hub - it's free:\n> [https://hub.docker.com/signup](https://hub.docker.com/signup)\n\nThen, login to that account by running the `docker login` command on your laptop.\n\n> :bulb: If you do not want your password to be stored on the workstation, create an access token instead: https://hub.docker.com/settings/security\n\nWe're almost ready to push our Flask image up to the Docker Hub. We just need to rename it to our namespace (which is the same as our docker username) first.\n\nUsing the `docker tag` command, tag the image you created in the previous section to your namespace. For example, I would run:\n\n```\ndocker tag myfirstapp <your-dockerhub-username>/myfirstapp:latest\n```\n\n`myfirstapp` is the tag I used in my `docker build` commands in the previous section, and `<your-dockerhub-username>/myfirstapp:` is the full name of the new Docker image I want to push to the Hub.\nThe `:latest` is a versioning scheme you can append to.\n\nAll that's left to do is push up your image:\n\n```\ndocker push <your-dockerhub-username>/myfirstapp\n```\n\nExpected output:\n\n```\nThe push refers to a repository [docker.io/<your-dockerhub-username>/myfirstapp]\n6daf7f1140cb: Pushed\n7f74a217d86b: Pushed\n09ccfff62b13: Pushed\nf83ccb38761b: Pushed\n584a7965008a: Pushed\n33f1a94ed7fc: Mounted from library/ubuntu\nb27287a6dbce: Mounted from library/ubuntu\n47c2386f248c: Mounted from library/ubuntu\n2be95f0d8a0c: Mounted from library/ubuntu\n2df9b8def18a: Mounted from library/ubuntu\nlatest: digest: sha256:e7016870c297b3c49996ee00972d8abe7f20b4cbe45089dc914193fa894991d3 size: 2407\n```\n\nGo to your profile page on the Docker Hub and you should see your new repository listed:\n[https://hub.docker.com/repos/u/<username>](https://hub.docker.com/repos/u/<username>)\n\n**Congrats!** You just made your first Docker image and shared it with the world!\n\nTry to pull down the images that your fellows have pushed to see that it's really up there.\n\nFor more info on the Docker Hub, and the cli integration,\nhead over to [https://docs.docker.com/docker-hub/](https://docs.docker.com/docker-hub/) and read the guides there.\n"
  },
  {
    "path": "labs/volumes/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Hello, world</title>\n</head>\n<body>\n<h1>Hello, world!</h1>\n<img src=\"https://www.docker.com/wp-content/uploads/2022/03/Moby-logo.png\"  style=\"margin-top: 0px;\" >\n</body>\n</html>\n"
  },
  {
    "path": "labs/windows-docker/win1-windows-on-linux.md",
    "content": "# Dotnet core in Docker\n\nBefore starting with .NET in docker, it is important to know the following: \n- The ASP framework is supported, but is done differently. \n- .NET core runs natively in linux docker (we start with this) \n- Microsoft is doing a lot of development on Docker for Windows, and are constantly improving the entire ecosystem. Things that did not work last week might work now. \n\nAnd since there is always people wondering if it is production ready - .NET core has been production capable since 2.0 according to microsoft, and Kubernetes has support for Windows nodes. \n\nAnyway let's get started ! \n\n```\ndocker container run -it -p 5000:5000 microsoft/dotnet\n```\n\nWe expose port 5000 preemptively, since that is what our app will run on. \n\nInside the container, make a new directory (dotnet has issues running in root directory) and then make a new project:  \n\n```\nmkdir myapp\ncd myapp && dotnet new razor\n```\n\nThe project should automatically restore nuget packages, but in the unlikely case it did not you can run : \n\n```\ndotnet restore\n```\n\n.NET has a thing with containers, where it needs to expose an environmental variable telling the environment where kestrel (.NET webserver) is allowed to host solutions: \n\n```\nexport ASPNETCORE_URLS=http://*:5000\n```\n\nAnd then just run the app: \n\n```\ndotnet run\n```\n\nGo to localhost:5000 on your machine, you should have a fresh web app running (razor pages). "
  },
  {
    "path": "labs/windows-docker/win2-windows-on-windows.md",
    "content": "# ASP framework in containers\n\nBefore starting, it is important to be aware of something when working with containers and ASP framework.\n\nMicrosoft is pushing for a specific workflow with ASP framework and Docker, and making the best practice workflow (ie everything as code) is out of the scope of this workshop.\n\nThe workflow proposed by Microsoft, is to run publish (ie build) from Visual Studio, and then package that output into a container.\n\nTo make this correctly, would require us to get our hands real dirty with MSBuild and MSDeploy in powershell - but instead we will run various ready made containers to show off Windows containers as these workshop's focus is on docker.\n\nTo start off, remote desktop to the windows machine given and open powershell.\n\nRun the familiar hello-world example:\n\n```\ndocker run hello-world\n```\n\nThe interesting thing here, is that command is being executed in powershell. On windows kernel. It is NOT the same hello-world we saw previously.\n\nLet's ramp things up a bit:\n\n```\ndocker run -it microsoft/nanoserver powershell\n```\n\nIf you want leave out the \"powershell\" in the end, it will automatically execute cmd which messes a bit with the powershell of your VM.\n\nExit the container by typing `exit` to exit the container.\n\nIf you run `docker image ls`, you'll note that hello-world is built on nanoserver by looking at the size:\n\n```\nREPOSITORY                                 TAG                 IMAGE ID            CREATED             SIZE\nmicrosoft/dotnet-framework-samples         latest              e5cc04acc880        13 hours ago        12.4GB\nmicrosoft/mssql-server-windows-developer   latest              9e08a14c562e        3 days ago          11.6GB\nhello-world                                latest              b14262c9a790        2 weeks ago         1.1GB\nmicrosoft/windowsservercore                latest              1fbef5019583        3 weeks ago         10.4GB\nmicrosoft/nanoserver                       latest              edc711fca788        3 weeks ago         1.1GB\n```\n\nIt is a bit bigger than the linux equivalent.. but it does the same thing, and loads an entire windows OS while we are at it.\n\nThe base image normally run in windows is microsoft/windowsservercore - and is what you should base your windows applications on.\n\n```\ndocker run -it microsoft/windowsservercore powershell\n```\n\nThe biggest challenge working with Windows containers in my experience, has been adapting things that are natively provided to run in a container.\n\nExamples include how to build and deploy a project that normally Visual Studio and IIS took care of. This means the real tradeoff to using Windows containers is learning how Microsoft works under the hood. The gain is that a lot of the common problems with Windows go away.\n\n> remember to `exit` your container before moving to the next command-\n\nContainers will allow you to spin up things seamlessly, just like on Linux. For example:\n\n```\ndocker run -d -p 1433:1433 -e sa_password=YOUR_PWD_HERE -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer\n```\n\nWhich spins up a development server for Microsoft SQL. Since the remote desktop machines are not set up with tools, you cannot access it - but a real development machine could just use SQL management tools.\n\nOr how about the entire azure powershell commandline interface?\n\n\n```\ndocker run -it azuresdk/azure-powershell powershell\nGet-Help Add-AzureRmAccount\n```\n\nMicrosoft did a pretty good job, making it feel and seem like native docker - because it is. They have an upstream docker fork, that they pull in for releasing docker on windows.\n\n\nLet's look at some examples to finish:\n```\ndocker run microsoft/dotnet-framework-samples\n```\n\nor specify a specific ASP framework version:\n\n```\ndocker run microsoft/dotnet-framework-samples:4.7.1\n```\nThe docker file for the above example looks like this:\n\n```\n\nFROM microsoft/dotnet-framework-build:4.7.1 AS builder\nWORKDIR /app\nCOPY . ./\nRUN [\"msbuild.exe\", \"dotnetapp-4.7.1.csproj\", \"/p:Configuration=Release\"]\n\nFROM microsoft/dotnet-framework:4.7.1\nWORKDIR /app\nCOPY --from=builder /app/bin/Release .\n\nENTRYPOINT [\"dotnetapp-4.7.1.exe\"]\n```\n\nThe above dockerfile is also a way to get started with shipping apps natively on Windows, as you can publish your app and copy it in in a similar fashion. Just make use of the servercore image instead.\n\nThese are not supposed to be base images, but serve as an exellent demo that Windows is capable of running native containers.\n\nThis concludes the ASP framework exercises.\n"
  },
  {
    "path": "labs/windows-docker/win3-common-commands.md",
    "content": "# The usual set of Docker commands on Windows\n\nNow, time to show that Docker on Windows really just is Docker as you know it from Linux by now. \n\n## Volumes\n\nLet's start with volume. Make a folder in your C drive, called data and run:  \n\n```\nmkdir \\data\n\ndocker container run -it -v C:\\data:C:\\data microsoft/nanoserver powershell\n```\n\nMake some files in the directory by your nomal explorer, and run the following in your container:\n```\nPS C:\\> dir data\n\n\n    Directory: C:\\data\n\n\nMode                LastWriteTime         Length Name\n----                -------------         ------ ----\n-a----       11/29/2017  12:54 PM              0 Docker.txt\n-a----       11/29/2017  12:54 PM              0 On.txt\n-a----       11/29/2017  12:54 PM              0 Windows.txt\n```\n\n## Building \n\nSimilarly, let's play with a Dockerfile like the one below:\n```\nFROM microsoft/windowsservercore\n\nENV chocolateyUseWindowsCompression false\n\nRUN powershell -Command \\\n    iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'));\n```\n`Build` that image, and run a container based on your image.\n\nYou now have chocolatey (a package manager for Windows powershell): \n```\nchocolatey -?\n``` \nIt will print out the avaliable commands on chocolatey. Now, exit the container again.\n\n## Port forwarding\n\nLet's look at an IIS Server: \n```\ndocker run -d -p 80:80 --name iis microsoft/iis\n```\n\nWhich can be accessed via your windows machine on this ip: \n```\ndocker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' iis\n```\nYou see the welcome screen of the IIS, but that is not very usefull, so let's start making an application.\n\n## Compiling and serving code\n\nThis fresh installation does not have golang installed. We can just use a container to fix that. \nCreate the following file called `webserver.go`: \n\n```\npackage main\n\nimport (\n    \"fmt\"\n    \"net/http\"\n    \"os\"\n)\n\nfunc main() {\n    port := os.Getenv(\"PORT\")\n    if port == \"\" {\n        port = \"8080\"\n    }\n    fmt.Println(\"Proudly serving content on port\", port)\n    panic(http.ListenAndServe(fmt.Sprintf(\":%s\", port), http.FileServer(http.Dir(\".\"))))\n}\n```\nand run: \n\n```\ndocker run -it -v C:\\data:C:\\code -w C:\\code golang:nanoserver powershell\n\ngo build webserver.go\n```\n\nVoila. Webserver.exe has been put into the current directory. \n\nNow we need to serve the application in a container.\n\nMake the following dockerfile in the same directory: \n```\nFROM microsoft/nanoserver\n\nCOPY webserver.exe /webserver.exe\n\nEXPOSE 8080\n\nCMD [\"/webserver.exe\"]\n```\nAnd build an image.\n\nIIS needs to be able to find it later, and it does not run on localhost. So we need to name our container: \n```\ndocker run -d --rm --name=mysite -p 8080:8080 <yourtag>\n```\n\n> the `--rm` part makes sure that if the container stops, it gets automatically deleted.\n\nYou can access it by running: \n```\nstart http://$(docker inspect -f '{{ .NetworkSettings.Networks.nat.IPAddress }}' mysite):8080\n```\n\nIt will show you a folder view of the containers files, including the webserver.exe that is running.\n\n## Summary\n\nThis concludes the Windows bit of the workshop for now, but everything you worked with in regards to Docker works with Windows. \n\nHowever multi container builds require a newer version of Docker than the Virtual machines have, so this is something you'll have to try at home ;) \n"
  },
  {
    "path": "trainer/README.md",
    "content": "# trainer\nThis is the how for additional examples and other trainer resources for the Praqma docker-slides and katas.\n\n# Examples:\n## Basic-compose\nA simple compose example with only a single container.\n\n## scratch\nA simple example building a really empty image to demo that scratch is 0 bytes.\n"
  },
  {
    "path": "trainer/examples/basic-compose/Dockerfile",
    "content": "FROM python:3\n#FROM ubuntu:latest\n#RUN apt-get update && apt-get install -y \\\n# python-pip \\\n# python-dev \\\n# build-essential\nCOPY requirements.txt /usr/src/app/\nRUN pip install --no-cache-dir -r /usr/src/app/requirements.txt\nWORKDIR /usr/src/app/\nENTRYPOINT  [\"python\"]\n\n"
  },
  {
    "path": "trainer/examples/basic-compose/README.md",
    "content": "# Simple single container compose example\n\n## Build image\n\n```\ndocker build -t basic-compose:latest .\n```\n\n## Run image manually\n\n```\ndocker run --rm -p 8050:5000 -v $(pwd):/usr/src/app -d basic-compose:latest /usr/src/app/app.py\n```\n\nand show at [localhost:8050](http://localhost:8050) that it is running.\n\n## Run with compose\n\nShow compose content\n\nRun with:\n`docker compose up` and demo that it is running, then stop again with `docker compose down`.\n\nThen show again with `docker compose up -d`\n"
  },
  {
    "path": "trainer/examples/basic-compose/app.py",
    "content": "from flask import Flask\n\napp = Flask(__name__)\n\n@app.route('/')\ndef hello_world():\n    return 'Hello! I am Jans Flask application'\n\nif __name__ == '__main__':\n    # Note the extra host argument. If we didn't have it, our Flask app\n    # would only respond to requests from inside our container\n    app.run(host='0.0.0.0')"
  },
  {
    "path": "trainer/examples/basic-compose/docker-compose.yml",
    "content": "services:\n  app:\n    image: basic-compose:latest\n    ports:\n      - \"8050:5000\"\n    volumes:\n      - \".:/usr/src/app/\"\n    command: \"/usr/src/app/app.py\"\n\n"
  },
  {
    "path": "trainer/examples/basic-compose/requirements.txt",
    "content": "flask>=1.0.0\n"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/Dockerfile",
    "content": "FROM ubuntu:22.04\nRUN apt-get update && apt-get install -y \\\n python3-pip \\\n python3-dev \\\n build-essential\nCOPY requirements.txt /usr/src/app/\nRUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt\nCOPY app.py /usr/src/app/\nEXPOSE 5000\nCMD [\"python3\", \"/usr/src/app/app.py\"]"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/Dockerfile-python",
    "content": "FROM python:3\nCOPY requirements.txt /usr/src/app/\nRUN pip install --no-cache-dir -r /usr/src/app/requirements.txt\nCOPY app.py /usr/src/app/\nEXPOSE 5000\nCMD [\"python\", \"/usr/src/app/app.py\"]"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/Dockerfile-python-alpine",
    "content": "FROM python:3-alpine\nCOPY requirements.txt /usr/src/app/\nRUN pip install --no-cache-dir -r /usr/src/app/requirements.txt\nCOPY app.py /usr/src/app/\nEXPOSE 5000\nCMD [\"python\", \"/usr/src/app/app.py\"]"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/README.md",
    "content": "# Building your flask application with three different from OS's\n\nBuilding our flask application with Ubuntu might seem like a bad idea, as Ubuntu is a general purpose OS.\n\nLooking into dockerhub we see that python already have an image we can use.\n\nBut after building that image, we see that the size of the image is almost doubled.\n\nWe go back again and find `python:3-alpine` based on the alpine OS.\n\nThis gives us a 75% reduction from Ubuntu, and ~90% reduction from normal python3 image.\n\n```bash\npraqmasofus/flaskapp   python3-alpine      f442f4941c53        9 minutes ago       109MB\npraqmasofus/flaskapp   python3             fb4d00f89ef3        10 minutes ago      926MB\npraqmasofus/flaskapp   ubuntu              817bf9d87465        8 days ago          435MB\n```\n"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/app.py",
    "content": "import socket\nfrom flask import Flask\napp = Flask(__name__)\n\n@app.route('/')\ndef hello_world():\n    return \"Hello! I am a Flask application running on {}\".format(socket.gethostname())\n\n\nif __name__ == '__main__':\n    # Note the extra host argument. If we didn't have it, our Flask app\n    # would only respond to requests from inside our container\n    app.run(host='0.0.0.0')\n"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/build.sh",
    "content": "# /bin/bash\ndocker build -t flask-app .\ndocker build -f Dockerfile-python -t flask-app:python .\ndocker build -f Dockerfile-python-alpine -t flask-app:alpine .\n"
  },
  {
    "path": "trainer/examples/building-flask-on-different-os/requirements.txt",
    "content": "Flask==2.3.2\n"
  },
  {
    "path": "trainer/examples/ci-tools/README.md",
    "content": "\n# Docker CI tools\nNote, you need to have run the \"building flask on different OS\" example first.\n\n## Hadolint\nhttps://github.com/hadolint/hadolint\n\n```\ndocker run --rm -i hadolint/hadolint < Dockerfile\ndocker run --rm -i hadolint/hadolint < Dockerfile-python\ndocker run --rm -i hadolint/hadolint < Dockerfile-python-alpine\n```\n\n## Trivy\nSee the different severities you get by the different OS.\n\n```bash\ndocker run -v /var/run/docker.sock:/var/run/docker.sock -v aquacache:/root/.cache aquasec/trivy image mypy:latest\ndocker run -v aquacache:/root/.cache aquasec/trivy image mypy:python\ndocker run -v aquacache:/root/.cache aquasec/trivy image mypy:python-alpine\n\n```"
  },
  {
    "path": "trainer/examples/multi-stage-golang/Dockerfile",
    "content": "FROM golang:1.13.2 AS builder\r\nWORKDIR /go\r\nCOPY hello.go /go\r\nRUN go build hello.go\r\n\r\nFROM scratch\r\nCOPY --from=builder /go/hello /hello\r\nCMD [\"/hello\"]\r\n"
  },
  {
    "path": "trainer/examples/multi-stage-golang/README.md",
    "content": "# Multistage build with go app\r\n\r\nBuilding a go application with the `golang` container,\r\nand copying it into `scratch`.\r\n\r\n## Build\r\n\r\n```shell\r\n$ docker build -t golang-scratch .\r\n...\r\nSuccessfully tagged golang-scratch:latest\r\n```\r\n\r\n## Run the image\r\n\r\n```shell\r\n$ docker run golang-scratch\r\nhello world\r\n```\r\n\r\n## Show the image size\r\n\r\n```shell\r\n$ docker image ls | grep golang\r\ngolang-scracth    latest              9330bf30dfe5        2 minutes ago       2.01MB\r\ngolang            1.13.2-buster       e37698ff7351        2 days ago          803MB\r\n```\r\n"
  },
  {
    "path": "trainer/examples/multi-stage-golang/hello.go",
    "content": "package main\r\n\r\nimport \"fmt\"\r\n\r\nfunc main() {\r\n    fmt.Println(\"hello world\")\r\n}\r\n"
  },
  {
    "path": "trainer/examples/multistage-java/Dockerfile",
    "content": "FROM gradle:jdk11\nLABEL author=\"Sofus Albertsen\"\nCOPY --chown=gradle:gradle . /home/gradle/src\nWORKDIR /home/gradle/src\nRUN gradle shadowjar\nRUN cp /home/gradle/src/build/libs/app-*-all.jar app.jar\nEXPOSE 8000\nCMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar app.jar\n"
  },
  {
    "path": "trainer/examples/multistage-java/Dockerfile-multistage",
    "content": "FROM gradle:jdk11 AS builder\nCOPY --chown=gradle:gradle . /home/gradle/src\nWORKDIR /home/gradle/src\nRUN gradle shadowjar\n\nFROM adoptopenjdk/openjdk11-openj9:alpine-slim\nLABEL author=\"Sofus Albertsen\"\nCOPY --from=builder /home/gradle/src/build/libs/app-*-all.jar app.jar\nEXPOSE 8000\nCMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar app.jar\n"
  },
  {
    "path": "trainer/examples/multistage-java/README.md",
    "content": "# Multistage build with java app\n\nBuilding a java application with the `gradle` container,\nand copying it into `java`.\n\n## Build\n\n```shell\n$ docker build -t java-app:single .\n...\nSuccessfully tagged java-app:single\n```\n\nAnd build the multi stage dockerfile as well:\n\n```shell\n$ docker build -t java-app:multi -f Dockerfile-multistage .\n...\nSuccessfully tagged java-app:multi\n```\n\n## Show the image size\n\n```shell\n$ docker image ls\nREPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE\njava-app                                        multi               d2f488502c4d        22 seconds ago      259MB\njava-app                                        single              0e8df415465d        3 minutes ago       701MB\n\n```\n"
  },
  {
    "path": "trainer/examples/multistage-java/build.gradle",
    "content": "plugins {\n    id \"net.ltgt.apt-eclipse\" version \"0.21\"\n    id \"com.github.johnrengelman.shadow\" version \"5.0.0\"\n    id \"application\"\n}\n\n\n\nversion \"0.1\"\ngroup \"example.micronaut\"\n\nrepositories {\n    mavenCentral()\n    maven { url \"https://jcenter.bintray.com\" }\n}\n\nconfigurations {\n    // for dependencies that are needed for development only\n    developmentOnly \n}\n\ndependencies {\n    annotationProcessor platform(\"io.micronaut:micronaut-bom:$micronautVersion\")\n    annotationProcessor \"io.micronaut:micronaut-inject-java\"\n    annotationProcessor \"io.micronaut:micronaut-validation\"\n    implementation platform(\"io.micronaut:micronaut-bom:$micronautVersion\")\n    implementation \"io.micronaut:micronaut-inject\"\n    implementation \"io.micronaut:micronaut-validation\"\n    implementation \"io.micronaut:micronaut-runtime\"\n    implementation \"javax.annotation:javax.annotation-api\"\n    implementation \"io.micronaut:micronaut-http-server-netty\"\n    implementation \"io.micronaut:micronaut-http-client\"\n    runtimeOnly \"ch.qos.logback:logback-classic:1.2.3\"\n    testAnnotationProcessor platform(\"io.micronaut:micronaut-bom:$micronautVersion\")\n    testAnnotationProcessor \"io.micronaut:micronaut-inject-java\"\n    testImplementation platform(\"io.micronaut:micronaut-bom:$micronautVersion\")\n    testImplementation \"org.junit.jupiter:junit-jupiter-api\"\n    testImplementation \"io.micronaut.test:micronaut-test-junit5\"\n    testRuntimeOnly \"org.junit.jupiter:junit-jupiter-engine\"\n}\n\ntest.classpath += configurations.developmentOnly\n\nmainClassName = \"example.micronaut.Application\"\n// use JUnit 5 platform\ntest {\n    useJUnitPlatform()\n}\ntasks.withType(JavaCompile){\n    options.encoding = \"UTF-8\"\n    options.compilerArgs.add('-parameters')\n}\n\nshadowJar {\n    mergeServiceFiles()\n}\n\nrun.classpath += configurations.developmentOnly\nrun.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote')\n"
  },
  {
    "path": "trainer/examples/multistage-java/gradle.properties",
    "content": "micronautVersion=1.2.7"
  },
  {
    "path": "trainer/examples/multistage-java/micronaut-cli.yml",
    "content": "profile: service\ndefaultPackage: example.micronaut\n---\ntestFramework: junit\nsourceLanguage: java"
  },
  {
    "path": "trainer/examples/multistage-java/settings.gradle",
    "content": "rootProject.name=\"app\""
  },
  {
    "path": "trainer/examples/multistage-java/src/main/java/example/micronaut/Application.java",
    "content": "package example.micronaut;\n\nimport io.micronaut.runtime.Micronaut;\n\npublic class Application {\n\n    public static void main(String[] args) {\n        Micronaut.run(Application.class);\n    }\n}"
  },
  {
    "path": "trainer/examples/multistage-java/src/main/java/example/micronaut/HelloController.java",
    "content": "package example.micronaut;\n\nimport io.micronaut.http.MediaType;\nimport io.micronaut.http.annotation.Controller;\nimport io.micronaut.http.annotation.Get;\nimport io.micronaut.http.annotation.Produces;\n\n@Controller(\"/hello\")\npublic class HelloController{\n    @Get(\"/{name}\") \n    @Produces(MediaType.TEXT_PLAIN) \n    public String index(String name) {\n        return combineName(name); \n    }\n    \n    //Example of a function that can be tested through normal unit frameworks\n    public String combineName( String name) {\n        return \"Hello \"+name;\n    }\n}\n"
  },
  {
    "path": "trainer/examples/multistage-java/src/main/java/example/micronaut/ReadyController.java",
    "content": "package example.micronaut;\n\nimport io.micronaut.http.MediaType;\nimport io.micronaut.http.annotation.Controller;\nimport io.micronaut.http.annotation.Get;\nimport io.micronaut.http.annotation.Produces;\n\n@Controller(\"/status\")\npublic class ReadyController{\n    @Get(\"/\") \n    @Produces(MediaType.TEXT_PLAIN) \n    public String index() {\n        return \"Up and running\"; \n    }\n}\n"
  },
  {
    "path": "trainer/examples/multistage-java/src/main/java/example/micronaut/RootController.java",
    "content": "package example.micronaut;\n\nimport io.micronaut.http.MediaType;\nimport io.micronaut.http.annotation.Controller;\nimport io.micronaut.http.annotation.Get;\nimport io.micronaut.http.annotation.Produces;\n\n@Controller(\"/\")\npublic class RootController{\n    @Get(\"/\") \n    @Produces(MediaType.TEXT_HTML) \n    public String index() {\n        return \"<h1> Hello World</h1>\"; \n    }\n    \n}\n"
  },
  {
    "path": "trainer/examples/multistage-java/src/main/resources/application.yml",
    "content": "micronaut:\n  server:\n    port: 8000\n  application:\n    name: app\n"
  },
  {
    "path": "trainer/examples/multistage-java/src/main/resources/logback.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <withJansi>true</withJansi>\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"STDOUT\" />\n    </root>\n</configuration>\n"
  },
  {
    "path": "trainer/examples/multistage-java/src/test/java/example/micronaut/HelloControllerTest.java",
    "content": "package example.micronaut;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport io.micronaut.http.HttpRequest;\nimport io.micronaut.http.client.RxHttpClient;\nimport io.micronaut.http.client.annotation.Client;\nimport io.micronaut.test.annotation.MicronautTest;\nimport org.junit.jupiter.api.Test;\n\nimport javax.inject.Inject;\n\n@MicronautTest \npublic class HelloControllerTest {\n\n    @Inject\n    @Client(\"/\")\n    RxHttpClient client; \n    //Test spins up an entire server and client to perform the test\n    \n    @Test\n    public void testHello() {\n        HttpRequest<String> request = HttpRequest.GET(\"/hello/sofus\"); \n        String body = client.toBlocking().retrieve(request);\n\n        assertNotNull(body);\n        assertEquals(\"Hello sofus\", body);\n    }\n    @Test\n    public void testCombineName() {\n        String name = \"Sonny\";\n        HelloController sut = new HelloController();\n        System.out.println(\"testing\");\n        assertEquals(\"Hello \"+name, sut.combineName(name),\"Name and greeting not properly combined\");\n        \n        \n    }\n\n}"
  },
  {
    "path": "trainer/examples/nextcloud/README.md",
    "content": "# Nextcloud showcase\n\nAn example of starting a two-container application using docker compose;  Nextcloud with a MariaDB backend.\n\nWe show that the volumes persist killing the containers,\nand a version-upgrade of Nextcloud.\n\nTags: docker-compose, volumes, upgrading\n\n## Part 1: Persistent storage\n\n1. Start the application with:\n\n    ```shell\n    docker compose up\n    ```\n\n1. Visit Nextcloud in a browser on: [localhost](http://localhost),\nand create an admin account.\n\n> NB: It takes ~1 min for `mariadb` to become ready.\n> (During a demo, you'll usually talk for this minute about the demo..)\n> If you get the error:\n>\n> ```output\n> Fejl:\n> Error while trying to create admin user: Failed to connect to the database: An exception occured in driver: SQLSTATE[HY000] [2002]Connection refused\n> ```\n>\n> Wait a little while, fill in the password again, and try to finish the setup again.\n\n## Upload a file\n\n1. Drag a picture or other file from your computer into the UI,\nto upload it to your Nextcloud.\n\n1. Stop the services afterwards.\n    > If you ran `docker compose` without `-d`,\n    > then `ctrl+c` will stop the services.\n\n    ```shell\n    docker compose down\n    ```\n\n1. Reload the window in the browser; Nextcloud is down.\n1. Show that no containers are running with the command:\n\n    ```shell\n    docker ps\n    ```\n\n## Removing the containers (optional)\n\n1. You can even remove the containers:\n\n    ```shell\n    docker compose rm\n    ```\n\n1. Show that they're gone with `docker ps -a`.\n\n## Restart Nextcloud\n\n1. Start the application again with,\n\n    ```shell\n    docker compose up\n    ```\n\n1. Reload your browser window,\nNextcloud should be available again;\nyou might even still be logged in.\n\n## Persistent storage\n\n1. Notice how the file you uploaded before is still available.\n\n## Part 2: Upgrading Nextcloud\n\n1. Stop the services again, using `docker compose down` or `ctrl+c`.\n1. Change the version of the Nextcloud image from `nextcloud:X`\n    to `nextcloud:X+1` (swap the commented image-version lines,\n    in the `docker-compose.yml`.)\n\n## Start the Application\n\n1. Run `docker compose up` to start the application.\n\nYou can find the version under the `gear icon -> help`,\n[link](http://localhost/settings/help).\n\n## Downgrading Nextcloud\n\n> Downgrading is not supported in Nextcloud.\n\n1. Try stopping the containers again\nwith `docker compose down` or `ctrl+c`.\n1. Changing the version back to `nextcloud:12`.\n1. Starting the application again with `docker compose up`.\n\nNextcloud will fail to start with the message:\n\n```shell\napp_1  | Can't start Nextcloud because the version of the data (12.0.13.2) is higher than the docker image version (11.0.8.1) and downgrading is not supported. Are you sure you have pulled the newest image version?\n```\n\n> NB: This composefile is missing a network directive, but that is ok.\n> Docker will create a network for these containers\n> and connect them on that network.\n\n## Possible issues\n\n### Making Nextcloud fail without `depends_on`\n\nIn the original example,\nthe upgrade would fail without adding the `depends_on: db`\nto the docker compose file.\nNicolaj has been unable to reproduce this error on `Docker for Windows 19.03`, and thus the \"example of using `depends_on`\" is left out of the tutorial.\n`depends_on` is however kept in the docker compose file,\nso as to not cause any unintentional errors.\n"
  },
  {
    "path": "trainer/examples/nextcloud/docker-compose.yml",
    "content": "volumes:\n  nextcloud:\n  db:\nservices:\n  app:\n    image: nextcloud:23\n    # image: nextcloud:24\n    depends_on:\n      - db\n    environment:\n      MYSQL_PASSWORD: nextcloud\n      MYSQL_DATABASE: nextcloud\n      MYSQL_USER: root\n      MYSQL_HOST: db\n    ports:\n    - 80:80\n    volumes:\n    - nextcloud:/var/www/html:rw\n  db:\n    image: mariadb:10.4.12-bionic\n    environment:\n      MYSQL_ROOT_PASSWORD: nextcloud\n    volumes:\n    - db:/var/lib/mysql:rw\n\n"
  },
  {
    "path": "trainer/examples/scratch/Dockerfile",
    "content": "FROM scratch\nCOPY emptyfile ."
  },
  {
    "path": "trainer/examples/scratch/README.md",
    "content": "# Empty image (useles, but really empty)\n\nIn this example we show that Scratch is in fact empty.\n\nIt is not possible to `docker pull scratch` as the image doesn't actualy exist.\n\nNeither is it possible ot just do a `FROM scratch` Dockerfile.\n\nBut we can do the following:\n\n```\nFROM scratch\nCOPY emptyfile .\n```\n\nand then `docker build -t empty:latest .`\n\nWe can now use `docker image ls` to see that the created image is empty.\n"
  },
  {
    "path": "trainer/examples/scratch/emptyfile",
    "content": ""
  },
  {
    "path": "trainer/examples/security-run-as-root/README.md",
    "content": "# Running as Root in a Container\n\nThe purpose of this example is to show,\nif you mount the filesystem into a container running as root,\nthe container will have root privileges on the filesystem.\n\nThis example runs well in a Google Cloud Shell.\n\n## As a basic user\n\n1. show the current user\n\n    ```shell\n    $ id\n    uid=1000(ng) gid=1000(ng) groups=1000(ng),4(adm),27(sudo),999(docker)\n    ```\n\n1. create a file and show the owner/permissions\n\n    ```shell\n    $ touch file1.txt\n    $ ls -la\n    total 16\n    drwxr-xr-x 2 ng   ng   4096 Sep 23 15:50 .\n    drwxr-xr-x 7 ng   ng   4096 Sep 23 15:40 ..\n    -rw-r--r-- 1 ng   ng      0 Sep 23 15:50 file1.txt\n    ```\n\n> `file1.txt` is has\n>\n> - `user:group` set to your user e.g. `ng:ng`\n> - permissions `-rw-r--r--`, where the first\n>   - `rw-` is `read, write` for `user`, the next\n>   - `r--` is `read` for `group`, and the last\n>   - `r--` is `read` for `other`. So,\n> - ng (`user`) can `read` and `write` to the file,\n> - users in the ng (`group`) can `read`, and\n> - everyone on the linux system (`other`) can `read` the file.\n\n## In the same folder\n\n1. start a privileged container that mounts in the current folder\n\n    ```shell\n    $ docker run --rm -it -v $(pwd):/mnt/host alpine\n    / #\n    ```\n\n1. show the current user (we have `uid=0` and are `root`)\n\n    ```shell\n    # id\n    uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\n    ```\n\n1. show the files and permissions in the `/mnt/host` folder\n    (we see the file we created before)\n\n    ```shell\n    / # cd /mnt/host/\n    /mnt/host # ls -la\n    total 16\n    drwxr-xr-x    2 1000     1000          4096 Sep 23 13:53 .\n    drwxr-xr-x    1 root     root          4096 Sep 23 13:42 ..\n    -rw-r--r--    1 1000     1000             0 Sep 23 13:50 file1.txt\n    ```\n\n1. create a file with some secret content and show the permissions\n\n    ```shell\n    /mnt/host # echo \"secret\" > file2.txt\n    /mnt/host # ls -la\n    total 12\n    drwxr-xr-x    2 1000     1000          4096 Sep 23 14:18 .\n    drwxr-xr-x    1 root     root          4096 Sep 23 13:42 ..\n    -rw-r--r--    1 1000     1000             0 Sep 23 13:50 file1.txt\n    -rw-r--r--    1 root     root             7 Sep 23 14:18 file2.txt\n    ```\n\n1. delete the `read` permission for `other` on the file\n\n    ```shell\n    /mnt/host # chmod o-r file2.txt\n    /mnt/host # ls -la\n    total 12\n    drwxr-xr-x    2 1000     1000          4096 Sep 23 14:18 .\n    drwxr-xr-x    1 root     root          4096 Sep 23 13:42 ..\n    -rw-r--r--    1 1000     1000             0 Sep 23 13:50 file1.txt\n    -rw-r-----    1 root     root             7 Sep 23 14:18 file2.txt\n    /mnt/host #\n    ```\n\n1. exit the privileged container, and try viewing the permissions on the files\n    (`file2.txt` is owned by `root:root`)\n\n    ```shell\n    /mnt/host # exit\n    $ ls -la\n    total 12\n    drwxr-xr-x 2 ng   ng   4096 Sep 23 16:21 .\n    drwxr-xr-x 7 ng   ng   4096 Sep 23 15:40 ..\n    -rw-r--r-- 1 ng   ng      0 Sep 23 15:50 file1.txt\n    -rw-r----- 1 root root    7 Sep 23 16:18 file2.txt\n    ```\n\n1. try viewing the contents of the file\n    (we can't since `other` doesn't have any permissons on the file)\n\n   ```shell\n    $ cat file2.txt\n    cat: file2.txt: Permission denied\n    ```\n\n1. Use the root user to view the contents of the file\n\n    ```shell\n    $ sudo cat file2.txt\n    secret\n    ```\n\n## Conclusion\n\nUse great caution when mounting the filesystem into a container,\nand pay attention to the privileges that a container is running with.\n\nGreatly inspired by the\n[Keynote: Running with Scissors - Liz Rice, Technology Evangelist, Aqua Security](https://www.youtube.com/watch?v=ltrV-Qmh3oY) at KubeCon 2018\n"
  },
  {
    "path": "trainer/examples/volume-python/README.md",
    "content": "The idea for this one is to make a small python application that prints \"Hello world!\" to your terminal, and volume mount in the code from the host.\n\nIt is a pre-example to building your own image.\n\n## Exercise\nYou need a terminal in this folder.\n\nRun:\n\n`docker run -v \"$PWD\":/usr/src/myapp -w /usr/src/myapp python:3 python main.py`"
  },
  {
    "path": "trainer/examples/volume-python/main.py",
    "content": "import os\nimport socket\n\ndef get_system_info():\n    # Get the host name\n    host_name = socket.gethostname()\n    \n    # Get the OS name\n    os_name = os.name\n    if os_name == 'posix':\n        os_name = os.uname().sysname\n    elif os_name == 'nt':\n        os_name = 'Windows'\n    \n    return host_name, os_name\n\nif __name__ == \"__main__\":\n    host_name, os_name = get_system_info()\n    print(f\"Host Name: {host_name}\")\n    print(f\"OS Name: {os_name}\")"
  },
  {
    "path": "trainer/experiments/unicode/README.md",
    "content": "# Testing unicode support\n\n```\ndocker build -f Файлдокера -t unicode:latest .\ndocker run -it unicode:latest\n```\n\nSadly, Docker doesn't support unicode in image names and tags. 😢"
  },
  {
    "path": "trainer/experiments/unicode/Файлдокера",
    "content": "FROM alpine:latest\nMAINTAINER \"ℑⒶ𝕹 𝕂®𝓪ⓖ 😋\"\nCOPY ఖాళీగా .\nRUN touch فارغة\nRUN echo \"नमस्ते विश्व यो डेमो नमूना हो\"\nCMD [\"echo\",\"😱😅👾❗️\"]"
  },
  {
    "path": "trainer/experiments/unicode/ఖాళీగా",
    "content": ""
  }
]