Girl Develop It is here to provide affordable and accessible programs to learn software through mentorship and hands-on instruction.
Some "rules"
As we have seen, the images on the Docker Hub are sometimes very basic.
How do we want to construct our own images?
As an example, we will build an image that has figlet.
First, we will do it manually with docker commit.
Then later, we will use a Dockerfile and docker build.
Our base will be the ubuntu image.
Start an Ubuntu container:
$ docker run -it ubuntu
root@<yourContainerId>:#/
Run the command apt-get update to refresh the list of packages available to install.
Then run the command apt-get install figlet to install the program we are interested in.
root@:#/ apt-get update && apt-get install figlet
.... OUTPUT OF APT-GET COMMANDS ....
Type exit at the container prompt to leave the interactive session.
Now let's run docker diff to see the difference between the base image and our container.
$ docker diff <yourContainerId>
C /root
A /root/.bash_history
C /tmp
C /usr
C /usr/bin
A /usr/bin/figlet
...
As we know:
The docker commit command will create a new layer with those changes, and a new image using this new layer.
$ docker commit <yourContainerId>
<newImageId>
The output of the docker commit command will be the ID for your newly created image.
We can run this image:
$ docker run -it <newImageId>
root@fcfb62f0bfde:/# figlet hello
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/
Referring to an image by its ID is not convenient. Let's tag it instead.
We can use the tag command:
$ docker tag <newImageId> figlet
But we can also specify the tag as an extra argument to commit:
$ docker commit <containerId> figlet
And then run it using its tag:
$ docker run -it figlet
Manual process = bad.
Automated process = good.
Next we will learn how to automate the build process by writing a Dockerfile.
Our Dockerfile must be in a new, empty directory.
$ mkdir myimage
$ cd myimage
$ vim Dockerfile
Of course, you can use any other editor of your choice.
FROM ubuntu
RUN apt-get update
RUN apt-get install figlet
Save our file, then execute:
$ docker build -t figlet .
The output of docker build looks like this:
$ docker build -t figlet .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu
---> e54ca5efa2e9
Step 1 : RUN apt-get update
---> Running in 840cb3533193
---> 7257c37726a1
Removing intermediate container 840cb3533193
Step 2 : RUN apt-get install figlet
---> Running in 2b44df762a2f
---> f9e8f1642759
Removing intermediate container 2b44df762a2f
Successfully built f9e8f1642759
Sending build context to Docker daemon 2.048 kB
Step 1 : RUN apt-get update
---> Running in 840cb3533193
(...output of the RUN command...)
---> 7257c37726a1
Removing intermediate container 840cb3533193
If you run the same build again, it will be instantaneous.
Why?
You can force a rebuild with docker build --no-cache
The resulting image is not different from the one produced manually.
$ docker run -ti figlet
root@91f3c974c9a1:/# figlet hello
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/
Sweet is the taste of success!
The history command lists all the layers composing an image.
For each layer, it shows its creation time, size, and creation command.
When an image was built with a Dockerfile, each layer corresponds to a line of the Dockerfile.
$ docker history figlet
IMAGE CREATED CREATED BY SIZE
f9e8f1642759 About an hour ago /bin/sh -c apt-get install fi 1.627 MB
7257c37726a1 About an hour ago /bin/sh -c apt-get update 21.58 MB
07c86167cdc4 4 days ago /bin/sh -c #(nop) CMD ["/bin 0 B
<missing> 4 days ago /bin/sh -c echo '#!/bin/sh' 194.5 kB
Most Dockerfile arguments can be passed in two forms:
Let's change our Dockerfile as follows!
FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]
Then build the new Dockerfile.
$ docker build -t figlet .
Compare the new history:
$ docker history figlet
Spend this time working on Docker Labs Exercise 2.0: Webapps with Docker
So far, we have installed things in our container images by downloading packages.
We can also copy files from the build context to the container that we are building.
Remember: the build context is the directory containing the Dockerfile.
Next, we will learn a new Dockerfile keyword: COPY.
We want to build a container that compiles a basic "Hello world" program in C.
Here is the program, hello.c:
int main () {
puts("Hello, world!");
return 0; }
Let's create a new directory, and put this file in there.
Then we will write the Dockerfile.
On Debian and Ubuntu, the package build-essential will get us a compiler.
When installing it, don't forget to specify the -y flag, otherwise the build will fail (since the build cannot be interactive).
Then we will use COPY to place the source file into the container.
FROM ubuntu
RUN apt-get update
RUN apt-get install -y build-essential
COPY hello.c /
RUN make hello
CMD /hello
Create this Dockerfile.
Success!
Can specify a base image:
FROM ubuntu
An image tagged with a specific version:
FROM ubuntu:12.04
A user image:
FROM training/sinatra
Or self-hosted image:
FROM localhost:5000/funtoo
The FROM instruction can be specified more than once to build multiple images.
FROM ubuntu:14.04 .. .
FROM fedora:20 .. .
Integrate CI and unit tests in the build system
FROM <baseimage>
RUN <install dependencies>
COPY <code>
RUN <build code>
RUN <install test dependencies>
COPY <test data sets and fixtures>
RUN <unit tests>
FROM <baseimage>
RUN <install dependencies>
COPY <vcode>
RUN vbuild code>
CMD, EXPOSE ...
The MAINTAINER instruction tells you who wrote the Dockerfile.
MAINTAINER Docker Education Team <education@docker.com>
It's optional but recommended.
The RUN instruction can be specified in two ways.
With shell wrapping, which runs the specified command inside a shell, with /bin/sh -c:
RUN apt-get update
Or using the exec method, which avoids shell string expansion, and allows execution in images that don't have /bin/sh:
RUN [ "apt-get", "update" ]
If you want to start something automatically when the container runs, you should use CMD and/or ENTRYPOINT.
It is possible to execute multiple commands in a single step:
RUN apt-get update && apt-get install -y wget && apt-get clean
It is also possible to break a command onto multiple lines:
It is possible to execute multiple commands in a single step:
RUN apt-get update \
&& apt-get install -y wget \
&& apt-get clean
The EXPOSE instruction tells Docker what ports are to be published in this image.
EXPOSE 8080
EXPOSE 80 443
EXPOSE 53/tcp 53/udp
A public port is reachable from other containers and from outside the host.
A private port is not reachable from outside.
The COPY instruction adds files and content from your host into the image.
COPY . /src
This will add the contents of the build context (the directory passed as an argument to docker build) to the directory /src in the container.
Note: you can only reference files and directories inside the build context. Absolute paths are taken as being anchored to the build context, so the two following lines are equivalent:
COPY . /src
COPY / /src
Attempts to use .. to get out of the build context will be detected and blocked with Docker, and the build will fail.
Otherwise, a Dockerfile could succeed on host A, but fail on host B.
ADD works almost like COPY, but has a few extra features.
ADD can get remote files:
ADD http://www.example.com/webapp.jar /opt/
This would download the webapp.jar file and place it in the /opt directory.
ADD will automatically unpack zip files and tar archives:
ADD ./assets.zip /var/www/htdocs/assets/
This would unpack assets.zip into /var/www/htdocs/assets. However, ADD will not automatically unpack remote archives.
So far, we have referenced containers with their ID.
We have copy-pasted the ID, or used a shortened prefix.
But each container can also be referenced by its name.
If a container is named prod-db, I can do:
$ docker logs prod-db
$ docker stop prod-db
etc.
When we create a container, if we don't give a specific name, Docker will pick one for us.
It will be the concatenation of:
Examples: happy_curie, clever_hopper, jovial_lovelace
You can set the name of the container when you create it.
$ docker run --name ticktock jpetazzo/clock
If you specify a name that already exists, Docker will refuse to create the container.
This lets us enforce unicity of a given resource.
$ docker inspect <containerID>
[{
"AppArmorProfile": "",
"Args": [],
"Config": {
"AttachStderr": true,
"AttachStdin": false,
"AttachStdout": true,
"Cmd": [
"bash"
],
"CpuShares": 0,
...
There are multiple ways to consume that information.
$ docker inspect <containerID> | jq .
We will see a better solution which doesn't require extra tools.
You can specify a format string, which will be parsed by Go's text/template package.
$ docker inspect --format '{{ json .Created }}'
"2015-02-24T07:21:11.712240394Z"