Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Automate Installing Docker and Docker Compose With Ansible


There's going to come a time where you'll want to install Docker and Docker Compose on your production server, and Ansible makes it easy.

Quick Jump: A Specific Version of Docker and Docker Compose | Configure Your System and Docker | Authenticate with your Docker Registries | Clean up on Aisle Docker | Customizable With Sane Defaults | Putting It All Together

I’m a huge fan of using Docker and Ansible together, so it’s no surprise that I let Ansible handle installing Docker and Docker Compose in production.

You can check the Ansible role out on GitHub.

This role works for Docker hosts running:
  • Ubuntu 16.04 LTS (Xenial)
  • Ubuntu 18.04 LTS (Bionic)
  • Debian 8 (Jessie)
  • Debian 9 (Stretch)

After all is said and done you can just add this to your stack of Ansible roles:

- { role: "nickjj.docker", tags: ["docker"] }

…and a few moments later you’ll have the latest Docker CE and Docker Compose installed.

A Specific Version of Docker and Docker Compose

In any production system, it’s important to be able to lock in a specific version so you that you can repeat the process at a later time without worrying that something in the future broke your set up.

That’s why I also supported pinning versions. Here’s what’s available in terms of versions:

# Do you want to use "ce" (community edition) or "ee" (enterprise edition)?
docker__edition: "ce"

# Do you want to use the "stable", "edge", "testing" or "nightly" channels?
# Add more than 1 channel by separating each one with a space.
docker__channel: "stable"

# When set to "latest" this role will always attempt to install the latest
# version based on the channel you selected. This could lead to something like
# Docker 18.06 being installed today but then a year from now, re-running the
# role will result in 19.06 or whatever Docker happens to use a year from now.
# If you want to pin a version simply put "18.06", "18.06.1" or whatever version
# you want. Even if you update your package list and newer Docker versions are
# available this role will stick to the pinned version on all future runs.
docker__version: "latest"

# Do you want to also install Docker Compose? When set to False, Docker Compose
# will not get installed or will be removed if it were installed previously.
docker__install_docker__compose: true

# If Docker Compose is being installed, which version do you want to use?
docker__compose_version: "1.22.0"

# If Docker Compose is being installed, where should it be downloaded from?
docker__compose_download_url: "https://github.com/docker/compose/releases/download/{{ docker__compose_version }}/docker-compose-Linux-x86_64"

Docker Compose is an optional install because there may be a case where you just want Docker installed without Docker Compose. Perhaps if you’re using Swarm.

Configure Your System and Docker

Then, there’s a couple of options to ensure users have permission to run Docker without root, as well as configure the Docker daemon with any options or environment variables you desire.

# A list of users to be added to the docker group. For example if you have a
# user of "admin", then you'll want to set docker__users: ["admin"] here.
# Keep in mind this user needs to already exist, this role will not create it.
# This role does not configure User Namespaces or any other security features
# by default. If the user you add here has SSH access to your server then you're
# effectively giving them root access to the system since they can run docker
# without sudo and volume mount in any file on your file system.
# In a controlled environment this is safe, but like anything security related
# it's worth knowing this up front. You can enable User Namespaces and any
# other options with the docker__daemon_options variable which is explained later.
docker__users: []

# How large should each Docker log file be? You can set -1 for unlimited.
# You can use "k" to denote kilobytes, "m" for megabytes and "g" for gigabytes.
# Here's 3 example sizes showcasing the format: 100k, 100m and 10g
docker__default_daemon_json_log_max_size: "1m"

# Docker rotates its own container logs. How many rotations do you want to keep
# on disk? With a size of 1m and 10,000 rotations, that would be a max of 10gb
# of disk space.
docker__default_daemon_json_log_max_file: 10000

# Default Docker daemon options as they would appear in /etc/docker/daemon.json.
# In this example, we're setting the log rotate related flags.
docker__default_daemon_json: |
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "{{ docker__default_daemon_json_log_max_size }}",
    "max-file": "{{ docker__default_daemon_json_log_max_file }}"

# Add your own additional daemon options without overriding the default options.
# It follows the same format as the default options, and don't worry about
# starting it off with a comma. The template will add the comma if needed.
docker__daemon_json: ""

# You can also configure the Docker daemon at the systemd service level using
# command line flags.
  - "-H fd://"

# Can be used to set environment variables for the Docker daemon, such as:
# docker__daemon_environment:
#   - "HTTP_PROXY=http://proxy.example.com:80"
#   - "HTTPS_PROXY=https://proxy.example.com:443"
docker__daemon_environment: []

Authenticate with your Docker Registries

When running in production, chances are you may end up using private repositories. That means you’ll need to login to your Docker Registry of choice.

You’ll be able to set credentials for any Docker Registries you want:

# Manage login credentials for 1 or more Docker registries. Example usage:
# docker__registries:
#     # Your registry URL is optional and defaults to the Docker Hub if undefined.
#   - registry_url: "https://index.docker.io/v1/"
#     # Your username is required.
#     username: "your_docker__hub_username"
#     # Your password is required.
#     password: "your_docker__hub_password"
#     # Your email address is optional (not all registries use it).
#     email: "your_docker__hub@emailaddress.com"
#     # Update your credentials. If undefined, this behavior will be skipped.
#     reauthorize: False
#     # Remove a login by setting an absent state (it defaults to present).
#     state: "absent"
#     # The system user that will have access to the registry. If undefined it
#     # will default to the root user. You likely want to set this to be a user
#     # defined in your docker__users list above.
#     system_user: "a_user_defined_in_docker__users"
docker__registries: []

Here’s a few examples:

  # Authenticate to the Docker Hub, and allow the "admin" system user acces to it:
  - username: "your_docker_hub_username"
    password: "your_docker_hub_password"
    email: "your_docker_hub@emailaddress.com"
    system_user: "{{ docker__users | first }}"
  # Authenticate to some other private registry and allow "zerocool":
  - registry_url: "https://your-registry.com"
    username: "some_other_username"
    password: "some_other_password"
    system_user: "zerocool"
  # Disable logging in to some old registry you don't use anymore:
  - registry_url: "https://old-registry.com"
    username: "some_old_username"
    password: "some_old_password"
    state: "absent"

# Or simply don't include this and nothing will happen since it defaults to [].

Clean up on Aisle Docker

When running Docker in production, chances are you’ll accumulate a bunch of Docker related cruft on disk. It’s important to clean those unused files on a regular basis. This role does it for you with a weekly cron job.

# Manage 1 or more cron jobs to perform Docker related system tasks. By default
# this will safely clean up disk space used by Docker every Sunday at midnight.
  - name: "Docker disk clean up"
    job: docker system prune -af &> /dev/null
    schedule: ["0", "0", "*", "*", "0"]
    cron_file: "docker-disk-clean-up"
    # Remove a cron task by setting an absent state (it defaults to present).
    # state: "absent"

You can add / remove any cron jobs you want, or set it to [] to not do this.

Customizable With Sane Defaults

Most of the above options are set by default, meaning you won’t have to override or supply them in your Ansible playbooks but they are available if you need to customize them.

That’s one thing I love about Ansible. It’s so easy to make things work without a ton of mandatory config settings, meanwhile you can always dive in and tweak every last thing if you really need it.

Putting It All Together

Here’s a minimal playbook. The example below expects you to have a web group with 1 or more hosts in it, but that’s easily changeable. It’s also setting a custom deploy user.

# site.yml

- name: Configure web server(s)
  hosts: "web"
  become: True
    docker__users: ["deploy"]
    # TODO: You should consider using docker_version: "XX.XX" to pin it!

    - { role: "nickjj.docker", tags: ["docker"] }

You can read more about this Docker role on GitHub.

So that wraps things up. Let me know how it goes in the comments.

Free Intro to Docker Email Course

Over 5 days you'll get 1 email per day that includes video and text from the premium Dive Into Docker course. By the end of the 5 days you'll have hands on experience using Docker to serve a website.