This is the first post in the series of posts exploring the ins and outs of using docker w/ elastic beanstalk as part of a continuous deployment strategy. As usual, the code in this example is in github.
Docker is an excellent tool for isolating and scaling web services. Read the Docker Overview if you are unfamiliar with containerization concepts. The most important concept is that containers are ephemeral. They come and go as needed to perform tasks.
It is possible to deploy docker containers via elastic beanstalk but the documentation is a bit daunting so we'll work through it step by step.
You will need to install the Docker Toolbox on your development machine and launch the Docker Quickstart Terminal.
This puts you in a shell that has all the docker tools.
First we need to define the contents and running context of our webapp container using a
Dockerfile. Here's some things to keep in mind when designing a container.
For our example we have a loopback web app that we want to containerize for deployment to elastic beanstalk. The Dockerfile defines the dependancies, copies needed files into the container and defines how the service within the container is started and what port to expose the service on. In this example we are using supervisord to start the app.
# install packages FROM ubuntu RUN apt-get update RUN apt-get install -y supervisor RUN apt-get install -y curl RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - RUN apt-get install -y nodejs RUN apt-get install -y git RUN apt-get install -y imagemagick # copy app into container ADD assets /var/app/current/assets ADD client /var/app/current/client ADD common /var/app/current/common ADD docker-assets/webapp /var/app/current/docker-assets/webapp ADD server /var/app/current/server ADD tests /var/app/current/tests ADD working /var/app/current/working ADD gruntfile.js /var/app/current/gruntfile.js ADD package.json /var/app/current/package.json # set up supervisord RUN cd /var/app/current; cp docker-assets/webapp/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # To run npm install or not to run npm install, that is the question. # # in this case it is not needed so just copy the entire node_modules # directory to the container so it exactly matches the development # environment # ADD node_modules /var/app/current/node_modules # If there were os specific npm modules we could run npm install # but that comes with some thorny issues - you could get different # versions of packages running inside the container than are # running in your development environment. This is an issue to # consider when controlling you continuous deployment strategy. # Where possible I like npm to be a function of the development # environment keeping deployment out of module version hell. # # RUN cd /var/app/current; npm install # expose webapp port EXPOSE 3000 WORKDIR "/var/app/current" CMD ["/usr/bin/supervisord"]
docker build -t webapp . to build the container. What that actually does is make an 'image' which the container will be based on. See docs
Power up the container shell and poke around:
docker run --name webapp --rm -p 3000:3000 -it --env-file ./localdev.env --entrypoint /bin/bash webapp
--env-file ./localdev.env As usual, our web app needs some environment variables to run. More info on that in the keeping secrets post. In short - that file contains the keys the app needs to run.
ls / It looks like a ubuntu box (because that was specified in the first line of the Dockerfile) and all the files we copied to it are in
exit when you are done and the container will shut down.
--rm option - this makes the container ephemeral so that it is deleted after running. I find this the best option for development environments. If you omit that option the container will still exist with status 'Exited'
docker ps -a lists all containers. Exited containers can be brought back from the dead by name in many docker commands. To clean up all exited containers use:
docker rm $(docker ps -q -f status=exited).
To start the webservice on port 3000 run:
docker run --name webapp -p 3000:3000 -dt --env-file ./localdev.env webapp
This starts the container and runs it in the background exposing the containers port as 3000. You can see what containers are running with
docker ps. To see the details about the running container use
docker inspect webapp
Note: Docker tools defines a virtual network so 'localhost' will not work to connect to the container. To find the ip address of the container use
Now you can access the webservice:
curl $(docker-machine ip):3000
Get a shell on a running container:
docker exec -it webapp sh
Shut down the container when you are done:
docker stop webapp
But remember - it's still there:
docker ps -a
To clean it up:
docker rm webapp
Now we have a container for the web app. In the next post we can figure out how to get that into a running Elastic Beanstalk cluster...
Continued in Connecting Services w/Docker Containers
Photo: ittoqqortoormiit, Eastern Greenland (2007)
Document version 1.0