What is Docker
In short, Docker enables you to install and run software in isolated containers. It provides a way to layer images. For instance, you could have a PostgresSQL image which is based on an Ubuntu 14.04 image. Containers can talk to each other via internal or external ports. This means, you could have two containers, one for the database and one for your application, both running on the same machine but not seeing each other except through the ports you expose.
Application-level virtualization is way more efficient than machine-level virtualization like VMware and OpenBox do it. IBM published a paper this year confirming this. The following slide visualizes this fact pretty good:
For a full introduction, please watch this excellent intro video:
- Image: Images are typically built through a
Dockerfileand can be layered and reused.
- Container: Containers are started images. Thus, when started, they contain all file which exist in the image but can create or delete files during runtime.
Think of images as an immutable DVD with application on it, and the container is the running application in memory. Something like that.
How we use docker
For instance, this is a
fig.yml file we use in a project:
db: image: orchardup/postgresql ports: - "5432" web: image: rweng/ini-rub-de-web command: /bin/bash -l -c "deploy/start_services.sh" volumes: - .:/var/www - ./log:/var/volume1/log ports: - "80:80" links: - db environment: RAILS_ENV: INI_DB_USERNAME: docker INI_DB_PASSWORD: docker test: image: rweng/ini-rub-de-web command: /bin/bash -l -c "deploy/test_setup.sh" volumes: - .:/var/www - /var/www/public/uploads links: - db environment: RAILS_ENV: test INI_DB_USERNAME: docker INI_DB_PASSWORD: docker
As you can see, we define three containers we use:
All three are based on a certain image.
If an image with this name exists, fig uses it to start a container of it.
If it does not exist, it looks at https://hub.docker.com/ if an image with this name has been published there.
It is also possible to use
build: ./ instead of
In this case, fig will build an image based on the
Dockerfile in the given directory, in the case above
We like to have one image, built once and used everywhere, to ensure the same foundation on all machines.
Thus, we build our image with
docker build -t rweng/ini-rub-de-web ./ and then push it to the docker hub with
docker push rweng/ini-rub-de-web.
Since we run
bundle install when a container starts, rebuilding the image is only required when we add system packages like redis, or when so much time has passed that the base image is totally outdated so that starting a container of it (running
bundle install) takes too long.
This is how our Dockerfile looks like:
# # Dockerfile to install Rails Stack # # This file must be in the root of the rails app due to add command below # see https://github.com/dotcloud/docker/issues/2745 # # VERSION 2 - EDITION 1 FROM ubuntu:14.04 MAINTAINER Robin Wenglewski, firstname.lastname@example.org # install required packages RUN apt-get update && apt-get install -y \ curl \ git \ nginx \ libpq-dev \ imagemagick \ libmagickwand-dev \ nodejs \ vim-nox \ phantomjs # Setup nginx RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf RUN mkdir /etc/nginx/ssl ADD deploy/default /etc/nginx/sites-available/default # install rvm RUN curl -sSL https://get.rvm.io | bash -s stable RUN echo 'source /usr/local/rvm/scripts/rvm' >> /etc/bash.bashrc # install ruby and gems RUN /bin/bash -l -c "rvm install --default 2.0.0" RUN /bin/bash -l -c "gem install bundler" ADD . /var/www WORKDIR /var/www RUN /bin/bash -l -c bundle install
You can read about the way the Dockerfile works in the docker documentation.
As you can see in the
fig.yml above, we pass in the database credentials via environment variables.
config/database.yml uses these:
development: &DEFAULT adapter: postgresql encoding: unicode database: ini_development pool: 5 username: <%= ENV.fetch('INI_DB_USERNAME', 'root') %> password: <%= ENV.fetch('INI_DB_PASSWORD', '') %> host: <%= ENV.fetch('DB_1_PORT_5432_TCP_ADDR', 'localhost') %> port: <%= ENV.fetch('DB_1_PORT_5432_TCP_PORT', '5432') %>
fig automatically creates environment variables of exposed ports in other containers.
Thus, the following lines in our
fig.yml make the variables
DB_1_PORT_5432_TCP_PORT available in our
ports: - "5432"
Note that this is only internally exposed, so other containers can connect to port
5432 but not other machines.
To expose ports externally use a complete mapping as we've done with the
ports: - "80:80"
To start the
web container, we only have to run
fig up -d db web.
This pull the images for both containers from the docker hub, if they are not already present.
Thereafter, a container is started based on these images and the specified
command: is executed.
In out web container, this is
#!/usr/bin/env bash # this script is executed once the web container started echo "running start_services.sh in RAILS_ENV=$RAILS_ENV" bundle install cp config/database.sample.yml config/database.yml rake tmp:create rake tmp:clear rake db:create rake db:migrate rake db:seed if [ "$RAILS_ENV" = "production" ]; then rake assets:precompile else rm -rf public/assets fi echo "assets precompiled" bundle exec unicorn -c config/unicorn.rb -D echo "Started unicorn" echo "starting nginx, finshed" nginx
RAILS_ENV is passed into the
web container from the host because no value was provided.
Compared to the
test container, where we set it explicitly to
fig up test to let the test suite run through once, or
fig run test /bin/bash which gets us into the container where we can run
guard as we please.
Tools around Docker
Drone can test and build your application and even deploy it when built successfully. It is extremely easy to get started with drone since you basically only have to configure your environment.
Dokku is build on docker and provides a mini-version of Heroku. Once started, you can push your applications to your machine which run in isolated containers.
However, Dokku only runs on a single host. The founder of Dokku, who also also worked / works(?) on docker, then started Flynn.
Flynn is not yet production ready, but it already looks awesome. It basically consists of two layers:
- a cluster layer which enables you to scale and distribute your applications
- multiple, independent services that enable you to push/deploy your applications, provide an API to manage Flynn, build and use heroku-like slugs to run your application, etc
In summary, Flynn is on a great path and I am looking forward to when it is production ready.
- Getting Started with Docker
- From zero to fully working CI server in less than 10 minutes with Drone & Docker
Our process is certainly not perfect yet. However, we are sure of the potential of the Docker ecosystem and are already profiting from it.
I'm looking forward to reading about your experiences with and impressions of Docker in the comments.