From our Previous Post we have gone through an overview of Docker and setting up Docker on Ubuntu 16.04.
In this guide we will be going through some Docker Examples, a hands-on guide.
Docker Hub:
Docker hub is a public repository that hosts images, you instead of installing MySQL for example, you can search for MySQL images on docker hub, and create containers from them.
But just as a side note, there's a LOT of images, and anyone can upload images to Docker Hub, its always a good practice, when launching a container, to see what is in the container just to make sure everything appears as they should be.
Examples, Examples and more Examples:
The will go through the following examples that I found helped me understand Docker from a "learn-as-you-do-experience":
Search for a Base Image:
In our example we will search for a small image, named busybox:
$ docker search busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
busybox Busybox base image. 1014 [OK]
Now that we located our image that is hosted on Docker Hub, we will pull down the image to our local cache then we will instantiate an instance from our image.
$ docker run -it busybox sh
/ #
Run a Container From a Image:
Note that while we are in the shell of this container, as soon as we exit this instance, any data that was created on the container will be lost, lets have a look at an example:
$ docker run -it busybox sh
/ # touch /root/test.txt
/ # ls /root/ | wc -l
1
/ # exit
Run another container from our image, and you will find that the data will be missing that was created above:
$ docker run -it busybox sh
/ # ls /root/ | wc -l
0
/ # exit
Now in order to persist data, you could look at a couple of options that includes:
-
You launch a container, you work on the cotainer, exit, and commit the changes to a image, then you also have the option to push it to a registry of choice.
-
You could mount the directory to the host.
-
Data Container Volumes.
Committing Changes to your image:
Let's create a container from the busybox image, and then create a file under the root partition:
$ docker run -it busybox sh
/ # echo "foo" > /root/data.txt
/ # exit
Let's first have a look at the images that we currently have stored locally on the docker host:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 00f017a8c2a6 3 months ago 1.11MB
We can see that we have one image stored. In order to have an image that contains the data that we produced on our container, we need to commit these changes to a image and we can set a tag as an identifier.
The first thing would be to get the container id, that we can achieve by running docker ps
, but we would like to filter the output to only show the latest container that ran, that includes all states (Created, Exited, or Running):
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae8d125e1b2b busybox "sh" 26 minutes ago Exited (0) 3 seconds ago gallant_goldwasser
Committing the changes to our new image:
$ docker commit ae8d125e1b2b mybusybox:v1
sha256:5bc7e5e074c8837964d99721ed36ff46dffe3f52259db8b14b6ccf53e29131ed
Have a look at our images:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mybusybox v1 5bc7e5e074c8 16 seconds ago 1.11MB
busybox latest 00f017a8c2a6 3 months ago 1.11MB
Running a container from our created image:
$ docker run -it mybusybox:v1 sh
/ # cat /root/data.txt
foo
We can also run our container in a detached mode, so when we exit our container, that it keeps running in the background. Optionally set a name for our container using the --name
option:
$ docker run --name container01 -itd mybusybox:v1 sh
030244a7edaa1d7eb09915c1c5d8f85ca55c4013f4456a040f8cf75fcf834758
List the running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
030244a7edaa mybusybox:v1 "sh" 3 minutes ago Up 3 minutes container01
Entering the Shell from our running container:
You can enter the shell either via passing the container id
or the container name
:
- Container ID:
$ docker exec -it 030244a7edaa sh
/ #
- Container Name:
$ docker exec -it container01 sh
/ #
Executing Commands on a Container directly from the Docker Host:
$ docker exec -it 030244a7edaa ifconfig eth0 | head -2
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:09
inet addr:172.17.0.9 Bcast:0.0.0.0 Mask:255.255.0.0
Data Volumes:
A data volume has a reserved directory within one or more containers that bypasses the union file system. One of the advantages that data volumes has to offer, is that multiple containers can share the same data volume.
Let's create a data volume called myvol01
:
$ docker volume create --driver local myvol01
myvol01
Listing the volumes on our Docker Host:
$ docker volume list | grep -E '(DRIVER|myvol)'
DRIVER VOLUME NAME
local myvol01
In order to view information about our volume, we use the inspect
option on our volume:
$ docker volume inspect myvol01
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvol01/_data",
"Name": "myvol01",
"Options": {},
"Scope": "local"
}
]
Create a Container with the Data Volume:
Creating a container using the data volume that we created:
$ docker run --name container01 -v myvol01:/opt -itd mybusybox:v1 sh
Inspecting the configuration of our container:
$ docker inspect container01
[
{
"Id": "aac1cad1e8f4db10ea5d8f64f2198b51070bcccfb4400b0b8677bda27595292d",
"Created": "2017-06-09T23:54:39.803934979Z",
"Path": "sh",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"StartedAt": "2017-06-09T23:54:40.485994777Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "",
"ResolvConfPath": "/var/lib/docker/foo/bar/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/foo/hostname",
"HostsPath": "/var/lib/docker/containers/foo/hosts",
"LogPath": "/var/lib/docker/containers/foo/bar-json.log",
"Name": "/container02",
"HostConfig": {
"Binds": [
"myvol01:/opt"
],
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"ConsoleSize": [
0,
0
],
..
},
"Mounts": [
{
"Type": "volume",
"Name": "myvol01",
"Source": "/var/lib/docker/volumes/myvol01/_data",
"Destination": "/opt",
"Driver": "local",
"Mode": "z",
"RW": true,
}
],
"Config": {
"Hostname": "aac1cad1e8f4",
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"sh"
],
"Image": "mybusybox:v1",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"Ports": {},
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.10",
"MacAddress": "02:42:ac:11:00:0a",
"Networks": {
"bridge": {
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.10",
"IPPrefixLen": 16,
"MacAddress": "02:42:ac:11:00:0a"
}
}
}
}
]
We can create a second container using the same volume, and you will find that both containers shares the same volume, which the path is mapped to:
$ docker run --name container02 -v myvol01:/opt -itd mybusybox:v1 sh
More information on Docker Volumes can be found on their docs
Building an Image from a Dockerfile:
A Dockerfile is like a set of instructions that defines how your expected environment inside your container should look like.
A Dockerfile will be created in a new directory, including all the needed files that our Dockerfile will be referencing. For this example, we will be creating a simple Python Flask app that just responds on a HTTP GET Request.
Create The Directory:
$ mkdir flask-app
$ cd flask-app
Create the Python Flask App:
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello from your Docker Container'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Create the Requirements File:
requirements.txt
flask
Create the Dockerfile:
Dockerfile
# use an official python runtime as a base image
FROM python:2.7-slim
# set the working directory to /app
WORKDIR /app
# copy the directory contents into the container to /app
ADD . /app
# install the dependencies from our requirements file
RUN pip install -r requirements.txt
# expose port 5000 outside the container
EXPOSE 5000
# run the python application
CMD ["python", "app.py"]
Build the Image:
We can now build the image by passing a tag name, the example output that I will provide will be a filtered version just so that you can see how it more or less looks like:
$ docker build -t flask-app:v1 .
Sending build context to Docker daemon 4.096kB
Step 1/6 : FROM python:2.7-slim
2.7-slim: Pulling from library/python
ef0380f84d05: Pull complete
4a615537653d: Pull complete
dd25cca6a171: Pull complete
860f8782a0f0: Pull complete
Step 2/6 : WORKDIR /app
Step 3/6 : ADD . /app
Step 4/6 : RUN pip install -r requirements.txt
Collecting flask (from -r requirements.txt (line 1))
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.2 click-6.7 flask-0.12.2 itsdangerous-0.24
Step 5/6 : EXPOSE 5000
Step 6/6 : CMD python app.py
Successfully built edbfd6c89474
Successfully tagged flask-app:v1
Listing our image:
docker images | grep -E '(REPO|flask-app)'
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-app v1 edbfd6c89474 22 seconds ago 194MB
Running a Container from Our Image:
$ docker run --name flaskapp -itd flask-app:v1
Listing our Container:
$ docker ps | grep -E '(CONTAINER|flaskapp)'
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a4584e5f8181 flask-app:v1 "python app.py" About a minute ago Up About a minute 5000/tcp
As we have exposed port 5000, we need to determine the IP Address of our Container, that can be done by doing the following:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' flaskapp
172.17.0.12
Now, to test our web application, lets make a GET Request to our Endpoint:
$ curl -XGET http://172.17.0.12:5000/
Hello from your Docker Container
We can also do port forwarding, by binding port 80
to the containers port which is 5000
, let's first delete our container:
$ docker rm flaskapp -f
flaskapp
Now that the container has been deleted, lets create that container again, but this time we will bind port 80
of our docker host, to port 5000
of our container, so that we can enable clients from outside our Docker Host to reach the endpoint:
$ docker run --name flaskapp -itd -p 80:5000 flask-app:v1
Now if we list the container we will see the port configuration shows that the port the host port 80
will be translated to port 5000
of the container:
$ docker ps | grep -E '(CONTAINER|flaskapp)'
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c4ef4da4b677 flask-app:v1 "python app.py" 15 seconds ago Up 14 seconds 0.0.0.0:80->5000/tcp flaskapp
To test it out, first get our IP Address of our Docker Host:
$ ifconfig eth0 | grep Bcast | awk '{print $2}' | cut -d':' -f2
10.4.116.69
Finally, making the GET request to our Docker Host:
$ curl -XGET http://10.4.116.69
Hello from your Docker Container
I hope this was useful, if there is any feedback or question, please feel free to leave a comment below.
Comments