├── 1_Create_view_pull_images ├── Dockerfile └── README.md ├── 2_Create_view_stop_containers └── README.md ├── 3_Working_inside_containers ├── README.md └── todo.sql ├── 4_Working_with_persistence └── README.md ├── 5_Working_with_container_registry └── README.md ├── Containers 101.pdf └── README.md /1_Create_view_pull_images/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | LABEL maintainer="Mofe Salami (tsalami@uk.ibm.com)" 3 | RUN apt-get update 4 | RUN apt-get install -y nginx 5 | CMD ["nginx", "-g", "daemon off;"] 6 | EXPOSE 80 7 | -------------------------------------------------------------------------------- /1_Create_view_pull_images/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 1: Creating, pulling and running Docker images 2 | 3 | In this exercise, you will create your first Docker image and inspect the history of image to see the layers which create the image. Finally, you will learn how to pull other existing Docker images from DockerHub for usage within your local Docker environment. 4 | 5 | ## Create a Dockerfile 6 | 7 | Create a file called `Dockerfile` with the following contents: 8 | 9 | ``` 10 | FROM ubuntu 11 | LABEL maintainer="Bob Smith (Bob.Smith@gmail.com)" 12 | RUN apt-get update 13 | RUN apt-get install -y nginx 14 | CMD ["nginx", "-g", "daemon off;"] 15 | EXPOSE 80 16 | ``` 17 | 18 | Our starting point is from an ubuntu image. From there we add several layers to install and configure the latest version of nginx with `RUN`. `CMD` allows us to specify the default command when the container is started. In this case, this will run the nginx executable. Finally, we specify that we want to `EXPOSE` port 80 so that our nginx application can be accessed outside the container. Remember to change the `LABEL` line to use your own name and Email. 19 | 20 | ## Build your image 21 | 22 | To create an image from your Dockerfile first open up the appropriate application. If running Docker-CE for Windows/Mac, ensure that Docker is running in your task bar and open up the `Terminal` application if on Mac or the `Command Prompt` application on Windows. Otherwise, if running Docker Toolbox on Windows, open up the `Docker QuickStart shell`. You will be using this shell environment for the duration of the workshop. To create the image, run: 23 | 24 | `docker build -t mynginx:latest .` 25 | 26 | The `-t` flag allows us to specify the image name followed by the image tag. By default, the image tag is always 'latest'. 27 | *Note: if you are not in your same directory as your Dockerfile make sure to replace the `.` with the path to the directory containing the Dockerfile.* 28 | 29 | ## Inspect image history 30 | 31 | To see the layers within the image you have just created run the following command: 32 | 33 | `docker history mynginx` 34 | 35 | Notice that the layers which are from the ubuntu image will have a time stamp longer than the layers explicitly defined in your Dockerfile. 36 | 37 | ## View your local images 38 | 39 | To see the images you have installed on your machine run the following command: 40 | 41 | `docker images` 42 | 43 | ## Pulling images 44 | 45 | You now know how to create a Docker image using a Dockerfile, but with exception of custom built software, you usually want to run images that you don't maintain. Docker Hub is a registry that provides a marketplace for Docker images for all to use. The [official images](https://hub.docker.com/explore/) for products in their Docker-ized format will be built by the maintainers and published here. To pull the official ubuntu image from DockerHub, run the following command: 46 | 47 | `docker pull ubuntu` 48 | 49 | As our custom built image for nginx was running on ubuntu, the docker engine will not actually pull any layers from DockerHub but will use the same layer it pulled when running the `mynginx` image. If you want to be double sure, you can run `docker history ubuntu` and you should see all the same commands and layer sizes as the base layers for `mynginx`. 50 | 51 | Congratulations, you now know how to create a Docker image from a Dockerfile, view the history of a Docker image and how to pull images from the Docker registry! Continue on to the [next exercise](../2_Create_view_stop_containers) to learn how to start working with containers. 52 | -------------------------------------------------------------------------------- /2_Create_view_stop_containers/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2: Creating, viewing and stopping containers 2 | 3 | ## Create a container 4 | 5 | Up until this point, you have only created a Docker image. You haven't actually run the desired application in its container form. To create a container from the custom built nginx image we made in [workshop 1](../1_Create_view_pull_images), run the following command: 6 | 7 | `docker run -d -p 8081:80 --name webserver mynginx` 8 | 9 | The `-d` flag tells Docker to run the container in the background and print the container Id. `-p` flag tells Docker that we will be exposing ports. The arguments supplied to `-p` tells Docker that we want to map the host port 8081 to the container port 80. If you wanted to use another port say if 8081 was being used by abother application, you would simply change the host port. (NOTE: We are only able to expose port 80 from within the container because we declared this behaviour in our Dockerfile with the command `EXPOSE 80`). The `--name` flag allows us to assign a name to our container as opposed to the random name and Id docker generates and we finally end with the image name to create the container from `mynginx`. 10 | 11 | The running nginx application will now be available on your local machine. Open a browser and navigate to: 12 | 13 | `http://localhost:8081` 14 | 15 | ## View containers 16 | 17 | Although we know that our webserver container should now be running, it's always nice to see what state our containers are in especially when we have multiple containers running over extended periods of time. To see the state of all your containers, run the following command: 18 | 19 | `docker ps` 20 | 21 | This command shows you all running containers running on your machine. The container Id shows the truncated to 12 character form of the unique Id associated with the container. You can refer to your container either using this Id or the name which you gave it at runtime. It is easier to refer to a name than a random string so its always a good idea to name your containers! This command also shows you the image the container was created from, the command run when creating the container, the time of creation, the ports mappings and finally the status of the container. 22 | 23 | ## Stop containers 24 | 25 | To safely stop our custom nginx container, you can run the following command: 26 | 27 | `docker stop webserver` 28 | 29 | If you run the `docker ps` command, you will notice that we no longer see any information about our container. Remember that the default behaviour for this command is to show *running* containers (NOTE: You could also use the unique Id as a reference to the container). To see all containers on your machine run: 30 | 31 | `docker ps -a` 32 | 33 | You should now see that the container is in the view but is now in the `Exited` state. 34 | 35 | ## Start containers 36 | 37 | To start our nginx container again, you can run the following command: 38 | 39 | `docker start webserver` 40 | 41 | As before the application will be available at `localhost:8081`. 42 | 43 | Congratulations, you now know how to create a container from a Docker image, start and stop containers and to monitor the state of all your containers! Continue on to the [next exercise](../3_Working_inside_containers) to learn how to start working inside containers. 44 | -------------------------------------------------------------------------------- /3_Working_inside_containers/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3: Working inside containers 2 | 3 | Now that you know how to run your own containers, we can start playing aroud with some real world applications and do some meaningful work. Let's say we wanted to run a simple TODO list application which runs on a MySQL backend. Let's start of by pulling the mysql image from DockerHub: 4 | 5 | `docker pull mysql/mysql-server` 6 | 7 | Depending on the internet speed, it should take no more than a couple of mintues to pull the image (300MB). When you have the image, run it with the following command: 8 | 9 | `docker run --name=mysql -e MYSQL_ROOT_PASSWORD=password -d mysql/mysql-server:latest` 10 | 11 | As before, `-d` tells Docker to run the container in the background in detached mode and the `--name` flag allows us to label our container with a name we can use to reference it rather than using the random container Id. Finally we set the environment variable `MYSQL_ROOT_PASSWORD` to initialise the database with. (NOTE: You definately shouldn't pass password as env variables but this is just a workshop so we'll turn a blind eye to this). 12 | 13 | ## View container logs 14 | 15 | As we are working with a stateful application, there is a slightly longer start up time for all the various start up processes. We can check the container logs to see if MySQL is ready: 16 | 17 | `docker logs mysql` 18 | 19 | The docker logs command is generally useful for debugging containers without the need to enter the containers to access their logs. If your container fails to start or becomes unhealthy, this is usually the first command you should run to see what is going on (NOTE: If the last few lines contains the output 'ready for connections' your database will have been succefully initialised!). 20 | 21 | ## Copy files into a container 22 | 23 | So we have successfully created a MySQL database but we now want to get some data inside it. Within this workshop there is a sample schema that will create our todo list application's database. Make sure you have a local copy of the `todo.sql` schema file (If you have cloned the repository, just change into the directory for this lab). Copy the sql file into the container with the following command: 24 | 25 | `docker cp ./todo.sql mysql:/tmp` 26 | 27 | or 28 | 29 | `docker cp C:\Users\...\todo.sql mysql:/tmp` if you're on Windows. (You will need to supply the full path to `todo.sql`) 30 | 31 | This will copy the SQL file to the /tmp directory within the container. 32 | 33 | ## Make changes in a container 34 | 35 | Now we can go back into the container: 36 | 37 | `docker exec -it mysql bash` 38 | 39 | Now that we are inside the container, we can load the schema into mysql: 40 | 41 | `mysql -u root -p < /tmp/todo.sql` 42 | 43 | You will be prompted for the password again. Now you will have loaded the schema and sample data into the database. To see the data we can go back into mysql: 44 | 45 | `mysql -u root -p` 46 | 47 | To see the databases we have available, run the command: 48 | 49 | `show databases;` 50 | 51 | Now we can specify that we want to use the `todo` database: 52 | 53 | `use todo` 54 | 55 | List the tables that are available with the following command: 56 | 57 | `show tables;` 58 | 59 | Let's see what we have in the `list` table: 60 | 61 | `select * from list;` 62 | 63 | Let's see what list items we have in the `work` list: 64 | 65 | `select * from list_items where list_id=2;` 66 | 67 | Since you now know how to create a Docker image from a Dockerfile and you know how to run a Docker container, lets update the `complete` status for items 8 and 9 in the list: 68 | 69 | `update list_items set complete='Y' where id=8 or id=9;` 70 | 71 | If we now look at the state of the list_items in the `work` list we can see that list_items 8 and 9 are now marked as complete. To exit MySQL and the container, type the command `exit` twice (The first time should responde with "Bye" and the second with "exit"). 72 | 73 | Congratulations! You now know how to start an interactive shell within a running container, copy data inside a container and make changes inside a container. Continue on to the [next exercise](../4_Working_with_persistence) to learn how to persist data with containers. 74 | -------------------------------------------------------------------------------- /3_Working_inside_containers/todo.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS todo; 2 | CREATE DATABASE IF NOT EXISTS todo; 3 | 4 | USE todo; 5 | 6 | DROP TABLE IF EXISTS list, list_items; 7 | 8 | CREATE TABLE list ( 9 | id INT NOT NULL AUTO_INCREMENT, 10 | list_name VARCHAR(20) NOT NULL, 11 | description VARCHAR(100) NOT NULL, 12 | PRIMARY KEY (id) 13 | ); 14 | 15 | CREATE TABLE list_items ( 16 | id INT NOT NULL AUTO_INCREMENT, 17 | list_id INT NOT NULL, 18 | task VARCHAR(200) NOT NULL, 19 | complete ENUM ('Y','N') NOT NULL DEFAULT 'N', 20 | FOREIGN KEY (list_id) REFERENCES list (id) ON DELETE CASCADE, 21 | PRIMARY KEY (id) 22 | ); 23 | 24 | 25 | -- Creating sample data to populate our todo backend 26 | INSERT INTO list VALUES(NULL, 'Shopping', 'A list of groceries to get when we next go shopping'); 27 | INSERT INTO list VALUES(NULL, 'Work', 'A list of all the things I need to do at work this week'); 28 | INSERT INTO list VALUES(NULL, 'Movies', 'A list of all the movies I must watch this year!'); 29 | INSERT INTO list VALUES(NULL, 'Restaurants', 'A list of the restaurants that have been recommended for me to try'); 30 | 31 | 32 | INSERT INTO list_items (list_id, task) VALUES(1, 'Eggs'); 33 | INSERT INTO list_items (list_id, task) VALUES(1, 'Milk'); 34 | INSERT INTO list_items (list_id, task) VALUES(1, 'Ham'); 35 | INSERT INTO list_items (list_id, task) VALUES(1, 'Cheese'); 36 | INSERT INTO list_items (list_id, task) VALUES(1, 'Coffee'); 37 | INSERT INTO list_items (list_id, task) VALUES(1, 'Bread'); 38 | 39 | INSERT INTO list_items (list_id, task) VALUES(2, 'Reply to my managers E-mail'); 40 | INSERT INTO list_items (list_id, task) VALUES(2, 'Learn how to create a Docker image'); 41 | INSERT INTO list_items (list_id, task) VALUES(2, 'Learn how to run Docker containers'); 42 | INSERT INTO list_items (list_id, task) VALUES(2, 'Install security patches'); 43 | 44 | INSERT INTO list_items (list_id, task) VALUES(3, 'Captain America: Civil War'); 45 | INSERT INTO list_items (list_id, task) VALUES(3, 'Black Panther'); 46 | INSERT INTO list_items (list_id, task) VALUES(3, 'Avengers: Infinity War'); 47 | INSERT INTO list_items (list_id, task) VALUES(3, 'Deadpool 2'); 48 | 49 | INSERT INTO list_items (list_id, task) VALUES(4, 'Five Guys'); 50 | INSERT INTO list_items (list_id, task) VALUES(4, 'Wagamama'); 51 | INSERT INTO list_items (list_id, task) VALUES(4, 'The Breakfast Club'); 52 | INSERT INTO list_items (list_id, task) VALUES(4, 'Wahaca'); 53 | INSERT INTO list_items (list_id, task) VALUES(4, 'Bad Egg'); 54 | -------------------------------------------------------------------------------- /4_Working_with_persistence/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 4: Working with persistence and commits 2 | 3 | In the last exercise, we learnt how to update our MySQL database. This is great as we didn't have to worry about setting up MySQL however, if our container were to go down, we would lose all the changes we made. The data lost would have been trivial as we made minor changes but with real applications, this would be unacceptable. Let's list the steps we took to setup our MySQL container: 4 | 5 | 1. Run the container from the image 6 | 2. Copy the schema into /tmp directory in the container 7 | 3. Login to the container 8 | 4. Load the schema from /tmp into MySQL 9 | 5. Login to MySQL 10 | 6. Make our changes 11 | 12 | Looking at the steps in the list, as a developer, we're really only interested in making changes to the database. Steps 2-4 are boring configuration steps that could be automated. Wouldn't it be great if our Docker image allowed us to skip these steps? Well good news... it can! 13 | 14 | ## Commit a new image 15 | 16 | To create a snapshot from our container, we need to get it at a good starting point for re-use. As we have already loaded the schema into MySQL and made are changes, we are all set to commit changes from our running MySQL container: 17 | 18 | `docker commit mysql /mysql-server` 19 | 20 | Make sure you replace `` with a username that identfies you. If we now look through our docker images, we will see we have 2 mysql-server images: 21 | 22 | `docker images | grep mysql-server` 23 | 24 | Now we have an image that will contain a configured MySQL database with the credentials we setup in [exercise 3](../3_Working_inside_containers), our schema in the containers /tmp directory and our schema loaded into MySQL with the updates we made to the `work` list items. Although it is unlikely we want to worry about setting up the database password, it would be nice to allow our database to work with different datasets. If we had several teams using this image, it would be great to use the same image and decouple the data from the application... Good news, we can! 25 | 26 | ## Mount persistent data into a container 27 | 28 | As developers, we want to have the flexibility to use different datasets - not just `todo.sql`. We would also like for the database files to be decoupled from the container. If the container was to die before we could run a commit, we would still lose changes we made after starting the container. There are numerous ways of doing this in Docker but in attempt to simplify the steps for both Windows and Linux/Mac users, we will create local data volumes. 29 | 30 | Create two local data volumes with the following commands: 31 | 32 | ``` 33 | docker volume create --name mysql-data --driver local 34 | docker volume create --name mysql-schema --driver local 35 | ``` 36 | 37 | To see these newly created volumes, we can run the command: 38 | 39 | `docker volume ls` 40 | 41 | Now we can run our custom MySQL container referencing these two data volumes: 42 | 43 | ``` 44 | docker run --name=mysql-persist -d \ 45 | -v mysql-data:/var/lib/mysql \ 46 | -v mysql-schema:/tmp/schema \ 47 | /mysql-server 48 | ``` 49 | 50 | We have now created a new container from our custom image of MySQL. This container is called `mysql-persist` and mounts the `mysql-data` volume on the host to the data directory for MySQL within the container. We also have an additional mount point at `mysql-schema` to load in any other schema files to populate the database with. 51 | 52 | ## Re-apply the database changes 53 | 54 | Let's see if we have our database changes in this new image: 55 | 56 | `docker exec -it mysql-persist mysql -u root -p` 57 | 58 | As we committed the MySQL image after we had initialised the database with the environment variable, the password should still be `password`. 59 | 60 | `show databases;` 61 | 62 | Uh-oh! The todo database isn't there! 63 | 64 | As containers are ephemeral, we will not have the data in the data directory saved from the first container as this is exposed as a volume by default in Docker and is decoupled from the container (See the [Dockerfile](https://github.com/docker-library/mysql/blob/fc3e856313423dc2d6a8d74cfd6b678582090fc7/8.0/Dockerfile#L65)). We will need to run the schema as before: 65 | 66 | `exit` 67 | 68 | `docker exec -it mysql-persist bash` 69 | 70 | `mysql -u root -p < /tmp/todo.sql` 71 | 72 | As we committed the MySQL image after we had initialised the database with the environment variable, the password should still be `password`. Now we can log into the MySQL instance: 73 | 74 | `mysql -u root -p` 75 | 76 | We then tell MySQL to use the todo database as before: 77 | 78 | `use todo;` 79 | 80 | We can check the state of our work list items which had an id of 2 in the list table: 81 | 82 | `select * from list_items where list_id=2;` 83 | 84 | Let's update the same items again: 85 | 86 | `update list_items set complete='Y' where id=8 or id=9;` 87 | 88 | We can now exit out of the MySQL and the container by running the command `exit` twice. It may seem like we haven't done anything however, the changes we made to our database now exist outside our container at `/tmp/mysql`. If we start another container using the same mount point, we will be able to see the data in the database with our latest changes. 89 | 90 | ## Test the persistence change 91 | 92 | Let's stop and remove all containers so we can be confident that we can start from a clean Docker environment and still use our persisted data in our local data volumes: 93 | 94 | `docker stop $(docker ps -aq)` 95 | 96 | This command stops all containers on the Docker host. The nested Docker command lists all the container on the host by Id. We can then remove all the containers: 97 | 98 | `docker rm $(docker ps -aq)` 99 | 100 | Now we can run the same command as before to run the containers along with the volumes: 101 | 102 | ``` 103 | docker run --name=mysql-persist -d \ 104 | -v mysql-data:/var/lib/mysql \ 105 | -v mysql-schema:/tmp/schema \ 106 | /mysql-server 107 | ``` 108 | 109 | NOTE: As before, remember to replace the username `` with the username you tagged the image with. We can then login to MySQL: 110 | 111 | `docker exec -it mysql-persist mysql -u root -p` 112 | 113 | As before, we supply the password `password`. Let's list the databases to see if our todo database is present: 114 | 115 | `show databases;` 116 | 117 | With some luck, todo should be there! We didn't have to run our schema. Let's now verify that our changes to the database have persisted: 118 | 119 | `use todo;` 120 | 121 | `select * from list_items where list_id=2;` 122 | 123 | You should see that for ids 8 and 9, we have `Y` in the complete column. 124 | 125 | Congratulations! You now know how to mount volumes into a container to persist data and commit changes from a container to create a new image. Continue on to the [next exercise](../5_Working_with_container_registry) to learn how to use the IBM container service. 126 | 127 | -------------------------------------------------------------------------------- /5_Working_with_container_registry/README.md: -------------------------------------------------------------------------------- 1 | # Working with IBM Cloud Container Registy 2 | 3 | So we have created our amazing database, we now want to push it to our private registry. In real agile development, a push to a private registry would trigger a build to run a test suite in a CI/CD pipeline. For this we will be using IBM Container Registry. If you have not created an IBM Cloud account or have not installed the IBM Cloud CLI, [click here](../../../) to follow the steps do so. 4 | 5 | First we will need to create a unique namespace for the registry: 6 | 7 | `ibmcloud cr namespace-add ` 8 | 9 | It will be a good idea to use the same `username` you used when created our custom MySQL image. If this username is taken, just use a value that is unique. 10 | 11 | Next, we need to log the local Docker daemon into the IBM Cloud Container Registry: 12 | 13 | `ibmcloud cr login` 14 | 15 | Take note of the registry that you log in to. It should be in the format: `.icr.io` e.g. `uk.icr.net` 16 | 17 | Now we are ready to push our image from our local machine up to the IBM Cloud Container Registry. Before we do so, we will need to tag the image appropriately: 18 | 19 | `docker tag /mysql-server //mysql-server` 20 | 21 | * The `` field is the same one we used when creating our custom MySQL image. 22 | * The `` field corresponds to the location returned after running `ibmcloud cr login`. 23 | * The `` field is the unique identifier we created in the `ibmcloud cr namespace-add` command. 24 | * As we have not specified a tag for the image, it will tag as `latest` by default. 25 | 26 | Now we can push our newly tagged custom MySQL image to Cloud Registry: 27 | 28 | `docker push //mysql-server` 29 | 30 | If you can see the layers of the image being pushed up then you are successfully pushing to IBM Container Registry. Let's go see what you just deployed: 31 | 32 | 1. Login to [IBM Cloud](https://cloud.ibm.com) 33 | 2. Click on the Catalog button on the menu 34 | 3. Search for: `container reg` and click on the `Container Registry` service 35 | 4. Ensure that you select the correct region e.g. if eu-gb, then make sure the region is London 36 | 5. Select `images` and locate the MySQL image we just pushed in the main screen 37 | 38 | One of the nice things about Container Registry is that it will scan the image for vulnerabilites everytime an image is pushed to the registry or is updated. Let's click on the link under the `Secuirty Status` column for a summary of the security report. In addition to vulnerability scans, Container Registry shows us configuration issues that we should fix to better secure the container. 39 | 40 | Congratulations, you have completed the Containers 101 workshop! You now know how the full Docker ecosystem works from creating a Docker image, creating a container, commiting a new image from a container and both pushing to and pulling from registries. You have also learnt how to work inside containers and control the lifecycle of container. 41 | 42 | ## What now? 43 | 44 | The natural progression from containers is managing a multiple containers in a cluster. This level of thinking adds another level of abstraction on top of Docker with open source solutions such as Kubetnetes. Using your IBM Cloud account, feel free to check out the [IBM Cloud Kubernetes Service](https://cloud.ibm.com/docs/containers/container_index.html#container_index) and the various tutorials to learn how you can start managing containers in a cluster. 45 | -------------------------------------------------------------------------------- /Containers 101.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMDeveloperUK/Containers-101/39761ab7614891f2984217d9bce407a956797ee2/Containers 101.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Containers 101 2 | 3 | Welcome to the Containers 101 workshop! 4 | 5 | ## Install Docker 6 | 7 | Before you start running any of the exercises, make sure you have downloaded Docker CE from the [Docker store](https://store.docker.com/search?type=edition&offering=community). 8 | 9 | *Note:* you will be prompted to create an account on the Docker store if you don't already have one. 10 | 11 | Once you have installed Docker, you will need to ensure that you [switch to Linux Containers](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers). 12 | 13 | ### Considerations for Windows 7/8 users 14 | 15 | Docker CE is only compatible with Microsoft Windows 10 Professional or Enterprise 64-bit. For people running other versions of Windows, you will need to install [Docker Toolbox](https://docs.docker.com/toolbox/toolbox_install_windows/) 16 | 17 | ## Registering for a free IBM Cloud Account 18 | 19 | 1. If you don't have one already, sign up for a free IBM Cloud Account [here](https://ibm.biz/BdzgtG). 20 | 21 | 2. Verify your account via the email the platform sends you. 22 | 23 | 3. Log in to your IBM Cloud Account. 24 | 25 | ## Install the IBM Cloud CLI 26 | 27 | For the last part of the workshop, we will be using IBM Container Registry. To run this part of the workshop, you will need to install the IBM Cloud coomand line tool (CLI) and then configure it to use Container Registry: 28 | 29 | 1. Go to [this docs link](https://cloud.ibm.com/docs/home/tools) and follow the short instructions to download and install the CLI. 30 | 31 | 2. Install the Container Registry plug-in. 32 | 33 | On Mac or Linux run: 34 | 35 | `curl -sL http://ibm.biz/idt-installer | bash` 36 | 37 | Or on Windows run: 38 | 39 | `Set-ExecutionPolicy Unrestricted; iex(New-Object Net.WebClient).DownloadString('http://ibm.biz/idt-win-installer')` 40 | 41 | ## Hands-on workshop 42 | 43 | Now you're ready to go! 44 | 45 | After completing the workshops, you will have experience with creating a Docker image from a Docker file, managing Docker containers (running, stopping, removing, viewing), working inside Docker containers, pushing and pulling Docker images to and from registries and working with persistent data. 46 | 47 | To begin, let's start of with the [first exercise](./1_Create_view_pull_images) where you will create your first Docker image! You will also learn how to pull other existing Docker images from DockerHub and inspect the history of image. 48 | --------------------------------------------------------------------------------