In Progress
Unit 1, Lesson 21
In Progress

Dockerfile

Continuing in our quest to containerize development, in this video we begin customizing our app container in order to capture an essential tool dependency.

Video transcript & code

When we left off, we had started capturing our project's development-time requirements in a Docker Composer file.

But right now all we have here is a base linux image to use.

Earlier when we manually fired up a Docker container to develop in, we found we needed to do a bit more configuration of that container.

One of the first things we needed to do was install the yarn JavaScript package manager.

root@f5d095b5cccc:/workspace# apt install yarnpkg

And then we symlinked the Debian-customized executable name so that we could invoke the yarn tool by its usual name.

root@f5d095b5cccc:/workspace# ln -s /usr/bin/yarnpkg /usr/local/bin/yarn

Our docker-compose.yml doesn't capture any of this machine configuration. Nor should it. The job of docker compose is to manage starting up and shutting down groups of related containers. Defining those containers is outside of its purview.

When we need a container be customized beyond one of the off-the-shelf images downloaded from Docker Hub, we have to build that container.

And in order to build a container, we need a Dockerfile.

This file instructs Docker in the steps to build a new container. But! We don't have to start from scratch.

Instead, we use the FROM directive to tell it to start from a known, named image. For this container, we use the same Docker Hub ruby image that we referenced in the docker-compose file.

FROM ruby:2.7.2

In container terms, this image now forms the base layer for our custom container. Everything we do after this line will be layered on top of that off-the-shelf image.

The first customization we want to make is to install the yarn package from the Debian package repository. This is actually going to require running a few commands inside the container.

To run a command inside the container, we use the RUN directive.

The first command we run is apt-get update. This command pulls down the latest Debian package lists from the distribution repository. The base image we're using doesn't have those lists already. That's because it's customary to strip down public base container images as slim as possible, in order to keep their file sizes small.

RUN apt-get update

Next we RUN the command to install the yarn package. We use the -y flag to apt-get install to indicate that we want to accept the default options for anything apt-get would otherwise try to prompt us about during the installation.

It's a good idea to always pass the -y option to apt-get install commands in Dockerfiles, since docker builds run non-interactively.

RUN apt-get install -y yarnpkg

Finally, just as we did manually before, we symlink the Debian-packages yarnpkg executable to the more expected name yarn.

RUN ln -s /usr/bin/yarnpkg /usr/local/bin/yarn

You might have noticed that none of these commands use have sudo prefix. At this point in the Dockerfile, any commands we run will be executed as root inside the container under construction, so we don't need to explicitly switch users.

By the way, while using three separate RUN statements for this task works, it's not technically good Dockerfile style. But we'll get to that in another video.

Next up, we need to go back to our docker-compose.yml file.

version: "3.2"
services:
  app:
    image: ruby:2.7.2
    volumes:
      - type: bind
        source: ..
        target: /workspace
    working_dir: /workspace
    command: sleep infinity

Instead of basing the app container on an image, we now want to build a container.

Under the build key, we supply a context of ., meaning that docker-compose should perform the build in the same directory as the docker-compose.yml file.

And for the dockerfile key, we supply the name of the Dockerfile we just wrote.

That's the only change we need to make for docker-compose. Now we open a terminal and cd into the .devcontainer subdirectory.

And then we execute docker-compose up.

This time, instead of just downloading and starting the ruby:2.7.2 image, docker-compose builds a new container before starting it up. This includes running the commands we specified in our Dockerfile. We can see the output as the apt-get commands fetch package listings and then install yarn and all its dependencies.

$ cd .devcontainer
$ docker-compose up

What happens if we shut down this docker-compose session and start it again? This time, the built container is already cached in Docker's local repository. So it starts right up without going through all those build steps.

Once the container is built and up, we can flip over to another terminal and start a shell inside it.

And here we can donfirm that the yarn command is available!

$ yarn --version
1.13.0

We've now met the two principle files at the core of a development container configuration. The docker-compose.yml file defines what services are needed to run the project locally, which containers to download or build to implement those services, and how to start up and shut down the services. The Dockerfile defines how to build a customized container for our app development.

As we proceed, we'll continue to make additions and refinements to these two files. Happy hacking!

Responses