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
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.
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
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
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
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
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
., meaning that
docker-compose should perform the build in the same directory as the
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
And then we execute
This time, instead of just downloading and starting the
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!