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