Drop #182 (2023-01-20): Weekend Project Edition
Docker Customization 101
In last week's WPE I fully ACK'd that some of these WPEs were…complex. I'm trying to adhere to a personal “no assumptions” rule in 2023 (ask me how that's working out sometime), and that goes triple for these WPEs. So, for a bit, these will be a more “101”-focused, similar to how last week's WireGuard issue was.
Today, we get down to Docker fundamentals, with the goal of the project being for you to customize a Docker container to run a command you create. Sure, the vast majority of readers have and do run Docker containers, likely just using
docker run with environment variables and command line options that point it to databases, local resources, or perform other external customizations. Bending an existing container image to your will and turning it into another container is a different experience, but one that opens up more paths for creativity, exploration, and skills development.
Contain Your Enthusiasm
Docker itself is a nothing more than one way to manage and execute something called “containers”. IBM has a decent primer on Docker-proper, as well as a 'splainer on containers. TL;DR: Traditional applications on your phone/tablet/desktop/laptop bundle all sorts of resources and libraries inside them and lay them out on the local file system during installation. Containers take this one step further, and container images can bundle a completely different operating system and multiple applications.
In modern compute environments, traditional applications can be run in a sandbox that restricts access to various resources. Containers, too, run in controlled environments and in such a way that makes it impossible to corrupt or even just access resources on the host they're running on.
Folks often pin resource dependencies to a specific version when writing a new utility, package, or analysis. This is to help ensure the code runs on more than just your machine. With containers, this “pinning” occurs all the way down to the operating system environment, making for even greater execution reproducibility. The tradeoff for this robustness comes at the expense of memory, processor power, and disk space. Thankfully, modern compute devices — whether they be phones, tablets, laptops, or servers — generally have those three resources in shameless abundance.
Note that while we're using Docker in today's Drop, there are other container ecosystems out there, a few of which are OS-dependent.
It's All About That Base [Image]
When you follow along with some README, blog post, or tutorial that has you use, at some point,
docker run … a whole lot is going on.
I'd like to encourage folks to drop some coin on a fantastic zine by Julia Evans that covers this in a very enjoyable way (we've linked to Julia's great work a few times in previous editions).
If you're unable to obtain that resource, Julia was kind enough to write a blog on how Docker images work that covers pretty much anything I would have said in this section, and also has info from the previous one).
I'll grab a ☕️ while you check that out…
We can't move to the last section if the following command doesn't drop you to a command line prompt in a container:
docker run --rm -ti alpine:latest
--rm just tells Docker to to automatically remove the container when it exits. The
-ti is really
-t -i, and that tells Docker to allocate a pseudo-TTY and keep
stdin open (so, combined, they enable you to type in the terminal of the container process).
When you exit the terminal, do a
docker ps -a to prove it's no longer available.
So, your mission is to make some Docker containers that will just run a command that you've built. I say “some” since the only way to get comfortable with Docker is to practice, and it's too easy to declare success after wrapping one simple example.
We'll kickstart this by looking at a basic
Dockerfile that runs the fortune command (which isn't installed on macOS at all and no longer comes with many linux distros).
Create a directory and then put these three lines in a file named
FROM alpine:latest RUN apk add fortune CMD fortune
Get to a command line and go to that directory.
docker build -t fortune .
It'll finish pretty quickly, and you should see
fortune when you do:
You should now be able to run:
docker run --rm fortune
and see some output!
Now, start off your customization journey by doing the following (keep the fortune man page handy):
Use a different naming convention for builds that are more like ones you see on the internets. For example:
docker build -t me/fortune:latest -t me/fortune:0.1.0and look at what it creates and try to run it with either tag.
Look up the
ENTRYPOINTdirective as it relates to
CMD(Ref: https://docs.docker.com/engine/reference/builder/#cmd). Now, modify the Dockerfile to let our
fortunetake command line parameters from the user. If you're struggling or learn better by using an example, check this one out.
Look up the
ADDdirective (Ref: https://docs.docker.com/engine/reference/builder/#add). Write a small shell script that does something innocuous, add it to the image, then have that be the
Write a Python script and have that be the
CMDtarget. You may want to consider using a different base image to make your life easier (or at least speed up this exercise). If you're more comfortable with R, try this one out.
Lookup how volumes work and modify what you did in the previous step to have the script you wrote be able to access files from the current directory you're in on the host system.
This old-ish post has some great info that still holds up today if you get stuck.
We'll cover Docker in more depth in future WPEs. Drop a comment with Docker resources that might help others out, or where some more details might have helped make this edition a bit easier to work through. ☮