Docker is gaining some popularity in recent months. While I am not advocating any bandwagon-jumpings, the following Google Search Trend graph highly suggests that Docker has been solving many people’s problems: Docker vs Rails Docker vs Rails Google search trends

Here I attempt to write up a short intro to docker, and how you can use it in your development environment.

What is Docker

Docker.com defines itself as:

Docker containers wrap a piece of software in a complete filesystem that contains everything needed to run: code, runtime, system tools, system libraries – anything that can be installed on a server. This guarantees that the software will always run the same, regardless of its environment.

That’s pretty plain English, but also pretty high level, and people still have disagreements.

To me the most important things to remember about Docker, so that it serves to help you in your dev env is the following:

  • It’s designed to contain your app/process/service.
  • You can create containers from images.
  • Images can be pulled from registries or built with their configuration declarations.
  • Containers are spawned to do the work/service.

Basic operations

As an example, I’ll run a dockerized mongodb. Below is an example command flow.

I list the available images:

$ docker images
REPOSITORY    TAG     IMAGE ID       CREATED        SIZE
postgres      latest  247a11721cbd   5 weeks ago    265.9 MB
mysql         latest  e583afc2f0e4   6 weeks ago    378.4 MB
mysql         5.6     ad517d403791   6 weeks ago    329 MB
redis         latest  be9c5a746699   7 weeks ago    184.9 MB
mongo         latest  a55d8a328b43   8 weeks ago    313.1 MB

I start a new container from the mongo image:

$ docker run -d mongo
724fc23032ae9855790e665e92e8891f018181f8ec707c763ccb2c12a7479332

I can check that the port 27017 is being served mongodb:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED      STATUS        PORTS                      NAMES
724fc23032ae   mongo     "/entrypoint.sh mongo"   5 weks ago  Up 2 seconds   0.0.0.0:27017->27017/tcp   sleepy_spence

And now I just proceed to tell my application to connect to localhost:27017 as per normal. If I need to see the mongodb logs, to check out what is being queried, for example, I can:

$ docker logs sleepy_spence

Normally I would let it run in the background. If, however, I decide that I need to shut mongo down (it’s using up memory afterall), I can:

$ docker stop sleepy_spence

The great thing here is that I now have a set up flow for… almost any service that I need to set up on my dev machine, before I start hacking away my awesome webapp. As you saw in the example, I have mysql, postgres and redis dockerized. The time and effort saved here means you’ll be able to quickly get down to pushing that hotfix from your brother’s windows machine during your family retreat (true story).

Setting up a container

Whatever you thought of, there’s probably already an app image for that. So the first step is to find that image from the public registry.

Analogous to how github.com hosts git repos, Docker Hub hosts docker images. With an account you can create public and images of your own. But today we’ll just pull from the offical account, which collects most of the things we need to speed up our dev set up. The setting up of private and custom images are left as an exercise for the reader.

Go here to explore, or as I prefer, just google “<what-you-need> docker”.

We find the mysql docker image here, from which we can see that, as of 01 July 2016, versions 5.5 to 5.7 are available for us to pull and use.

Most images come with some minimal configurations, like env var, ports, file volume linking, etc. We can use docker-compose to alleviate our task of remembering the rather long commands, which is a certainty if we configure everything inline.

I create a docker-compose.yml file like so:

# ./docker-compose.yml
mysql:
  image: mysql:5.6
  ports:
    - "3306:3306"
  volumes:
    - "/home/keang/data/mysql:/var/lib/mysql"
  environment:
    MYSQL_ROOT_PASSWORD: 'password'

And from this I can invoke the new basic operations:

Basic operations 2.0

$ docker-compose up -d mysql
$ docker-compose logs --tail=20 [-f] mysql
$ docker-compose stop mysql

docker-compose is really powerful because it lets us declare container linkings and share .env, etc. The declarative style makes it easy to orchestrate multiple services across different environments too, for example testing, staging, and even production. For example, I can have the following declaration that spins up new instances of worker, database, cache store, all done at the Continuous Integration service, thanks to docker-compose.

# ./docker-compose.test.yml
testpages:
  build: .
  dockerfile: ./Dockerfile_test_pages
  env_file: test.env
  volumes:
    - ./:/app
  links:
    - postgres
    - redis
    - mongo
postgres:
  image: postgres
  ports:
    - "5434:5432"
  environment:
   - POSTGRES_PASSWORD=password

mongo:
  image: mongo

redis:
  image: redis
# ./Dockerfile_test_pages
FROM deepo/go-python:data-science
RUN mkdir -p `/app
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["py.test"]

With the above set up, I can run an integration test command simply like so:

$ docker-compose -f docker-compose.test.yml run testpages

Recap

This post aims to plant a seed of interest in docker, by showing how it can help with the set up of dev environment quickly, and also a small glimpse at how it can help manage services configurations in a declarative-and hence version-controllable-way.

As the title of this post suggest, this is only the tip of the iceberg; there are many more not covered features, and I believe it is worthwhile to get aquainted with this handy software. I definitely learned a lot from diving into docker, even on things other than docker itself. I’m sure it’ll be the same for many.

Happy dockering!