Back on December 10th, I launched by first book, Docker for Developers, on Leanpub. One of the things that I kind of glossed over, mostly because it wasn't the focus of the book, was at the the beginning of the "Containerizing Your Application" chapter. It was this:
Modern PHP applications do not generally tote around their vendor/ directory and instead rely on Composer to do our dependency injection. Let’s pull down the dependencies for the project.
$ docker run --rm -u $UID -v `pwd`:/app composer/composer install
This first initial run will download the image as you probably do not have this composer/composer image installed. This container will mount our project code, parse the composer.lock, and install our dependencies just like if we ran composer locally. The only difference is we wrapped the command in a docker container which we know has PHP and all the correct libraries pre-installed to run composer.
There's something very powerful in there that I'm not sure many people take away from the book. I spend most of my time showing how Docker is used and how to get your application into it, and the book answers the question which many people have at the outset - how do I get my application into Docker?
One thing many people overlook is that Docker is not just a container for servers or other long-running apps, but it is a container for any command you want to run. When you get down to it, that is all Docker is doing, just running a single command (well, if done the Docker way). Most people just focus on long running executables like servers.
Any sort of binary can generally be pushed into a container and since Docker can mount your host file system you can start to containerize any binary executable. In the Composer command above I've gotten away from having a dedicated Composer command, or even phar, on my development machines and just use the Dockerized version.
Why?
Less maintenance and thinking.
Docker has become a standard part of my everyday workflow now even if the project I'm working on isn't running inside of a Docker container. I no longer have to install anything more than Docker to get my development tools I need. Let's take Composer for example.
Putting Composer in a Container
Taking a look at Composer, it is just a phar file that can be downloaded from the internet. It requires PHP with a few extensions installed.
Let's make a basic Dockerfile and see how that works:
FROM php:7
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENTRYPOINT ["composer"]
CMD ["--version"]
We can then build it with the following:
docker build -t composer .
I should then be able to run the following and get the Composer version:
docker run -ti --rm composer
Great! There's a problem though. Go ahead and try to install a few things, and eventually you'll get an error stating that
the zip extension isn't installed. We need to install and enable it through the docker-php-ext-*
commands available in
the base image. It has some dependencies so we will install those through apt
as well.
FROM php:7
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng12-dev \
libbz2-dev \
php-pear \
curl \
git \
subversion \
&& rm -r /var/lib/apt/lists/*
RUN docker-php-ext-install zip
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENTRYPOINT ["composer"]
CMD ["--version"]
Now rebuild the image and try again. It will probably work. You won't have a vendor directory, but the command won't fail anymore. We need to mount our directory inside of the container, which brings us back to the original command:
docker run --rm -u $UID -v `pwd`:/app composer/composer install
That is a lot of stuff to type out, especially compared to just composer
. Through the beauty of most CLI-based operating
systems you can create Aliases though. Aliases allow you to type short commands that are expanded out into much longer
commands. In my ~/.zshrc
file (though you might have a ~/.bashrc
or ~/.profile
or something similar) we can create
a new alias:
alias composer="docker run --rm -u $UID -v $PWD:/app composer"
Now I can simply type composer
anywhere from the command line and my composer
image will kick up.
A better version can be found in the Dockerfile for the PHP base image of composer/composer on Github, which I based the above on. In fact, I don't build my own Composer image, I use the existing one at https://hub.docker.com/r/composer/composer/ since I don't have to maintain it.
It isn't just PHP stuff
Earlier today I sent out the following tweet after getting frustrated with running Grunt inside of Virtualbox.
Next project - containerizing grunt and bower, because it takes too damn long for file changes to propagate into virtualbox
— Rogue PHP Engine (@dragonmantank) December 22, 2015
It is a pain because some of Grunt's functionality relies on the filesystem notifying that a file has changed, and when Grunt runs inside of a virtual machine and is watching a mounted folder (be it NFS or anything else other than rsync) it can take up to 30 seconds for the notify signal to bubble up. That makes some slow development.
I hate polluting my work machine with development tools. I had a few people say they would love having Grunt and Bower inside of a Docker container, so I did just that.
I created a new container called dragonmantank/nodejs-grunt-bower and pushed it up as a public repository on the Docker Hub.
Since these images are pre-built I don't have to worry about any dependencies they might need, and setting up a new machine for these tools now is down to installing Docker (which is going to happen for me anyway) and setting up the following aliases:
alias composer="docker run --rm -u $UID -v $PWD:/app composer/composer"
alias node="docker run -ti --rm -u $UID -v `pwd`:/data dragonmantank/nodejs-grunt-bower node"
alias grunt="docker run -ti --rm -u $UID -v `pwd`:/data dragonmantank/nodejs-grunt-bower grunt"
alias npm="docker run -ti --rm -u $UID -v `pwd`:/data dragonmantank/nodejs-grunt-bower npm"
alias bower="docker run -ti --rm -u $UID -v `pwd`:/data dragonmantank/nodejs-grunt-bower bower"
The first time I run one of the commands the image is automatically downloaded so I don't even have to do anything other than just run the command I want.
Start Thinking about Dockerizing Commands
Don't think that Docker is only about running servers or daemons. Any binary can generally be put inside of a container, and you might as well make your life easier by making your tools easier to install and maintain.