Ajeet Raina Docker Captain, ARM Innovator & Docker Bangalore Community Leader.

What is a Dockerfile – A Step-By-Step Guide

21 min read

A Dockerfile is a text file which contains a series of commands or instructions. These instructions are executed in the order in which they are written. Execution of these instructions takes place on a base image. On building the Dockerfile, the successive actions form a new image from the base parent image.

 

We will go through each of instructions under Dockerfile and see how it is used.

Lab #1: Create an image with GIT installed

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Create an image with GIT installed
  • Tag your image as labs-git:v1.0
  • Create a container based on that image, and run git –version to check that it is installed correctly

Creating Dockerfile

FROM alpine:3.5
RUN apk update
RUN apk add git

Build Docker Image

docker build -t ajeetraina/alpine-git .

Tagging image as labs-git

docker tag ajeetraina/alpine-git ajeetraina/labs-git:v1.0

Verify the Images

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
ajeetraina/alpine-git   latest              cb913e37a593        16 seconds ago      26.6MB
ajeetraina/labs-git     v1.0                cb913e37a593        16 seconds ago      26.6MB

Create a container

docker run -itd ajeetraina/labs-git:v1.0 /bin/sh
$ docker ps
CONTAINER ID        IMAGE                      COMMAND             CREATED             STATUS      PORTS               NAMES
3e26a5268f55        ajeetraina/labs-git:v1.0   "/bin/sh"           4 seconds ago       Up 2 seconds  elated_neumann

Enter into Container Shell

docker attach 3e26

Please press “Enter” key twice so as to enter into container shell

Verify if GIT is installed

/ # git --version
git version 2.13.7

Lab #2: Create an image with ADD instruction

COPY and ADD are both Dockerfile instructions that serve similar purposes. They let you copy files from a specific location into a Docker image.

COPY takes in a src and destination. It only lets you copy in a local file or directory from your host (the machine building the Docker image) into the Docker image itself.

ADD lets you do that too, but it also supports 2 other sources. First, you can use a URL instead of a local file / directory. Secondly, you can extract a tar file from the source directly into the destination.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Create an image with ADD instruction
  • Tag your image as labs-add:v1.0
  • Create a container based on that image, and see the extracted tar file.

Creating Dockerfile

FROM alpine:3.5
RUN apk update
ADD http://www.vlsitechnology.org/pharosc_8.4.tar.gz .

Build Docker Image

docker build -t alpine-add . -f 

Tagging image as labs-add

docker tag alpine-add labs-add:v1.0

Verify the Images

$ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
alpine-add        latest              cdf97cb49d48        38 minutes ago       300MB
labs-add          v1.0                cdf97cb49d48        38 minutes ago       300MB

Create a container

docker run -itd saiyam911/labs-add:v1.0 /bin/sh
$ docker ps
CONTAINER ID        IMAGE                      COMMAND             CREATED             STATUS              PORTS               NAMES
f0940750f61a        labs-add:v1.0   "/bin/sh"           20 seconds ago      Up 18 seconds                           distracted_darwin

Enter into Container Shell

docker attach f094

Please press “Enter” key twice so as to enter into container shell

Verify if the link has been extracted onto the container

/ # ls -ltr
-rw-------    1 root     root     295168000 Sep 19  2007 pharosc_8.4.tar.gz

ADD Command lets you to add a tar directly from a link and explode to the container.

 

Lab #3: Create an image with COPY instruction

The COPY instruction copies files or directories from source and adds them to the filesystem of the container at destinatio.

Two form of COPY instruction

COPY [--chown=:] ... 
COPY [--chown=:] ["",... ""] (this form is required for paths containing whitespace)

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Create an image with COPY instruction
  • COPY instruction in Multi-stage Builds

Create an image with COPY instruction

Dockerfile

FROM nginx:alpine
LABEL maintainer="Collabnix"

COPY index.html /usr/share/nginx/html/
ENTRYPOINT ["nginx", "-g", "daemon off;"]

Lets create the index.html file

$ echo "Welcome to Dockerlabs !" > index.html

Building Docker Image

$ docker image build -t cpy:v1 .

Staring the container

$ docker container run -d --rm --name myapp1 -p 80:80 cpy:v1

Checking index file

$ curl localhost
Welcome to Dockerlabs !

COPY instruction in Multi-stage Builds

Dockerfile

FROM alpine AS stage1
LABEL maintainer="Collabnix"
RUN echo "Welcome to Docker Labs!" > /opt/index.html

FROM nginx:alpine
LABEL maintainer="Collabnix"
COPY --from=stage1 /opt/index.html /usr/share/nginx/html/
ENTRYPOINT ["nginx", "-g", "daemon off;"]

Building Docker Image

$ docker image build -t cpy:v2 .

Staring the container

$ docker container run -d --rm --name myapp2 -p 8080:80 cpy:v2

Checking index file

$ curl localhost:8080
Welcome to Docker Labs !

NOTE: You can name your stages, by adding an AS to the FROM instruction.By default, the stages are not named, and you can refer to them by their integer number, starting with 0 for the first FROM instruction.You are not limited to copying from stages you created earlier in your Dockerfile, you can use the COPY –from instruction to copy from a separate image, either using the local image name, a tag available locally or on a Docker registry.

 

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

 

Lab #4: Create an image with CMD instruction

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Creating Dockerfile

FROM alpine:3.6

RUN apk update
CMD ["top"]

Building Docker Container

docker build -t ajeetraina/lab3_cmd . -f Dockerfile_cmd

Running the Docker container

docker run ajeetraina/lab3_cmd:latest

 

 

Lab #5 : Create an image with ENTRYPOINT instruction

The ENTRYPOINT instruction make your container run as an executable.
ENTRYPOINT can be configured in two forms:

  • Exec Form
    ENTRYPOINT [“executable”, “param1”, “param2”]
  • Shell Form
    ENTRYPOINT command param1 param2

If an image has an ENTRYPOINT if you pass an argument it, while running container it wont override the existing entrypoint, it will append what you passed with the entrypoint.To override the existing ENTRYPOINT you should user –entrypoint flag when running container.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Create an image with ENTRYPOINT instruction(Exec Form)
  • ENTRYPOINT instruction in Shell Form
  • Override the existing ENTRYPOINT

Create an image with ENTRYPOINT instruction(Exec Form)

Dockerfile

FROM alpine:3.5
LABEL maintainer="Collabnix"

ENTRYPOINT ["/bin/echo", "Hi, your ENTRYPOINT instruction in Exec Form !"]

Build Docker Image

$ docker build -t entrypoint:v1 .

Verify the Image

$ docker image ls

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
entrypoint          v1                  1d06f06c2062        2 minutes ago       4MB
alpine              3.5                 f80194ae2e0c        7 months ago        4MB

Create a container

$ docker container run entrypoint:v1
Hi, your ENTRYPOINT instruction in Exec Form !

ENTRYPOINT instruction in Shell Form

Dockerfile

$ cat Dockerfile 
FROM alpine:3.5
LABEL maintainer="Collabnix"

ENTRYPOINT echo "Hi, your ENTRYPOINT instruction in Shell Form !"

Build Docker Image

$ docker build -t entrypoint:v2 .

Verify the Image

$ docker image ls

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
entrypoint          v2                  cde521f13080        2 minutes ago       4MB
entrypoint          v1                  1d06f06c2062        5 minutes ago      4MB
alpine              3.5                 f80194ae2e0c        7 months ago        4MB

Create a container

$ docker container run entrypoint:v2
Hi, your ENTRYPOINT instruction in Shell Form !

Override the existing ENTRYPOINT

$ docker container run --entrypoint "/bin/echo" entrypoint:v2 "Hello, Welocme to Docker Meetup! "
Hello, Welocme to Docker Meetup! 

 

Lab #6: WORKDIR instruction

The WORKDIR directive in Dockerfile defines the working directory for the rest of the instructions in the Dockerfile. The WORKDIR instruction wont create a new layer in the image but will add metadata to the image config. If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction. you can have multiple WORKDIR in same Dockerfile. If a relative path is provided, it will be relative to the previous WORKDIR instruction.

WORKDIR /path/to/workdir

If no WORKDIR is specified in the Dockerfile then the default path is /. The WORKDIR instruction can resolve environment variables previously set in Dockerfile using ENV.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment

  • Dockerfile with WORKDIR instruction
  • WORKDIR with Relative path
  • WORKDIR with Absolute path
  • WORKDIR with environment variables as path

Dockerfile with WORKDIR instruction

Dockerfile

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

WORKDIR /opt

Building Docker image

$ docker build -t workdir:v1 .

Testing current WORKDIR by running container

$ docker run -it workdir:v1 pwd

WORKDIR with relative path

Dockerfile

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

WORKDIR /opt
RUN echo "Welcome to Docker Labs" > opt.txt
WORKDIR folder1
RUN echo "Welcome to Docker Labs" > folder1.txt
WORKDIR folder2
RUN echo "Welcome to Docker Labs" > folder2.txt

Building Docker image

$ docker build -t workdir:v2 .

Testing current WORKDIR by running container

$ docker run -it workdir:v2 pwd

WORKDIR with Absolute path

Dockerfile

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

WORKDIR /opt/folder1
RUN echo "Welcome to Docker Labs" > opt.txt
WORKDIR /var/tmp/

Building Docker image

$ docker build -t workdir:v3 .

Testing current WORKDIR by running container

$ docker run -it workdir:v3 pwd

WORKDIR with environment variables as path

Dockerfile

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

ENV DIRPATH /myfolder
WORKDIR $DIRPATH

Building Docker image

$ docker build -t workdir:v4 .

Testing current WORKDIR by running container

$ docker run -it workdir:v4 pwd

Lab #7: RUN instruction

The RUN instruction execute command on top of the below layer and create a new layer.
RUN instruction can be wrote in two forms:

  • RUN (shell form)
  • RUN [“executable”, “param1”, “param2”] (exec form)

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Create an image with RUN instruction
  • Combining multiple RUN instruction to one

Create an image with RUN instruction

FROM alpine:3.9.3
LABEL maintainer="Collabnix"
RUN apk add --update 
RUN apk add curl
RUN rm -rf /var/cache/apk/

Building Docker image

$ docker image build -t run:v1 .

Checking layer of the image

$  docker image history run:v1 
IMAGE               CREATED             CREATED BY                                      SIZE                
NT
5b09d7ba1736        19 seconds ago      /bin/sh -c rm -rf /var/cache/apk/               0B                  
192115cc597a        21 seconds ago      /bin/sh -c apk add curl                         1.55MB              
0518580850f1        43 seconds ago      /bin/sh -c apk add --update                     1.33MB              
8590497d994e        45 seconds ago      /bin/sh -c #(nop)  LABEL maintainer=Collabnix   0B                  
cdf98d1859c1        4 months ago        /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
           4 months ago        /bin/sh -c #(nop) ADD file:2e3a37883f56a4a27…   5.53MB 

Number of layers 6

Checking image size

$ docker image ls run:v1
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
run                 v1                  5b09d7ba1736        4 minutes ago       8.42MB

Its 8.42MB

Combining multiple RUN instruction to one

FROM alpine:3.9.3
LABEL maintainer="Collabnix"
RUN apk add --update && \
	apk add curl  && \  
	rm -rf /var/cache/apk/

Building Docker image

$ docker image build -t run:v2 .

Checking layer of the image

$ docker image history run:v2
IMAGE               CREATED             CREATED BY                                      SIZE            
NT
784298155541        50 seconds ago      /bin/sh -c apk add --update  && apk add curl…   1.55MB              
8590497d994e        8 minutes ago       /bin/sh -c #(nop)  LABEL maintainer=Collabnix   0B                  
cdf98d1859c1        4 months ago        /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
           4 months ago        /bin/sh -c #(nop) ADD file:2e3a37883f56a4a27…   5.53MB

Number of layers 4

Checking image size

$ docker image ls run:v2
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
run                 v2                  784298155541        3 minutes ago       7.08MB

its now 7.08MB

Lab #8: Create an image with ARG instruction

The ARG directive in Dockerfile defines the parameter name and defines its default value. This default value can be overridden by the --build-arg = in the build command docker build.

`ARG [=]`

The build parameters have the same effect as ENV, which is to set the environment variables. The difference is that the environment variables of the build environment set by ARG will not exist in the future when the container is running. But don’t use ARG to save passwords and the like, because docker history can still see all the values.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment

  • Writing a Dockerfile with ARG instruction
  • Building Docker Image with default argument
  • Running container argv:v1
  • Passing the argument during image build time
  • Running container argv:v2

Writing a Dockerfile with ARG instruction

We are writing a Dockerfile which echo “Welcome $WELCOME_USER, to Docker World!” where default argument value for WELCOME_USER as Collabnix.

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

#Setting a default value to Argument WELCOME_USER
ARG WELCOME_USER=Collabnix
RUN echo "Welcome $WELCOME_USER, to Docker World!" > message.txt
CMD cat message.txt

Building Docker Image with default argument

$ docker image build -t arg:v1 .

Running container argv:v1

$ docker run arg:v1

Welcome Collabnix, to Docker World!

Passing the argument(WELCOME_USER) during image build time using –build-arg flag

$ docker image build -t arg:v2 --build-arg WELCOME_USER=Savio .

Running container argv:v2

$ docker run arg:v2

Welcome Savio, to Docker World!

NOTE: ARG is the only one instruction which can come before FROM instruction, but then arg value can be used only by FROM.

Lab #9: ENV instruction

The ENV instruction in Dockerfile sets the environment variable for your container when you start. The default value can be overridden by passing --env = when you start the container.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment

  • Writing a Dockerfile with ENV instruction
  • Building Docker Image
  • Running container env:v1
  • Override existing env while running container

Writing a Dockerfile with ENV instruction

Dockerfile

FROM alpine:3.9.3
LABEL maintainer="Collabnix"

ENV WELCOME_MESSAGE="Welcome to Docker World"

CMD ["sh", "-c", "echo $WELCOME_MESSAGE"]

Building Docker Image

$ docker build -t env:v1 .

Running container env:v1

$ docker container run env:v1
Welcome to Docker World

Override existing env while running container

$ docker container run --env WELCOME_MESSAGE="Welcome to Docker Workshop" env:v1 
Welcome to Docker Workshop
 

 

Lab #10: VOLUME instruction

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment

  • Create an image with VOLUME instruction
  • Finding the volume created on the host
  • Testing mount working as exepected

Create an image with VOLUME instruction

Dockerfile

FROM nginx:alpine
LABEL maintainer="Collabnix"

VOLUME /myvol
CMD [ "nginx","-g","daemon off;" ]

Building Docker image

$ docker build -t volume:v1 .

Create a container based on volume:v1 image

$ docker container run --rm -d --name volume-test volume:v1

Finding the volume created on the host

Checking the volume name of the container

$ docker container inspect -f '' volume-test
ed09456a448934218f03acbdaa31f465ebbb92e0d45e8284527a2c538cc6b016

Listout Volume in the host

$ docker volume ls
DRIVER              VOLUME NAME
local               ed09456a448934218f03acbdaa31f465ebbb92e0d45e8284527a2c538cc6b016

You will see the volume has been created.

Volume mount path in host

$ docker container inspect -f '' volume-test
/var/lib/docker/volumes/ed09456a448934218f03acbdaa31f465ebbb92e0d45e8284527a2c538cc6b016/_data

Testing mount working as exepected

Create a file in this folder

$ touch /var/lib/docker/volumes/ed09456a448934218f03acbdaa31f465ebbb92e0d45e8284527a2c538cc6b016/_data/mytestfile.txt

Checking file is there in run container

$ docker container exec -it volume-test ls myvol

Lab #11: EXPOSE instruction

The EXPOSE instruction expose a port, the protocol can be UDP or TCP associated with the indicated port, default is TCP with no specification. The EXPOSE won’t be able to map the ports on the host machine. Regardless of the EXPOSE settings, EXPOSE port can be override using -p flag while starting the container.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment

  • Create an image with EXPOSE instruction
  • Inspecting the EXPOSE port in the image
  • Publish all exposed port

Create an image with VOLUME instruction

Dockerfile

FROM nginx:alpine
LABEL maintainer="Collabnix"

EXPOSE 80/tcp
EXPOSE 80/udp

CMD [ "nginx","-g","daemon off;" ]

Building Docker image

$ docker build -t expose:v1 .

Create a container based on expose:v1 image

$  docker container run --rm -d --name expose-inst expose:v1

Inspecting the EXPOSE port in the image

$ docker image inspect --format= expose:v1

Publish all exposed ports

We can publish all EXPOSE port using -P flag.

$ docker container run --rm -P -d --name expose-inst-Publish expose:v1

Checking the publish port

$  docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                          NAMES
24983e09bd86        expose:v1           "nginx -g 'daemon of…"   46 seconds ago      Up 45 seconds       0.0.0.0:32768->80/tcp, 0.0.0.0:32768->80/udp   expose-inst-Publish
 

 

Lab #12: LABEL Instruction

You can add labels to your image to help organize images by project, record licensing information, to aid in automation, or for other reasons. For each label, add a line beginning with LABEL and with one or more key-value pairs. The following examples show the different acceptable formats.
Docker offers support to add labels into images as a way to add custom metadata on them.
The label syntax on your Dockerfile is as follows:

LABEL = = = ...

The LABEL instruction adds metadata to an image. A LABEL is a key-value pair. To include spaces within a LABEL value, use quotes and backslashes as you would in command-line parsing. A few usage examples:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

An image can have more than one label. You can specify multiple labels on a single line. Prior to Docker 1.10, this decreased the size of the final image, but this is no longer the case. You may still choose to specify multiple labels in a single instruction, in one of the following two ways:

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

Labels included in base or parent images (images in the FROM line) are inherited by your image. If a label already exists but with a different value, the most-recently-applied value overrides any previously-set value.

To view an image’s labels, use the docker inspect command.

"Labels": {
    "com.example.vendor": "ACME Incorporated"
    "com.example.label-with-value": "foo",
    "version": "1.0",
    "description": "This text illustrates that label-values can span multiple lines.",
    "multi.label1": "value1",
    "multi.label2": "value2",
    "other": "value3"
},

 

Lab #13: ONBUILD Making wedding clothes for others

Format: ONBUILD .

ONBUILD is a special instruction, followed by other instructions, such as RUNCOPY, etc., and these instructions will not be executed when the current image is built. Only when the current image is mirrored, the next level of mirroring will be executed.

The other instructions in Dockerfile are prepared to customize the current image. Only ONBUILD is prepared to help others customize themselves.

Suppose we want to make an image of the application written by Node.js. We all know that Node.js uses npm for package management, and all dependencies, configuration, startup information, etc. are placed in the package.json file. After getting the program code, you need to do npm install first to get all the required dependencies. Then you can start the app with npm start. Therefore, in general, Dockerfile will be written like this:

FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]

Put this Dockerfile in the root directory of the Node.js project, and after building the image, you can use it to start the container. But what if we have a second Node.js project? Ok, then copy this Dockerfile to the second project. If there is a third project? Copy it again? The more copies of a file, the more difficult it is to have version control, so let’s continue to look at the maintenance of such scenarios.

If the first Node.js project is in development, I find that there is a problem in this Dockerfile, such as typing a typo, or installing an extra package, then the developer fixes the Dockerfile, builds it again, and solves the problem. The first project is ok, but the second one? Although the original Dockerfile was copied and pasted from the first project, it will not fix their Dockerfile because the first project, and the Dockerfile of the second project will be automatically fixed.

So can we make a base image, and then use the base image for each project? In this way, the basic image is updated, and each project does not need to synchronize the changes of Dockerfile. After rebuilding, it inherits the update of the base image. Ok, yes, let’s see the result. Then the above Dockerfile will become:

FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]

Here we take out the project-related build instructions and put them in the subproject. Assuming that the name of the base image is my-node, the own Dockerfile in each project becomes:

FROM my-node

Yes, there is only one such line. When constructing a mirror with this one-line Dockerfile in each project directory, the three lines of the previous base image ONBUILD will start executing, successfully copy the current project code into the image, and execute for this project. npm install, generate an application image.

Lab

# Dockerfile
FROM busybox
ONBUILD RUN echo "You won't see me until later"

Docker build

docker build -t me/no_echo_here .

Uploading context  2.56 kB
Uploading context
Step 0 : FROM busybox
Pulling repository busybox
769b9341d937: Download complete
511136ea3c5a: Download complete
bf747efa0e2f: Download complete
48e5f45168b9: Download complete
 ---> 769b9341d937
Step 1 : ONBUILD RUN echo "You won't see me until later"
 ---> Running in 6bf1e8f65f00
 ---> f864c417cc99
Successfully built f864c417cc9

Here the ONBUILD instruction is read, not run, but stored for later use.

# Dockerfile
FROM me/no_echo_here

docker build -t me/echo_here . Uploading context 2.56 kB Uploading context Step 0 : FROM cpuguy83/no_echo_here

Executing 1 build triggers

Step onbuild-0 : RUN echo "You won't see me until later"
 ---> Running in ebfede7e39c8
You won't see me until later
 ---> ca6f025712d4
 ---> ca6f025712d4
Successfully built ca6f025712d4

Ubutu Rails

FROM ubuntu:12.04

RUN apt-get update -qq && apt-get install -y ca-certificates sudo curl git-core
RUN rm /bin/sh && ln -s /bin/bash /bin/sh

RUN locale-gen  en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8
ENV LC_ALL en_US.UTF-8

RUN curl -L https://get.rvm.io | bash -s stable
ENV PATH /usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN /bin/bash -l -c rvm requirements
RUN source /usr/local/rvm/scripts/rvm && rvm install ruby
RUN rvm all do gem install bundler

ONBUILD ADD . /opt/rails_demo
ONBUILD WORKDIR /opt/rails_demo
ONBUILD RUN rvm all do bundle install
ONBUILD CMD rvm all do bundle exec rails server

This Dockerfile is doing some initial setup of a base image. Installs Ruby and bundler. Pretty typical stuff. At the end are the ONBUILD instructions.

ONBUILD ADD . /opt/rails_demo Tells any child image to add everything in the current directory to /opt/railsdemo. Remember, this only gets run from a child image, that is when another image uses this one as a base (or FROM). And it just so happens if you look in the repo I have a skeleton rails app in railsdemo that has it’s own Dockerfile in it, we’ll take a look at this later.

ONBUILD WORKDIR /opt/rails_demo Tells any child image to set the working directory to /opt/rails_demo, which is where we told ADD to put any project files

ONBUILD RUN rvm all do bundle install Tells any child image to have bundler install all gem dependencies, because we are assuming a Rails app here.

ONBUILD CMD rvm all do bundle exec rails server Tells any child image to set the CMD to start the rails server

Ok, so let’s see this image build, go ahead and do this for yourself so you can see the output.

git clone gi*@gi****.com:sangam14/docker_onbuild.git 
cd docker_onbuild
docker build -t sangam14/docker_onbuild .

Step 0 : FROM ubuntu:12.04
 ---> 9cd978db300e
Step 1 : RUN apt-get update -qq && apt-get install -y ca-certificates sudo curl git-core
 ---> Running in b32a089b7d2d
# output supressed
ldconfig deferred processing now taking place
 ---> d3fdefaed447
Step 2 : RUN rm /bin/sh && ln -s /bin/bash /bin/sh
 ---> Running in f218cafc54d7
 ---> 21a59f8613e1
Step 3 : RUN locale-gen  en_US.UTF-8
 ---> Running in 0fcd7672ddd5
Generating locales...
done
Generation complete.
 ---> aa1074531047
Step 4 : ENV LANG en_US.UTF-8
 ---> Running in dcf936d57f38
 ---> b9326a787f78
Step 5 : ENV LANGUAGE en_US.UTF-8
 ---> Running in 2133c36335f5
 ---> 3382c53f7f40
Step 6 : ENV LC_ALL en_US.UTF-8
 ---> Running in 83f353aba4c8
 ---> f849fc6bd0cd
Step 7 : RUN curl -L https://get.rvm.io | bash -s stable
 ---> Running in b53cc257d59c
# output supressed
---> 482a9f7ac656
Step 8 : ENV PATH /usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
 ---> Running in c4666b639c70
 ---> b5d5c3e25730
Step 9 : RUN /bin/bash -l -c rvm requirements
 ---> Running in 91469dbc25a6
# output supressed
Step 10 : RUN source /usr/local/rvm/scripts/rvm && rvm install ruby
 ---> Running in cb4cdfcda68f
# output supressed
Step 11 : RUN rvm all do gem install bundler
 ---> Running in 9571104b3b65
Successfully installed bundler-1.5.3
Parsing documentation for bundler-1.5.3
Installing ri documentation for bundler-1.5.3
Done installing documentation for bundler after 3 seconds
1 gem installed
 ---> e2ea33486d62
Step 12 : ONBUILD ADD . /opt/rails_demo
 ---> Running in 5bef85f266a4
 ---> 4082e2a71c7e
Step 13 : ONBUILD WORKDIR /opt/rails_demo
 ---> Running in be1a06c7f9ab
 ---> 23bec71dce21
Step 14 : ONBUILD RUN rvm all do bundle install
 ---> Running in 991da8dc7f61
 ---> 1547bef18de8
Step 15 : ONBUILD CMD rvm all do bundle exec rails server
 ---> Running in c49139e13a0c
 ---> 23c388fb84c1
Successfully built 23c388fb84c1

 

 

Lab #14: Create a Docker Image with HEALTHCHECK instruction

The HEALTHCHECK directive tells Docker how to determine if the state of the container is normal. This was a new directive introduced during Docker 1.12. Before the HEALTHCHECK directive, the Docker engine can only determine if the container is in a state of abnormality by whether the main process in the container exits. In many cases, this is fine, but if the program enters a deadlock state, or an infinite loop state, the application process does not exit, but the container is no longer able to provide services. Prior to 1.12, Docker did not detect this state of the container and would not reschedule it, causing some containers to be unable to serve, but still accepting user requests.

The syntax look like:

HEALTHCHECK [options] CMD :

The above syntax set the command to check the health of the container

How does it work?

When a HEALTHCHECK instruction is specified in an image, the container is started with it, the initial state will be starting, and will become healthy after the HEALTHCHECK instruction is checked successfully. If it fails for a certain number of times, it will become unhealthy.

What options does HEALTHCHECK support?

--interval=: interval between two health checks, the default is 30 seconds; --timeout=: The health check command runs the timeout period. If this time is exceeded, the health check is regarded as a failure. The default is 30 seconds. --retries=: When the specified number of consecutive failures, the container status is treated as unhealthy, the default is 3 times. Like CMD, ENTRYPOINT, HEALTHCHECK can only appear once. If more than one is written, only the last one will take effect.

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

Assignment:

  • Writing a Dockerfile with HEALTHCHECK instruction
  • Build a Docker Image
  • Check that the nginx config file exists
  • Check if nginx is healthy
  • Make Docker container Unhealthy and check
  • Create the nginx.conf file and Making the container go healthy

Writing a Dockerfile with HEALTHCHECK instruction

Suppose we have a simple Web service. We want to add a health check to determine if its Web service is working. We can use curl to help determine the HEALTHCHECK of its Dockerfile:

FROM nginx:1.13
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost/ || exit 1
EXPOSE 80
Here we set a check every 3 seconds (here the interval is very short for the test, it should be relatively long), if the health check command does not respond for more than 3 seconds, it is considered a failure, and use curl -fs http://localhost/   exit 1 As a health check command.

Building Docker Image

docker image build -t nginx:1.13 .

Check that the nginx config file exists

docker run --name=nginx-proxy -d \
        --health-cmd='stat /etc/nginx/nginx.conf || exit 1' \
        nginx:1.13

Check if nginx is healthy

docker inspect --format='' nginx-proxy

Make Docker container Unhealthy and check

docker exec nginx-proxy rm /etc/nginx/nginx.conf

Check if nginx is healthy

sleep 5; docker inspect --format='' nginx-proxy

Creating the nginx.conf file and Making the container go healthy

docker exec nginx-proxy touch /etc/nginx/nginx.conf
sleep 5; docker inspect --format='' nginx-proxy
healthy

Lab #15: Create an image with SHELL instruction

Pre-requisite:

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

1- Install Docker latest version.
2- Shell instruction syntax

SHELL ["executable", "parameters"]
  • The SHELL instruction allows the default shell used for the shell form of commands to be overridden. The default shell on Linux is ["/bin/sh", "-c"], and on Windows is ["cmd", "/S", "/C"]. The SHELL instruction must be written in JSON form in a Dockerfile.

  • The SHELL instruction is particularly useful on Windows where there are two commonly used and quite different native shells: cmd and powershell, as well as alternate shells available including sh.

  • The SHELL instruction can appear multiple times. Each SHELL instruction overrides all previous SHELL instructions, and affects all subsequent instructions.

3- Create the Dockerfile with instruction.

FROM windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello
  • The following instructions can be affected by the SHELL instruction when the shell form of them is used in a Dockerfile: RUN, CMD and ENTRYPOINT.

The following example is a common pattern found on Windows which can be streamlined by using the SHELL instruction:


RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

The command invoked by docker will be:


cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

  • This is inefficient for two reasons. First, there is an un-necessary cmd.exe command processor (aka shell) being invoked. Second, each RUN instruction in the shell form requires an extra powershell -command prefixing the command.

  • To make this more efficient, one of two mechanisms can be employed. One is to use the JSON form of the RUN command such as:


RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]

While the JSON form is unambiguous and does not use the un-necessary cmd.exe, it does require more verbosity through double-quoting and escaping. The alternate mechanism is to use the SHELL instruction and the shell form, making a more natural syntax for Windows users, especially when combined with the escape parser directive:

# escape=`

FROM windowsservercore
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

  1. output
PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 3.584 kB
Step 1 : FROM windowsservercore
 ---> 5bc36a335344
Step 2 : SHELL powershell -command
 ---> Running in 87d7a64c9751
 ---> 4327358436c1
Removing intermediate container 87d7a64c9751
Step 3 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in 3e6ba16b8df9


Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         6/2/2016   2:59 PM                Example


 ---> 1f1dfdcec085
Removing intermediate container 3e6ba16b8df9
Step 4 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> 6770b4c17f29
Removing intermediate container b139e34291dc
Step 5 : RUN c:\example\Execute-MyCmdlet -sample 'hello world'
 ---> Running in abdcf50dfd1f
Hello from Execute-MyCmdlet.ps1 - passed hello world
 ---> ba0e25255fda
Removing intermediate container abdcf50dfd1f
Successfully built ba0e25255fda
PS E:\docker\build\shell>


  • The SHELL instruction could also be used to modify the way in which a shell operates. For example, using SHELL cmd /S /C /V:ON|OFF on Windows, delayed environment variable expansion semantics could be modified.
  • The SHELL instruction can also be used on Linux should an alternate shell be required such zsh, csh, tcsh and others.
  • The SHELL feature was added in Docker 1.12.

 

How is ENTRYPOINT instruction under Dockerfile different from RUN instruction?

Tested Infrastructure

Platform Number of Instance Reading Time
Play with Docker 1 5 min

Pre-requisite

  • Create an account with DockerHub
  • Open PWD Platform on your browser
  • Click on Add New Instance on the left side of the screen to bring up Alpine OS instance on the right side

What is ENTRYPOINT meant for?

ENTRYPOINT is meant to provide the executable while CMD is to pass the default arguments to the executable. To understand it clearly, let us consider the below Dockerfile:

My Image

If you try building this Docker image using docker build command –

My Image

Let us run this image without any argument.

My Image

Let’s run it passing a command line argument

My Image

This clearly state that ENTRYPOINT is meant to provide the executable while CMD is to pass the default arguments to the executable.

 

References:

 Lab

Have Queries? Join https://launchpass.com/collabnix

Ajeet Raina Docker Captain, ARM Innovator & Docker Bangalore Community Leader.

68 Replies to “What is a Dockerfile – A Step-By-Step Guide”

  1. It’s a shame you don’t have a donate button! I’d certainly donate to this brilliant blog! I suppose for now i’ll settle for bookmarking and adding your RSS feed to my Google account. I look forward to brand new updates and will talk about this website with my Facebook group. Chat soon!

  2. It is appropriate time to make some plans for the future and it is time to be happy. I have read this post and if I could I desire to suggest you few interesting things or advice. Maybe you can write next articles referring to this article. I wish to read more things about it!

  3. Whats up very nice blog!! Guy .. Beautiful .. Wonderful .. I will bookmark your blog and take the feeds alsoKI’m satisfied to search out a lot of helpful info here within the submit, we’d like develop extra strategies in this regard, thanks for sharing. . . . . .

  4. Very good website you have here but I was curious if you knew of any forums that cover the same topics discussed here? I’d really like to be a part of online community where I can get suggestions from other experienced individuals that share the same interest. If you have any recommendations, please let me know. Thank you!

  5. This web page is known as a stroll-by way of for the entire data you wanted about this and didn’t know who to ask. Glimpse right here, and you’ll undoubtedly uncover it.

  6. Nice post. I learn something more challenging on different blogs everyday. It will always be stimulating to read content from other writers and practice a little something from their store. I’d prefer to use some with the content on my blog whether you don’t mind. Natually I’ll give you a link on your web blog. Thanks for sharing.

  7. It’s a shame you don’t have a donate button! I’d without a doubt donate to this fantastic blog! I guess for now i’ll settle for book-marking and adding your RSS feed to my Google account. I look forward to fresh updates and will share this blog with my Facebook group. Talk soon!

  8. That is the precise weblog for anyone who wants to seek out out about this topic. You realize so much its almost laborious to argue with you (not that I actually would want…HaHa). You undoubtedly put a new spin on a subject thats been written about for years. Great stuff, simply great!

  9. Good post. I learn one thing tougher on completely different blogs everyday. It should at all times be stimulating to read content material from different writers and practice a bit one thing from their store. I’d want to make use of some with the content on my blog whether you don’t mind. Natually I’ll offer you a hyperlink in your internet blog. Thanks for sharing.

  10. My wife and i felt so glad Ervin managed to finish off his inquiry while using the precious recommendations he came across from your web pages. It’s not at all simplistic to simply continually be freely giving ideas many others could have been making money from. We really recognize we need the blog owner to give thanks to for this. The entire illustrations you have made, the straightforward blog navigation, the relationships you will give support to engender – it is all superb, and it’s facilitating our son and us reckon that that issue is satisfying, and that is wonderfully essential. Many thanks for all the pieces!

  11. I¦ve been exploring for a bit for any high quality articles or weblog posts in this kind of space . Exploring in Yahoo I eventually stumbled upon this web site. Reading this information So i¦m happy to exhibit that I’ve an incredibly just right uncanny feeling I came upon exactly what I needed. I most unquestionably will make sure to do not overlook this website and provides it a look regularly.

  12. Heya i’m for the first time here. I came across this board and I find It truly useful & it helped me out much. I hope to give something back and aid others like you aided me.

  13. I like what you guys are up also. Such intelligent work and reporting! Carry on the excellent works guys I’ve incorporated you guys to my blogroll. I think it will improve the value of my site :).

  14. Good day! I know this is kind of off topic but I was wondering if you knew where I could get a captcha plugin for my comment form? I’m using the same blog platform as yours and I’m having problems finding one? Thanks a lot!

  15. I’ve been surfing on-line greater than three hours lately, yet I by no means found any attention-grabbing article like yours. It is beautiful worth sufficient for me. Personally, if all site owners and bloggers made excellent content as you probably did, the web will probably be much more helpful than ever before.

  16. Thanks for sharing excellent informations. Your website is so cool. I am impressed by the details that you?¦ve on this blog. It reveals how nicely you perceive this subject. Bookmarked this website page, will come back for extra articles. You, my friend, ROCK! I found just the info I already searched everywhere and simply couldn’t come across. What a perfect site.

  17. За всю историю существования человека питание всегда было и остается существенным фактором, который осуществляет постоянное воздействие на здоровье.

    Как похудеть без диеты: простые принципы раздельного питания

    В раздельного питания много плюсов и еще больше поклонников. Главный плюс – не соблюдая диет, потребляя что хочешь и в любых количествах, можно улучшить здоровье и похудеть на один-два размера.

    Все наверняка слышали о раздельном питании

    Все наверняка слышали о раздельном питании. Продукты имеют состав разной полезности и, соответственно, разную калорийность.

    Главные правила системы раздельного питания

    Для переваривания различных видов продуктов в желудочно-кишечном тракте вырабатываются определенные пищеварительные соки, начиная полостью рта и заканчивая кишечником. Все виды пищи перерабатываются в определенных отделах пищеварительного тракта в течение определенного времени.

    Важная роль в обеспечении высокого уровня здоровья

    Важная роль в обеспечении высокого уровня здоровья, увеличение продолжительности жизни, сохранении трудоспособности человека принадлежит питании. Оно должно быть рациональным (разумным). Рациональное питание – это физиологическое полноценное питание с учетом особенностей каждого человека, которое обеспечивает постоянное состояние внутренней среды организма, поддерживает его жизненные проявления, (рост, развитие, деятельность различных органов и систем), способствует укреплению здоровья, повышению сопротивляемости организма человека инфекциям.

    Универсальных рациональных режимов питания не существует. Для каждого человека оно специфично. При этом учитываются индивидуальные особенности обмена веществ, пол, возраст, характер труда.

    Привычки и игнорирование научных рекомендаций при составлении дневного рациона провоцируют во многих случаях развитие тяжелых заболеваний.

    Рациональное питание основано на таких законах: энергетическая ценность – это

    И. Соблюдение равновесия между энергией, поступающей с пищей и энергетическими затратами организма. Пользуются для измерения энергетической ценности пищи – калориями, а работы организма (затрат) – джоулями. Даже в условиях покоя и в комфортных температурных условиях уровень энергетических затрат взрослого человека составляет 1300-1900 ккал. (1 ккал * 80 кг. * 24 час.)

    Всякая физическая или умственная работа требует дополнительных затрат энергии. Если у людей, занятых малоподвижной, “сидячей” работой, суточная потребность в энергии составляет 2500-2800 ккал., То у лиц занятых тяжелым физическим трудом эти величины достигают 4000-5000 ккал. (Таб)

    designvtomske. ru 2012 Здоровое питание

    Источник: http://remontkvartir63.tk/http://remontkvartir63.tk/

  18. Great work! That is the kind of info that are meant to be shared around the net. Shame on Google for now not positioning this post higher! Come on over and discuss with my website . Thanks =)

  19. I will immediately grasp your rss feed as I can not find your e-mail subscription hyperlink or newsletter service. Do you’ve any? Kindly permit me understand in order that I may just subscribe. Thanks.

  20. Hi there, i read your blog from time to time and i own a similar one and i was just curious if you get a lot of spam responses? If so how do you protect against it, any plugin or anything you can recommend? I get so much lately it’s driving me insane so any assistance is very much appreciated.

  21. I not to mention my pals were going through the nice things found on the website while suddenly came up with a horrible suspicion I had not expressed respect to you for those techniques. All of the young boys had been consequently very interested to learn them and now have undoubtedly been tapping into those things. Appreciate your actually being simply thoughtful as well as for choosing varieties of magnificent areas most people are really wanting to learn about. Our own sincere apologies for not expressing appreciation to sooner.

  22. whoah this blog is excellent i love reading your articles. Keep up the good work! You know, many people are hunting around for this information, you could help them greatly.

  23. I love your blog.. very nice colors & theme. Did you create this website yourself? Plz reply back as I’m looking to create my own blog and would like to know wheere u got this from. thanks

  24. Hey are using WordPress for your site platform? I’m new to the blog world but I’m trying to get started and create my own. Do you require any coding expertise to make your own blog? Any help would be really appreciated!

  25. hi!,I like your writing very much! share we communicate more about your article on AOL? I require an expert on this area to solve my problem. Maybe that’s you! Looking forward to see you.

  26. Thank you for the sensible critique. Me & my neighbor were just preparing to do some research on this. We got a grab a book from our area library but I think I learned more clear from this post. I’m very glad to see such magnificent info being shared freely out there.

  27. Woah! I’m really loving the template/theme of this blog. It’s simple, yet effective. A lot of times it’s challenging to get that “perfect balance” between usability and appearance. I must say that you’ve done a amazing job with this. Also, the blog loads super quick for me on Opera. Excellent Blog!

  28. I am really loving the theme/design of your site. Do you ever run into any internet browser compatibility issues? A couple of my blog visitors have complained about my blog not working correctly in Explorer but looks great in Firefox. Do you have any tips to help fix this problem?

  29. I have been exploring for a little for any high-quality articles or weblog posts on this sort of space . Exploring in Yahoo I eventually stumbled upon this website. Reading this info So i am glad to exhibit that I’ve a very excellent uncanny feeling I found out just what I needed. I most for sure will make sure to don’t put out of your mind this site and provides it a glance on a continuing basis.

  30. It?s really a nice and helpful piece of info. I?m glad that you shared this helpful info with us. Please keep us up to date like this. Thanks for sharing.

  31. This design is incredible! You obviously know how to keep a reader entertained. Between your wit and your videos, I was almost moved to start my own blog (well, almost…HaHa!) Fantastic job. I really loved what you had to say, and more than that, how you presented it. Too cool!

  32. Undeniably believe that which you said. Your favorite reason seemed to be on the net the easiest thing to be aware of. I say to you, I certainly get annoyed while people think about worries that they just do not know about. You managed to hit the nail upon the top and defined out the whole thing without having side effect , people could take a signal. Will probably be back to get more. Thanks

  33. I found your weblog site on google and examine just a few of your early posts. Continue to keep up the excellent operate. I simply further up your RSS feed to my MSN Information Reader. Seeking forward to studying more from you in a while!…

  34. What i don’t realize is if truth be told how you’re no longer really a lot more smartly-liked than you may be right now. You are so intelligent. You know thus considerably relating to this matter, produced me in my opinion imagine it from a lot of various angles. Its like men and women don’t seem to be interested until it’s one thing to accomplish with Lady gaga! Your personal stuffs nice. All the time take care of it up!

  35. Heya i?m for the first time here. I came across this board and I find It really useful & it helped me out much. I hope to give something back and help others like you helped me.

  36. I want to express my thanks to you just for rescuing me from this type of scenario. Just after surfing throughout the online world and coming across strategies which were not beneficial, I assumed my life was gone. Living minus the approaches to the issues you have sorted out by means of this write-up is a serious case, and the kind which might have negatively affected my entire career if I hadn’t come across your web blog. Your personal ability and kindness in handling the whole lot was vital. I’m not sure what I would’ve done if I had not encountered such a stuff like this. I can now look forward to my future. Thanks a lot very much for this skilled and sensible guide. I will not be reluctant to endorse the blog to any individual who needs and wants guide about this subject matter.

  37. Howdy just wanted to give you a quick heads up and let you know a few of the pictures aren’t loading correctly. I’m not sure why but I think its a linking issue. I’ve tried it in two different browsers and both show the same results.

  38. I love your blog.. very nice colors & theme. Did you create this website yourself? Plz reply back as I’m looking to create my own blog and would like to know wheere u got this from. thanks

Leave a Reply to Anonymous Cancel reply

Your email address will not be published.

© Copyright Collabnix Inc

Built for Collabnix Community, by Community