I have made a start on the project I mentioned in my last post. I decided that no matter what approach I'm going to take in order to move this web site into the cloud, I'm certainly going to want to run the main application in a Docker container - so that's what I have been concentrating on this week.
It turns out that I've tried this before. There was an existing Github repo containing a Dockerfile
that was supposed to build an image for the application. It looks like I copied it from this blog post. I can't remember why I stopped working on that, but it's been pretty easy to get it up and running.
Here's the current version of the Dockerfile
:
FROM perl:latest
MAINTAINER Dave Cross dave@perlhacks.org
RUN apt-get install openssl libssl-dev libz-dev libexpat1-dev
RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm Carton Starman
RUN git clone http://github.com/davorg/succession.git
RUN cd succession && carton install --deployment
EXPOSE 1701
WORKDIR succession
CMD carton exec starman --port 1701 Succession/bin/app.psgi
Let's go through it in a some detail.
FROM perl:latest
Docker images tend to be built on top of other images. I've chosen an image which contains an up to date version of Perl.
MAINTAINER Dave Cross dave@perlhacks.org
When I'm happy with this image, I'll upload it to a public Docker hub. This line will allow people to get in touch with me about it.
RUN apt-get install openssl libssl-dev libz-dev libexpat1-dev
RUN
commands are run as the image is being built. Here we're loading some fairly standard libraries that underlie some of the Perl modules I'll be loading later. Actually, I'm not sure that these are necessary; they might well be included in the underlying image.
RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm Carton Starman
Here, I'm installing cpanm
and then using it to install Carton
(a mechanism for installing known versions of Perl modules) and Starman
(the software that we'll use to run our web service).
RUN git clone https://github.com/davorg/succession.git
On this line we clone the latest version of the Github repo that contains the actual code for the web service.
RUN cd succession && carton install --deployment
And here, we change into the newly-cloned subdirectory and use carton
to install all of the Perl modules that we need.
EXPOSE 1701
This line tells Docker that port 1701 on the container should be made available to the outside world.
WORKDIR succession
WORKDIR
sets the working directory that we use when the container starts up.
CMD carton exec starman --port 1701 Succession/bin/app.psgi
And finally, CMD
defines the command that is run when you start up a container using your new image. Here we're using a pretty standard command line that will start our Dancer application running under Starman.
Having created our Dockerfile
, we can turn it into a Docker image by running the docker build
command:
$ docker build -t succession .
The -t succession
gives the image a tag and the .
means "use the Dockerfile
in the current directory.
Once the command has finished, you'll have a new image. You can confirm that using the command docker image ls
.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
succession latest 6a6fbfcbebbc 2 hours ago 1.17GB
perl latest 9a41aacaefa5 11 days ago 891MB
But having a Docker image is only half the battle. We now need to create and run a container. And we can do this using docker run
. The naive approach would be something like this:
$ docker run succession
And that works - but it doesn't do anything useful. For many reasons.
Firstly, although we've asked Docker to expose port 1701 on the container (with the EXPOSE
command in the Dockerfile
) we haven't done anything useful with that port. We need to use the -p
option to docker run
in order to make that port visible on the host system. So our command becomes:
$ docker run -p 1701:1701 succession
This makes port 1701 on the container available as port 1701 on the host.
And at this point, I was actually able to connect to my Dancer application (on http://localhost:1701/) running on the container. It was an error message, sure, but at least the communication was working.
The error message was interesting. As we've seen, the Dockerfile
uses a Perl tool called Carton to ensure that all the correct libraries are installed. And that uses a file called cpanfile
to work out what libraries are needed. All that was happening was that my cpanfile
didn't have all of the required listed. I admit it took a few iterations to get that right!
There was one final problem. The application needs a database and nothing in the Dockerfile
sets up that database. That's ok as I don't want the database running in the same container. But the application needs to know where its database is. It works that out by looking at some environment variables and those variables weren't being set in the container. So I had to use the -e
option (multiple times) to pass the host's environment variables to the container. By this time, the docker run
command was looking a bit complicated, so I wrote a bash script to make my life easier.
docker run --name succession \
-e SUCC_DB_HOST \
-e SUCC_DB_NAME \
-e SUCC_DB_USER \
-e SUCC_DB_PASS \
-p 1701:1701 succession
You'll see I've also added --name
to give the container a name.
That looked a lot better. But I hadn't quite got to the bottom of all of the networking problems. The database host variable (SUCC_DB_HOST
) was set to localhost
. And on the container, that wasn't right. A quick Google and I discovered that the option --network="host"
would allow the container to see the network from the host's perspective, so I could just change the setting to 127.0.0.1
and, suddenly, it all started working.
And that's where I currently am. After just a few hours work over the last week or so, I now have a working containerised version of my web application. This makes me happy. I feel that this could well work.
So, over to any Docker experts that might be reading this. Is what I'm doing sensible or have I missed some obvious improvements? All advice is welcome.
Update: I've already had two people giving me some really useful updates to this. It seems that the Dockerfile
I borrowed heavily from wasn't really very good. I'll try and improve it over the next couple of days - in the meantime, please don't copy this version.