Red Green Repeat Adventures of a Spec Driven Junkie

Docker: Building Images

Continue sharing my journey in having all my virtualization needs in docker.

This time, I will talk about my method in building docker images from base images.

You will learn a way to build docker images for your needs.

This article will take about four minutes to read.

Martin Johnson Heade - York Harbor, Coast of Maine source and more information

Introduction

After figuring out the commands I need in docker for all my virtualization needs, I want to start building out specific docker images so I can be productive with a consistent environment.

Goal

The goal is to create a docker image that does what you need when running: docker run <image tag>.

Building on Shoulders of Giants

If possible, use a pre-existing docker image. Docker hub hosts these here:

https://hub.docker.com/search?q=&type=image

The official ones by docker (for cross platform compatibility)

https://hub.docker.com/search?q=&type=image&image_filter=official

I’m not sure if it’s possible, but one can build their own docker image without an image. 🤔

Operating System Playground

A docker image can be as simple as an operating system image. If you just want to just play around with another operating system instead of tainting your host system, a base operating system would be perfect:

https://hub.docker.com/search?source=verified&type=image&category=os

The Dockerfile would be:

FROM ubuntu

Build it with:

docker build . -t <image tag>

Run it using:

docker run -it <image tag> /bin/sh

Language Playground

If you want to play with a different programming language in a clean environment,

https://hub.docker.com/search?source=verified&type=image&category=languages

The Dockerfile would be:

FROM ruby

Build it with:

docker build . -t <image tag>

Run it using:

docker run -it <image tag> /bin/sh

Docker Philosophy

When building out other systems when I install programming languages, I usually include version management like rvm/nvm

When building out an image in docker, you can install these. They will be harder to use later on as these version management system require user environment configurations.

The philosphy for a docker image is different than building out other systems, the intention for a docker image is a single use case, like an appliance. The system will be rebuilt consistently each time and not intended for multi-user/applications. To run an application with another version of a programming language, create a new image for it.

Frameworks Playground?

I would expect there would be docker images for web frameworks such as Express (node), Ruby on Rails (ruby), or even Django (python). Django and Ruby on Rails have depreciated their images for the base language versions instead.

When considering changes in the language and frameworks, this would become an n x m problem.

From the framework pages, each recommend their own method to create a docker image with the framework.

FROM ruby:2.3

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        postgresql-client \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src/app
COPY Gemfile* ./
RUN bundle install
COPY . .

EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

Docker Image

With a base image, I go into this cycle in creating a custom docker image that fit my needs:

  1. run image
  2. connect to container
  3. interactively execute configuration commands
  4. update Dockerfile
  5. build new image
  6. goto step 1

When using docker run -it <image tag>, the cycle becomes:

  1. run & connect to container - docker run -it <image tag>
  2. interactively execute configuration commands
  3. update Dockerfile
  4. build new image - docker build . -t <image tag>
  5. goto step 1

Five simple steps to custom docker images with all your needs.

Updating Dockerfile

When updating the Dockerfile, use the RUN directive to add new parts to the image when running docker build. Whatever the system requires a command to build to your desire, add it in to the Dockerfile:

FROM <os>

RUN <command_1>
# ...
RUN <command_n>

For example: setting up a system to run Jekyll:

# originally: https://github.com/BretFisher/jekyll-serve/blob/main/Dockerfile
FROM ruby:3-alpine

RUN apk add --no-cache build-base gcc bash cmake git
RUN gem install bundler

Build the image:

docker build . -t <image name>

Now, when you run the image:

docker run -it <image name> /bin/bash

the environment is exactly how you want it to be. If something goes wrong when working with it:

docker image rm <image name>

Conclusion

Building a docker image to suit your needs can be tedious as there’s a “run, record, update” loop with the system and Dockerfile.

Before building, use a docker image from Docker Hub that most resembles your goal. Using public docker images lets you focus on parts of the system that is unique to your goal.