Red Green Repeat Adventures of a Spec Driven Junkie

From docker run to docker compose

I want to share how to use docker compose to save yourself from going insane remember different docker run options for your images. (A clipboard manager can help only so far here.)

I will walk through having different functions for a docker image through docker run, then taking those commands into docker compose.

You will learn about docker compose saving docker run commands to a file and how YAML’s anchors and aliases feature can keep the file tidy.

This article will take you about four minutes to read.

Pendant Shaped as a Ship source and more information

Introduction

After building a docker image - you have a single appliance, software built to do one thing. What if you want to do other things with that image?

In real life, a refrigerator and freezer do similar things: keep things cold, they share the compressor, yet, what goes in each can be different or similar. Another appliance, the toaster - is different from the refrigerator that they don’t share any characteristics.

With docker images, you can create images that are similar, like a refrigerator and freezer, with overlapping characteristics. You can also create images that are so different so there’s no overlap, like a toaster and a refrigerator.

Today I will talk about reusing images for multiple tasks, like having a compressor that powers both the refrigerator and freezer.

Base Image

Last article I discussed the workflow for creating your own docker image. You can use that image or follow along with the ubuntu image

Functionality

First, let’s test the functionality of the base image and validate it can handle the tasks we want.

In my case, I will cover the following tasks:

  • uptime - get system uptime
  • tar gzip - combine files into single archive and compress them
  • untar and unzip - take compressed archive and restore files

Testing

uptime

It’s a super simple utility that describes how long the system has been running:

% docker run ubuntu uptime
 23:53:52 up 6 days, 16:42,  0 users,  load average: 0.01, 0.02, 0.00

tar gzip

This will take a bit more configuration as you need input files to make this function useful, namely:

  • -v - use current directory as the /docker volume in the container
  • w - set the working directory in the container to /docker
  • /bin/bash -c "<command>" - pass a command to bash to execute
% docker run -v $(pwd):/docker -w=/docker ubuntu /bin/bash -c "tar -cvf backup.tar *.md && gzip -9 backup.tar"
LICENSE.md
README.md
about.md
% ls -l backup.tar.gz*
-rw-r--r--@ 1 andrewleung  staff  1525 Jul 18 20:06 backup.tar.gz

unzip & untar

Let’s take the item created by tar gzip and restore it:

%  docker run -v $(pwd):/docker -w=/docker ubuntu /bin/bash -c "tar -zxvf backup.tar.gz"
LICENSE.md
README.md
about.md

Overlap: Great; Tedious: Bleah!

Even just using a minimal ubuntu image, there can be a different functionality created just with different options passed into run command.

At the same time, remember each command and their options can become tedious.

Is there a better way?

Docker Compose

This is where docker compose comes in! It’s a way to manage this use case for docker (or at least how I am in this case)

Instead of remember all of those command options for docker run, the following docker-compose.yml will do it:

version: '3.9'
services:
  uptime:
    image: ubuntu
    command: uptime

  targz:
    image: ubuntu
    volumes:
      - .:/docker
    working_dir: /docker
    command: /bin/bash -c "tar -cvf backup.tar *.md && gzip -9 backup.tar"

  untargz:
    image: ubuntu
    volumes:
      - .:/docker
    working_dir: /docker
    command: /bin/bash -c "tar -zxvf backup.tar.gz"

To use this, run docker-compose run <service>.

uptime

This is the same command as used in docker run format:

% docker-compose run uptime
Creating network "scratch_default" with the default driver
Creating scratch_uptime_run ... done
 00:17:57 up 6 days, 17:06,  0 users,  load average: 0.16, 0.03, 0.01

docker compose did more work, like the network and new container (for later exploration!)

targz

I named this service to targz, as it’s doing that: “tarring then gzipping”.

% docker-compose run targz
Creating scratch_targz_run ... done
LICENSE.md
README.md
about.md

The service names in the docker-compose.yml file can be anything you want, so tarring_gzipping would be fine too!

untargz

I will leave this command as an exercise for the reader. :-)

Can I do better?

Well, going from a bunch of docker run commands to a neat docker-compose.yml file is a big step. Can things be better?

Yes, if we want to DRY-up the file so the overlapping settings for the tar and untar commands are not repeated, use YAML anchors and aliases. The docker-compose.yml file can become:

version: '3.9'
services:
  uptime:
    image: ubuntu
    command: uptime

  targz: &tarconfig
    image: ubuntu
    volumes:
      - .:/docker
    working_dir: /docker
    command: /bin/bash -c "tar -cvf backup.tar *.md && gzip -9 backup.tar"

  untargz:
    <<: *tarconfig
    command: /bin/bash -c "tar -zxvf backup.tar.gz"

In this case, reducing four lines is not a game changer. At the same time, knowing how to utilize YAML features for the future compose will be handy! 😉

Conclusion

Having a single docker image that can do couple of things because the base image functionality overlap is a win.

When docker run command options start driving you nuts, write them into a docker-compose.yml file and run them with `docker-compose run

`. Your future self up late at night will thank you. Since the docker compose file is essentially YAML, you can use anchors and aliases, giving you a great tool to keep the file tidy and neat, especially when there are lots of overlapping configurations. Part of me appreciates using one thing for multiple applications, like a good compressor for powering both a refrigerator and freezer. ❄️