tldr; Let's get started

I've heard a lot about Docker over the past year but haven't really dived into it much until recently. So far the experience has been amazing! I've found the technology to be very easy and fun to work with. I believe Docker is going to be incredibly popular in the near future and I'd like to get you excited about it by showing you what I've learned so far.


What is Docker?

Docker is a platform for building, shipping, and running distributed applications.

It uses lightweight, portable runtimes called containers to package up an application with all of its dependencies, including the operating system, native packages, and any other libraries or plugins required to run it.

Containers are pretty similar to virtual machines. The main difference is that Docker allows containers to share the same Linux kernel as the host system that they're running on. Containers only need to provide the packages and dependencies that are not already available on the host system. This greatly reduces the size of an application and provides a significant boost to performance allowing containers to be booted up in mere milliseconds.

Snapshots of containers called images allow applications to be distributed amongst developer laptops, production data centers, or any cloud service provider, running and behaving exactly the same in all environments.

Why should I use Docker?

  • It makes it incredibly easy for developers to create, manage, and deploy production ready application containers
  • It allows developers to work in an environment that perfectly mirrors production, reducing the introduction of bugs to live sites
  • It dramatically speeds up productivity by allowing new developers to quickly spin up a local development environment that behaves exactly the same as the rest of the teams'
  • It encourages the decoupling of application concerns by breaking apart monolithic code bases into smaller and more focused components or services
  • It provides the flexibility to allow containers that run completely different frameworks, programming languages, or operating systems to seamlessly work and communicate with each other on the same host system

How do I use Docker?

Let's learn by example and integrate Docker with a Rails application!

We'll use a pretty common stack consisting of:

  • Ubuntu 14.04
  • Ruby 2.1.2
  • Rails 4.1.7
  • Redis 2.8.17
  • PostgreSQL 9.3.5

Let's get started

Read on or feel free to jump around to the different sections using the links below.

Install Docker

We need a Linux host to run Docker since containers share the host's kernel.

I'm not running a Linux distribution for development so let's spin up a host VM by either:

Boot up Ubuntu 14.04 with Vagrant

First we'll need to define a Vagrantfile so let's add one to the root of our application. A box called ubuntu/trusty64 running Ubuntu 14.04 already exists so we can just use that.


Vagrant.configure VAGRANTFILE_API_VERSION do |config| = "ubuntu/trusty64"

Then let's open our terminal and ssh to the new Ubuntu image by calling vagrant up then vagrant ssh.

Install the package with apt-get

The apt-get and docker commands both require sudo so let's change to the root user since we'll be running the docker command frequently.

The root password is vagrant.

vagrant@ubuntu-trusty-64:~$ su

Now let's update and install the package.

root@ubuntu-trusty-64:/vagrant# apt-get update && apt-get install

After Docker installs we can use docker build to create a new image.

Use the lightweight boot2docker Linux distribution

The boot2docker distro was made specifically for running Docker containers. It currently supports both OSX and Windows hosts.

If you're using homebrew you can just brew install boot2docker docker to install both. Then call boot2docker init && boot2docker start.

Build images with a Dockerfile

New images are defined in a file called Dockerfile. This file is a simple list of statements that define the commands required to run a container. Let's add a Dockerfile to the root of our application next to our Vagrantfile.

Inherit from a base image with FROM

Docker images remind me a lot of classes in object oriented programming.

Just like classes, Docker images are inheritable.

All images start with a base image. Like Vagrant, there are existing official images for Ubuntu so let's use that as our base.

FROM ubuntu:14.04

The FROM statement is the only thing required to build a Dockerfile image. Let's try building it from our terminal.

root@ubuntu-trusty-64:/vagrant# docker build .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
5506de2b643b: Pulling dependent layers
511136ea3c5a: Download complete
d497ad3926c8: Download complete
ccb62158e970: Download complete
e791be0477f2: Download complete
3680052c0f5c: Download complete
22093c35d77b: Download complete
22093c35d77b: Download complete
 ---> 5506de2b643b
Successfully built 5506de2b643b

Success! We just built our first Docker image called 5506de2b643b!

Wait... what's all that noise about pulling and downloading dependent layers?

Fetch images from the Docker Hub with docker pull

Like RubyGems for Ruby, Docker has a registry for images called Docker Hub.

This public Docker registry allows developers from all over the world to share open source images that run all kinds of different services and applications!

There are even official images for running popular projects like Ubuntu, Redis, PostgreSQL, Nginx, Node.js, and Jenkins.

When we called docker build just now, Docker noticed that the ubuntu:14.04 image didn't exist on our local system, so it searched the Docker Hub and automatically downloaded the official one for us!

Our application actually requires Redis and PostgreSQL as well so let's try pulling the official images for those from the Docker Hub registry!

root@ubuntu-trusty-64:/vagrant# docker pull redis:2.8.17
Pulling repository redis
3ce54e911389: Download complete
511136ea3c5a: Download complete
6a8a6a35a96b: Download complete
28fdd31ac753: Download complete

Wow, that was easy! Now for PostgreSQL!

root@ubuntu-trusty-64:/vagrant# docker pull postgres:9.3.5
Pulling repository postgres
746b819f315e: Download complete
511136ea3c5a: Download complete
ec77bb5a53d3: Download complete
165394769d57: Download complete

Let's use docker images to print out a list of all images on our system so far.

root@ubuntu-trusty-64:/vagrant# docker images
redis         2.8.17     3ce54e911389      3 days ago         110.7 MB
postgres      9.3.5      746b819f315e      4 days ago         212.9 MB
ubuntu        14.04      5506de2b643b      3 weeks ago        197.8 MB

Notice that the ubuntu:14.04 image has the same image ID as the one that we built earlier! This is because our Dockerfile just inherits FROM ubuntu:14.04 and doesn't add any additional behavior! Docker is smart enough to know that it doesn't need to create a whole new image in this case, it can just use ubuntu.

Let's try starting up a new container with this 5506de2b643b Ubuntu image.

Start a container from an image with docker run

Containers can be started by simply specifying an image id.

root@ubuntu-trusty-64:/vagrant# docker run -i -t 5506de2b643b /bin/bash

Whoa, did you see how fast that started up!?

Now we're in our new container with its own prompt root@b58db262db27:/#

Anything we do or change in the container will not effect the host system.

All modifications made to the container's filesystem will be lost as soon as the container is shut down. It is an incredibly lightweight stateless runtime that can be stopped and thrown away whenever we're done with it.

When we started the container we specified a couple options:

  • -i keeps STDIN open even if we're not attached to the container
  • -t allocates a pseudo-tty

For interactive processes (like a shell) we typically want a tty and persistent standard input (STDIN), so we'll use -i -t together in most interactive cases.

We also specified a couple of arguments:

  • 5506de2b643b is the ID of the image that we want to run
  • /bin/bash is the command that we want to execute on start up

This container is not very useful since it doesn't actually provide any additional functionality. Let's exit back out to our Vagrant host and add some packages that are required by our application to our Dockerfile.

Install required packages with apt-get

We need to install native dependencies for:

  • Precompiling assets - nodejs
  • Pulling packages and gems - git
  • Generating image thumbnails - imagemagick
  • Connecting to PostgreSQL - postgresql-client and libpq-dev
  • Parsing XML documents with Nokogiri - libxml2-dev and libxslt-dev
  • Building and installing Ruby with Rbenv - build-essential, wget, libreadline-dev, libssl-dev, libyaml-dev

We can use apt-get commands to install these packages in our Dockerfile by declaring RUN statements.

FROM ubuntu:14.04

RUN apt-get update && \
    apt-get -y install \
               build-essential \
               git \
               imagemagick \
               postgresql-client \
               nodejs \
               libpq-dev \
               libreadline-dev \
               libssl-dev \
               libyaml-dev \
               libxml2-dev \
               libxslt-dev \
               wget && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Notice that we run apt-get clean and rm -rf to clean up after ourselves once the packages are installed.

Docker commits changes as layers to an image every time it processes commands like RUN in Dockerfile build steps.

Like a Git repository, if we add files to an image and then delete them in future RUN commands, the image's history still contains the files in previous layers. If we're not careful, we could accidentally add bloat to images with leftover files.

Let's try building our Dockerfile with these new changes!

root@ubuntu-trusty-64:/vagrant# docker build .
Sending build context to Docker daemon 10.75 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
 ---> 5506de2b643b
Step 1 : RUN apt-get update && apt-get -y install build-essential git imagemagick postgresql-client nodejs libpq-dev libreadline-dev libssl-dev libyaml-dev libxml2-dev libxslt-dev wget && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 ---> Running in d3ea35797f02
Ign trusty InRelease
Ign trusty-updates InRelease
Ign trusty-security InRelease
Ign trusty-proposed InRelease
Get:1 trusty Release.gpg [933 B]
Get:2 trusty-updates Release.gpg [933 B]
Get:3 trusty-security Release.gpg [933 B]
Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
Processing triggers for libgdk-pixbuf2.0-0:amd64 (2.30.7-0ubuntu1) ...
 ---> ea8c31ce96ce
Removing intermediate container d3ea35797f02
Successfully built ea8c31ce96ce

Sweet! We've got a new Docker image called ea8c31ce96ce. Let's try it out!

root@ubuntu-trusty-64:/vagrant# docker run -it ea8c31ce96ce /bin/bash
root@0d67a0fd8284:/# which git
root@0d67a0fd8284:/# which psql

We've got Git and PostgreSQL! Just to make sure that everything is isolated to this container, let's exit back out to our host and check for those commands.

root@ubuntu-trusty-64:/vagrant# which git
root@ubuntu-trusty-64:/vagrant# which psql

The git and psql commands don't exist on our host system, awesome!

Now that we've got our packages installed, let's try to get Ruby 2.1.2 working!

Install Ruby with ENV, rbenv, and ruby-build

  • First we'll install rbenv and ruby-build by cloning them under /.rbenv.
  • Next we'll configure rbenv with ENV and run the ruby-build installation script.
  • Then we'll install Ruby version 2.1.2.
  • Finally we'll install bundler since we'll need it to install gem depedencies.

Let's update our Dockerfile to perform these actions.

RUN github="" && \
    git clone --depth=1 $github/rbenv.git /.rbenv && \
    git clone --depth=1 $github/ruby-build.git /.rbenv/plugins/ruby-build

ENV PATH /.rbenv/bin:/.rbenv/shims:$PATH

RUN /.rbenv/plugins/ruby-build/ && \
    echo 'eval "$(rbenv init -)"' >> /.bashrc && \
    echo "gem: --no-rdoc --no-ri" >> /.gemrc

RUN version="2.1.2" && \
    rbenv install $version && \
    rbenv global $version

RUN gem install bundler && \
    rbenv rehash

Notice that we used a new statement called ENV. This simply sets an environment variable. Existing environment variables can be referenced in ENV declarations just like we did with PATH.

Let's build our new image and try it out!

root@ubuntu-trusty-64:/vagrant# docker build .
Sending build context to Docker daemon 11.26 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
 ---> 5506de2b643b
Step 1 : RUN apt-get update && apt-get -y install build-essential git imagemagick postgresql-client nodejs libpq-dev libreadline-dev libssl-dev libyaml-dev libxml2-dev libxslt-dev wget && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 ---> Using cache
 ---> ea8c31ce96ce
 Successfully built 8a8f8ad165b7

Whoa, check it out, it skipped the apt-get update step this time!

Docker is smart enough to know that it doesn't need to re-execute commands like RUN unless the command has changed since the last time it was evaluated.

In this case, our list of packages to install didn't change so Docker just used its cached copy.

Let's try out the updated 8a8f8ad165b7 image by starting up an irb console!

root@ubuntu-trusty-64:/vagrant# docker run -it 8a8f8ad165b7 irb
irb(main):001:0> RUBY_VERSION
=> "2.1.2"

Awesome! Now we've got Ruby 2.1.2 and all of the other system level dependencies that are required to run our application!

Now, let's figure out how we can add our Rails code into the container so we can actually run our application.

Mount volumes in containers with docker run -v

Docker allows containers to mount directories on the host system so that we can share files with it. This allows us to share our Rails application in the current directory with a container!

First we'll need to update our Dockerfile to support a VOLUME for our Rails app.

FROM ubuntu:14.04


RUN version="2.1.2" && \
    rbenv install $version && \
    rbenv global $version


This simple VOLUME command let's Docker know that our image expects /app to be a shared directory that lives somewhere on the host system. Let's try it out by building a new image from these changes.

root@ubuntu-trusty-64:/vagrant# docker build .
Step 6 : RUN version="2.1.2" && rbenv install 2.1.2 && rbenv global 2.1.2
 ---> Using cache
 ---> 8a8f8ad165b7
Step 7 : VOLUME /app
 ---> Running in a999838c4841
 ---> e4d5a879c353
Removing intermediate container a999838c4841
Successfully built 59778b8a0ce2

Now let's try it mounting /app to our Rails app in the current directory.

root@ubuntu-trusty-64:/vagrant# docker run -it -v $PWD:/app 59778b8a0ce2 /bin/bash
root@64c5e7b2ba49:/# ls -la /app
drwxr-xr-x  15 1000  1000    510 Nov 14 13:33 .git
-rw-r--r--   1 1000  1000    706 Nov 11 11:19 .gitignore
-rw-r--r--   1 1000  1000      6 Nov  5 15:56 .ruby-version
-rw-r--r--   1 1000  1000   1015 Nov 12 01:31 Dockerfile
-rw-r--r--   1 1000  1000   1338 Nov 11 11:18 Gemfile
-rw-r--r--   1 1000  1000   8556 Nov 11 11:30 Gemfile.lock
-rw-r--r--   1 1000  1000    249 Nov  5 15:56 Rakefile
drwxr-xr-x  20 1000  1000    680 Nov  5 15:56 app
drwxr-xr-x  76 1000  1000   2584 Nov 12 01:01 bin
drwxr-xr-x  25 1000  1000    850 Nov 10 22:33 config
-rw-r--r--   1 1000  1000    154 Nov  5 15:56
drwxr-xr-x   5 1000  1000    170 Nov 10 13:24 db
drwxr-xr-x  26 1000  1000    884 Nov 10 13:46 lib
drwxr-xr-x   3 1000  1000    102 Nov 11 11:59 log
drwxr-xr-x  10 1000  1000    340 Nov 10 23:49 public
drwxr-xr-x  18 1000  1000    612 Nov  7 14:25 spec
drwxr-xr-x   3 1000  1000    102 Nov 11 11:59 vendor

Sweet, now we've got our Rails code mounted under the /app directory!

Any changes that a container makes to mounted volumes will be shared and reflected on the host system as well.

Now let's try install gem dependencies with bundle install and write them to the vendor/bundle directory.

We'll need to configure Nokogiri to use the system libraries for libxml2 first.

root@ubuntu-trusty-64:/vagrant# docker run -it 59778b8a0ce2 /bin/bash
root@f44fde88a15d:/# cd /app
root@f44fde88a15d:/app# bundle config build.nokogiri --use-system-libraries --with-xml2-include=/usr/include/libxml2
root@f44fde88a15d:/app# bundle install --path vendor/bundle
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and installing your bundle as root will break this application for all non-root users on this machine.
Fetching gem metadata from
Resolving dependencies...
Installing rake 10.3.2
Installing i18n 0.6.11
Installing turbolinks 2.5.2
Installing uglifier 2.5.3
Your bundle is complete!
It was installed into ./vendor/bundle

Great, all of the gem dependencies installed successfully! Let's check our host system to make sure that vendor/bundle was created and actually has contents.

root@f44fde88a15d:/app# exit
root@ubuntu-trusty-64:/vagrant# ls -la vendor/bundle
total 0
drwxr-xr-x 1 vagrant vagrant 102 Nov 16 06:23 .
drwxr-xr-x 1 vagrant vagrant 102 Nov 16 06:23 ..
drwxr-xr-x 1 vagrant vagrant 102 Nov 16 06:23 ruby

Looks like bundle installed the gems to vendor/bundle as expected!

To verify, let's start up a new container and try to bundle install again.

root@ubuntu-trusty-64:/vagrant# docker run -it -v $PWD:/app 59778b8a0ce2 /bin/bash
root@f44fde88a15d:/# cd /app
root@837a7a749b19:/app# bundle install
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and installing your bundle as root will break this application for all non-root users on this machine.
Using rake 10.3.2
Using i18n 0.6.11
Using turbolinks 2.5.2
Using uglifier 2.5.3
Your bundle is complete!
It was installed into ./vendor/bundle

Success! Bundler skips Installing all of our gems and imforms us that it's Using the existing versions from vendor/bundle instead!

Notice that we didn't have to configure Nokogiri again! Since the bundle config command saves settings to .bundle/config (which is mounted on our host system) this configuration can persist and be shared between containers.

Since we keep calling cd /app when we first run the container, let's make that the default working directory.

Set the working directory with WORKDIR

The Dockerfile WORKDIR directive simply sets the working directory to the specified value for all commands that run after it. Let's try adding it to our Dockerfile.


Now we can rebuild it and try it out!

root@ubuntu-trusty-64:/vagrant# docker build .
Successfully built 9075d0fd41f5
root@ubuntu-trusty-64:/vagrant# docker run -it -v $PWD:/app 9075d0fd41f5 /bin/bash
root@130321bd404c:/app# pwd

Sweet, now we don't have to cd /app all the time!

It's a little annoying having to copy paste the image ids like 9075d0fd41f5 when we want to run a container. There's got to be an easier way!

Tag images with docker tag or docker build -t

Since we've already built the 9075d0fd41f5 image for our Rails application, let's try tagging it as example-app so we can refer to it by that instead.

root@ubuntu-trusty-64:/vagrant# docker tag 9075d0fd41f5 example-app
root@ubuntu-trusty-64:/vagrant# docker images
example-app     latest      9075d0fd41f5     26 minutes ago   554.7 MB
<none>          <none>      e4d5a879c353     2 hours ago      550.7 MB
<none>          <none>      4ecd2ce76f10     46 hours ago     550.7 MB
<none>          <none>      73ab02dbc5df     46 hours ago     437.1 MB
<none>          <none>      047c19d8acb0     2 days ago       397.9 MB
redis           2.8.17      3ce54e911389     5 days ago       110.7 MB

Sweet, Docker will now allow us to refer to our image as example-app. Let's give it a shot!

root@ubuntu-trusty-64:/vagrant# docker run -it example-app /bin/bash

Great, it worked as expected! We can also tag an image when it's built by specifying the -t flag.

root@ubuntu-trusty-64:/vagrant# docker build -t example-app .

That's pretty convenient! We can run that command whenever we update our Dockerfile and the example-app image will always stay up to date!

Did you notice all of those <none> images from the docker images output earlier? Those are all of the images we made from our previous calls to docker build .. Since we don't really need those anymore, let's try removing them.

Remove images with docker rmi

If your docker images list starts to fill up, you can remove them by their name or IMAGE ID.

root@ubuntu-trusty-64:/vagrant# docker rmi 4ecd2ce76f10
Deleted: 4ecd2ce76f10611d6f0f6a31653f9245414d198c92bbc265886bb1c79152a06c

We've got a bunch of images to delete but it's pretty tedious and annoying to have to list out all of those image ids. Here's a couple of commands to make removing multiple images easier.

To delete all untagged images

docker rmi $(docker images | grep "^<none>" | awk '{print $3}')

To delete all images

docker rmi $(docker images -q)

Now that we've got our Docker images ready, let's figure out how to allow running containers to communicate with each other.

Run containers in the background with docker run -d

Let's try booting up our Redis image to see if it works.

root@ubuntu-trusty-64:/vagrant# docker run -t redis:2.8.17
[1] 17 Nov 08:32:30.476 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 2.8.17 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 1
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    | 
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'

[1] 17 Nov 08:32:30.485 # Server started, Redis version 2.8.17
[1] 17 Nov 08:32:30.487 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[1] 17 Nov 08:32:30.487 * The server is now ready to accept connections on port 6379

Uh oh, this process never ends so our shell is stuck! We'll have to run this container in the background using the -d flag. Let's hit ctrl+c to stop the process and exit back out to our host system.

root@vagrant-ubuntu-trusty-64:/vagrant# docker run -d redis:2.8.17

Docker allows us to daemonize a container process with -d so it runs in the background

The docker run command printed out the id of the container that it created. Let's try starting a background container running PostgreSQL as well.

root@ubuntu-trusty-64:/vagrant# docker run -d postgres:9.3.5

That was pretty easy! Let's figure out a way to list out the names and ids of the currently running containers just to make sure.

List running containers with docker ps

Simply calling docker ps will list out all currently running containers.

root@ubuntu-trusty-64:/vagrant# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS               NAMES
8170b29887b7        postgres:9.3.5      /docker-entrypoint.s   About a minute ago   Up About a minute   5432/tcp            dreamy_lalande
9e3b945adf4e        redis:2.8.17        / redis   6 minutes ago        Up 5 minutes        6379/tcp            distracted_leakey
e83564543aed        redis:2.8.17        / redis   8 minutes ago        Up 8 minutes        6379/tcp            naughty_wozniak

Wait... why do we have two Redis containers running? One of them was from earlier when we didn't start the container in the background with -d! We detached from the process but we didn't actually stop the container.

Stop containers with docker stop

The docker stop command accepts the id of a container and stops it. Pretty straightforward!

Let's try stopping the old Redis container.

root@ubuntu-trusty-64:/vagrant# docker stop e83564543aed

Looks like it worked! Let's run docker ps again just to make sure.

root@ubuntu-trusty-64:/vagrant# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
8170b29887b7        postgres:9.3.5      /docker-entrypoint.s   11 minutes ago      Up 11 minutes       5432/tcp            dreamy_lalande
9e3b945adf4e        redis:2.8.17        / redis   16 minutes ago      Up 16 minutes       6379/tcp            distracted_leakey

The old Redis container is gone! Now let's try connecting our running background services to our Rails application.

TODO: Demonstrate naming and linking containers for Redis and PostgreSQL.

root@ubuntu-trusty-64:/vagrant# docker run -v $PWD:/app --link 8170b29887b7:db --link 9e3b945adf4e:redis example-app bundle exec rake db:create:all

Name containers with docker run --name

TODO: Demonstrate naming the Redis and PostgreSQL containers.

root@ubuntu-trusty-64:/vagrant# docker run -d --name redis redis:2.8.17
root@ubuntu-trusty-64:/vagrant# docker run -d --name db postgres:9.3.5

Set environment variables with docker run -e

TODO: Demonstrate setting DB_ADAPTER, DB_USER, DB_PASS, and REDIS_URL.

root@ubuntu-trusty-64:/vagrant# docker run -v $PWD:/app --link db:db --link redis:redis -e "DB_ADAPTER=postgis" -e "DB_USER=docker" -e "DB_PASS=docker" -e "REDIS_URL=redis://redis" example-app bundle exec rake db:create:all

List all containers with docker ps -a

TODO: Demonstrate listing all containers, including stopped, with docker ps -a.

Remove containers with docker rm and docker run --rm

TODO: Demonstrate removing stopped containers with docker rm. Demonstrate shortcut for removing multiple containers at once. Auto remove containers after its process ends with docker run --rm.

Start up multiple containers with Fig

TODO: Demonstrate spinning up a full stack with Rails, Redis, and PostgreSQL.

Run Docker within Docker by mounting /var/lib/docker

TODO: Demonstrate managing Docker images and containers from within a running container.


  • compare Dockerfile definitions to classes in programming
    • they're inheritable
    • they're only supposed to "do" one thing, in this case, perform a single task
    • dependencies are passed into containers when initializing
    • fig is like a dependecy injection framework for provisioning docker containers
  • explain how Docker containers are super light weight
    • they only run one process
    • they only contain the dependencies required to run that single process
    • they are supposed to be "stateless", services and data volumes run in their own containers
  • explain how to run containers
    • naming containers with --name
    • creating volumes with -v example
    • mounting volumes with --volumes-from
  • explain data only volumes and how to mount them
  • explain how to commit running containers
  • explain how to push images to the Docker Hub
  • explain how private Docker image registries work
  • explain how the ADD, COPY, and RUN commands cache works
  • explain how we can create a package of our application
  • explain how to import and export images
    • compressing tar archives with gzip
    • exporting to and from S3