Docker Basics for Local Development

There’s been a lot of talk about how Docker can be used in conjunction with tools like Kubernetes to manage clusters of highly scalable microservices.

But Docker can also be a very useful tool for local development, especially when it comes to making repeatable builds and environments faster and easier.

Getting Started

Docker is under very active development, and the best way to get started seems to change every couple of months. As of this writing (2015-09-28), the best way to get set up on OS X seems to be via Docker Toolbox (which can also be downloaded using Homebrew Cask: brew cask install dockertoolbox.)

Docker Toolbox automates the setup of the Docker runtime and several supporting tools. On OS X, this means ensuring that VirtualBox is installed, installing docker-machine and using it to create a VirtualBox boot2docker1 VM to host the Docker daemon.

Once Docker Toolbox is installed, we should have access to the Docker Quickstart Terminal. This application simply opens OS X’s with a wrapper script, running a few checks and setting up environment variables so that the docker commands know how to find the Docker daemon running on the VM.

Rather than jumping over to the Docker Quickstart Terminal every time I want to do something with Docker locally, I’ve simply added the following to my Bash configuration so that it sets up the correct environment with each new shell session:

# Connect docker client to Docker Toolbox's boot2docker VM
# (A docker-machine created VirtualBox VM called 'default')
eval $(docker-machine env default)

After sourcing this file or starting a new shell, we should have some environment variables set for Docker:

$ env | grep DOCKER

Now, I can run docker ps and other Docker commands from any Bash shell and connect to the Docker daemon running on the default Virtual Machine.

Testing It Out

Let’s say we’ve been developing a Rails app on OS X, and we’d like to test it on Linux so we can document its dependencies in preparation for a production deployment. We could use a tool like Vagrant to spin up a Linux VM from a known basebox, but if we’re splitting our time between several projects, we may not want to deal with the overhead of downloading and running several full VMs on our development workstation.

Since Docker uses containers, we only need one VM running Linux (or none if our workstation is already running Linux, but here, we’re assuming development on OS X). Container images can be much smaller than full VM images, and they can also be spun up much faster since the VM that hosts them only has to boot once.

Let’s start an Ubuntu container and see if we can get our application running inside of it.

docker run --rm -ti ubuntu:14.04 bash

This will pull down the official Ubuntu 14.04 Docker image from the _/ubuntu Docker Hub repo (with the 14.04 tag) if we don’t already have it it locally. It will create a new container using that image (in the default VM provided by Docker Toolbox), then run the command bash within that container, attaching it to our terminal in interactive mode (-ti). After running, it will remove the container (--rm), not saving any modifications that we may have made to it.

Once all of the layers2 of the image are pulled down, we should see something like this:


By default, we’re logged in to the running container as root. The 8d6cb95178f4 is the container’s ID. We can use this later to operate on the container. If we poke around a bit, we’ll see that we’re in a minimal Ubuntu Linux environment. Let’s exit and try something more advanced.

This time, let’s attach a volume containing the source code for our application. This will let us access that directory from within the running container. WARNING: This is not a copy! Modifications will also be made to the directory on our workstation.

From our application’s source directory, try this:

docker --rm -v $PWD:/src ubuntu bash 

We’ve added -v $PWD:/src. This will mount the current working directory from our host as a volume at /src in the container3.

We should now be able change to that directory and poke around. We should be able to see files from our app’s source repository and working within the container, create files that show up on our workstation.

For this example, I’m using the sample Rails 4 app from Michael Hartl’s Rails Tutorial.

root@9c0c1fc48459:/# cd /src/
root@9c0c1fc48459:/src# ls
Gemfile  Gemfile.lock  Guardfile  LICENSE  Rakefile  app  bin  config  db  features  lib  log  public  script  spec  vendor

root@9c0c1fc48459:/src# cat
# This file is used by Rack-based servers to start the application.

require ::File.expand_path('../config/environment',  __FILE__)
run SampleApp::Application
root@9c0c1fc48459:/src# ls
Gemfile       Guardfile          Rakefile  bin  features        lib  public  spec
Gemfile.lock  LICENSE  app       config  db         fromdocker.txt  log  script  vendor
root@9c0c1fc48459:/src# touch fromdocker.txt
root@9c0c1fc48459:/src# exit
vonnegut:sample_app_rails_4 english$ ls
Gemfile           LICENSE           Rakefile          config            features          log               spec
Gemfile.lock         app              fromdocker.txt    public            vendor
Guardfile bin               db                lib               script

We now have the basic tools for doing local development with Docker. We’ve pulled images from Docker Hub, we’ve run local containers based on those images, and we’ve mounted volumes into these containers allowing us to interact with files on our host workstation.


1. The name “boot2docker” has been used to refer to both the minimal VM for hosting a Docker daemon and the package that was a predecessor to the Docker Toolbox. Here, we refer to the minimal VM with the Docker daemon installed. The Boot2Docker package has been superceded by the Docker Toolbox as the preferred means of installing Docker on OS X.

2. Docker images are built up in layers using union filesystems with copy on write (CoW) semantics. This allows some lower layers to be shared between different images. Pulling down an image pulls down all of the image’s layers. If we omitted the --rm from our docker run commands and made modifications to the container’s filesystem, we could save a new image with a new layer added on top of all the previous layers. For more on Docker’s union filesytems, see Jérôme Petazzoni’s “Deep Dive into Docker Storage Drivers”.

3. One of the things that Docker Toolbox simplifies for us is mounting filesystems into the boot2docker VM. By default, it sets up a file sharing mount of /Users to /Users on the boot2docker VM. This allows -v in our docker run commands to mount individual directories into individual containers. If you need to mount something outside of /Users, you will need to manually set up the file sharing in VirtualBox to support it.

  • Sunny says:

    This was a good one.

    Do you also have a write up where you talk about how you can run an app inside of a docker container when the host is either OS X or a Linux destro? What I mean is, my production env is Ubuntu with Nginx and my local env is Ubuntu with Apache. And we are a team with some people on OS X. So I’m trying to build a Docker image so that everyone of us can use that on our local machines to emulate the production env. So if its possible to run the apps inside a container and access that from the browser in the host OS, that’d be awesome.

    Any direction here is appreciated! :) Thanks!

  • Matt Scilipoti says:

    Thanks! This was helpful.
    Correction: I believe the second command (to mount a dir as a volume) requires the ‘run’ command and the interactive flags.
    From this:
    `docker –rm -v $PWD:/src ubuntu bash`
    To this:
    `docker run –rm -ti -v $PWD:/src ubuntu bash`

  • Comments are closed.