Docker and Dockerfiles Made Easy!

It’s become second nature for developers to use Virtual Machines to configure and manage their working environments. Most professionals who use VMs use Vagrant for dealing with their development environments. In this article, we’ll be moving from Vagrant to Docker, and use a small Laravel application to test that everything is working as expected.

Installation

The installation page contains instructions for almost every popular platform. Docker runs natively on Linux OS, but we can use it on Windows and Mac OS by installing the Docker Toolbox utility. If you run into some problems, you can visit this article which highlights the most common issues. You can verify it’s installed and working by running the following command in your terminal.

docker -v

# outputDocker version 1.8.1, build d12ea79

Docker Machines

--ADVERTISEMENT--

A Docker machine is the VM that holds your images and containers (more on those later). Let’s create our first VM.

You can print the machine’s configuration by running the docker-machine env docker-vm command. This is how you can switch between machines.

# Use the docker-vmeval"$(docker-machine env docker-vm)"# Switch to the dev machineeval"$(docker-machine env dev)"

You can read more about the docker-machine command in the documentation and explanation on why we’re using eval here can be found on this page.

Docker Images

Docker images are OS boxes that contain some pre-installed and configured software. You can browse the list of available images on the Docker Hub. In fact, you can create your own image based on another one and push it to the Docker hub so that other users can use it.

The docker images command lists the available images on your machine, and you can download a new one from the hub using the docker pull <image name> command. For our demo application, we pulled the mysql and nimmis/apache-php5 images.

Docker Containers

Docker containers are separate instances that we create from images. They can also make a good starting point for creating a new personalized image that others can use. You can see the list of available containers using the docker ps command. This only lists running containers, but you can list all available ones by adding the -a flag (docker ps -a).

Now, let’s create an Ubuntu container. First, we need to pull the image from the hub, then we create a new container instance from it.

The first command may take a while to finish downloading the image from the hub. The first flag (t) we specified on the second command means that we want to allocate a TTY to interact with the container, the second flag (i) means that we want an interactive STDIN/OUT, and the last one (d) means that we want to run it in the background.

Since this container will host our web server documents, you may be thinking, how are we going to access our server from the browser?

The -P option on the run command will automatically expose any ports needed from the container to the host machine, while the -p option lets you specify ports to expose from the container to the host.

Now you can access the container using the docker-machine ip docker-vm address and the specified port. If you don’t know the port, you can run the docker ps command and look at the ports column.

Container Volumes

Volumes are an easy way to share storage between your host machine and the container. They are initialized during the container’s creation and kept synced. In our case we want to mount /var/www to a local directory ~/Desktop/www/laravel_demo.

Note: The default Apache DocumentRoot directive points to /var/www/html, so you have to change it to /var/www/public inside the /etc/apache2/sites-enabled/000-default.conf configuration file and restart Apache. You can log into the container using the exec command.

docker exec -it <container> bash

# Restart Apache/etc/init.d/apache2 restart

Naming Containers

Even though you can use the container ID for most commands, it’s always a good idea to name the container for what it does or following a naming convention to avoid looking for the ID (using docker ps) every time you want to do something.

Now if you want to start, stop, or remove the container, you can use its name instead of the ID. Be sure to check the Docker documentation for more details about containers.

docker start wazo_server

Database Container

At this point, we have our Apache server set up and we will use a separate container to host our database(s). If you ever had to run two or more VMs at the same time, you know that your machine can get a bit slower, and it gets worse when you add more. Adding a separate lightweight container to host our databases and another one for managing background jobs will help a lot in keeping things lightweight, separate and manageable.

We create the MySQL container the same way we did above. We mount the /var/lib/mysql folder to a local one on our host machine to keep the data synced between the two machines. In case of data loss, we can mount the same local folder to another container.

The -e option lets you set an environment variable on the container creation. In this case, the MYSQL_ROOT_PASSWORD will tell the MySQL installation process to use the password we specified in the command.

Now if you want to connect to the database from outside, you can use the address returned from the docker-machine ip docker-vm command and the exposed port on the docker run command. My database configuration will look like the following.

You can test that things are working by running the Laravel database migration command, and make sure your mysql container is running.

./artisan migrate

Using Links

Links are a secure way to share connection details between containers through environment variables. The first method to link to a container is to expose it to a specific port and then use the credentials in your application.

The other way is to use Docker links. So, lets do the same thing we did above but using links this time.

The new option here is --link <container>:<env alias>. The environment alias will prefix your connection environment variables, in our case mysqldb. You can log into the web server’s container and run printenv | grep MYSQLDB to print all MySQL linking variables.

Running the ./artisan migrate command from the command line gives the following result.

Dockerfiles

Now that we learned how to work with Docker images, containers and how to link them together, we’re going to learn how to use Dockerfiles to create personalized images and Docker compose to manage our containers.

Dockerfiles help you build and share system images similar to Vagrant. We will highlight the main commands used to configure a Docker image. In this example, we’re going to build a Laravel 5 image. You can check out the final code on Github if you want to follow along.

FROM

The FROM instruction lets you choose the base image to start from. We can choose to start from a bare bones image like Ubuntu:14.04 or we can start from an image that already contains Apache/NGINX and PHP installed and configured. We will use the Ubuntu image for our example.

FROM ubuntu:14.04

MAINTAINER

Although this is an optional part, it is recommended to specify the image maintainer for reference.

MAINTAINER RAFIE Younes<younes.rafie@gmail.com>

ENV

The ENV instruction lets you define an environment variable that you can access through the setup process. In our case, we’ll use it to specify the Laravel version we want to install, and you can also change it and create a different version for Laravel 4.

ENV LARAVEL_VERSION ~5.1.0

RUN

The RUN instruction allows us to run commands on the shell (not bash). You can a run command like RUN apt-get update, but if you want to use bash, you should use it as the following RUN ["/bin/bash", "-c", "apt-get update"].

After installing Apache, PHP, and the other extensions, we’ll need to configure the virtualhost and specify our public folder. Let’s create a file called 000-default.conf containing the needed configuration.

After moving to /var/www we install Laravel and make the necessary folders writable by the Apache user www-data. If you noticed, we used the --no-interaction flag on Composer to avoid any installation questions.

EXPOSE

Because we’re creating small service containers, it makes sense that we expose those containers through a specific port to allow other services to consume it.

EXPOSE 80
EXPOSE 443

We expose our server through the standard HTTP port 80, and we also allow HTTPS connections through port 443.

CMD

The CMD instruction can be used to init the container. In our case, we’ll use it to run our Apache server in the background.

CMD ["/usr/sbin/apache2ctl","-D","FOREGROUND"]

Although our image is now ready for building, we’ll go through some other useful instructions.

ENTRYPOINT

The ENTRYPOINT instruction lets you specify a command to execute when the user runs the Docker image. We can use it to update our Composer dependencies.

// setup-laravel.sh
#!/bin/bash
cd /var/www/laravel
composer update

COPY setup-laravel.sh /setup-laravel.sh # Copy the local file to the image
RUN ["chmod","+x","/setup-laravel.sh"]# Make sure it's executable
ENTRYPOINT ["/setup-laravel.sh"]

You can go through all the available instructions in the documentation. In the next section, we’ll see how to use Docker Compose to manage our containers.

Docker Compose

If you’ve been following along from the first part of this post, we went through the installation process and we already have the environment ready. If not, you can check the documentation for more installation details.

We start by creating a docker-compose.yml file that will hold our configuration. Docker Compose uses YAML for configuration files, you can check the official documentation if you’re not familiar with it.
The first node defines the service name; this could be anything. In this case, it’s called web. The build child node specifies the image source, and you may choose an image from the Docker hub or a Dockerfile path to build the image from.

web:
build:.

The next part is to expose the container ports to make it accessible through the host machine.

web:
build:.
ports:-"80:80"-"443:443"

We talked about mounting volumes in the first part; we can specify that inside our docker-compose file as well.

You specify links in the form of <container name>:<env alias>. The last part is to create the mysqldb container.

mysqldb:
image: mysql
environment:- MYSQL_ROOT_PASSWORD=root

Notice that we used the environment to send the MYSQL_ROOT_PASSWORD to the image. You can use this option to make a generic Laravel image that can install any Laravel version.

If you already have an existing container that you want to link, you can use the external_links configuration.

Now, we are ready to build our image and create the containers to test that everything works as expected. Run the docker-compose up command inside the project folder to build the image and create the containers. This may take a while before everything is installed. You can see the running containers by running docker ps and the build image using docker images from the terminal.

Conclusion

This article was a brief introduction to moving from your usual Vagrant development environment to Docker, and how you can benefit from using lightweight containers instead of creating full VMs for small services.

Docker Compose is a new tool that’s still being developed and improved to remove the pain of managing and porting development environments. You can check the documentation for more details, and the Docker Compose YML file reference to see the list of supported properties. For general Docker docs, see here.

If you’ve already tried Docker and Docker Compose we would love to know what you think about them! If you have any questions or comments, please post them below!

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.