Treating Dockerfiles as shell scripts - Jake Wharton
Treating Dockerfiles as shell scripts
03 December 2020
I use Docker to run a lot of tools. With the tools all wrapped up in containers, my computers are<br>free of Python and Go and the various other dependencies needed for their use. While this is a nice<br>win for isolation and reproducibility, the user experience is sub-par.
To run a tool, I just use docker run:
$ docker run --rm tool arguments...
But the editing workflow is something like:
$ nano tool.dockerfile<br># hack hack hack...<br>$ docker build -t tool -
I also use a lot of bash scripts with easy-to-remember names. To run a script I just type its name:<br>./update_containers.sh. To edit, I open it in an editor, save, and then run. The user experience<br>of this is top-notch!
Can we combine the two?
Executable Dockerfiles
If the first line of an executable starts with #!, unix-y systems will treat what follows on that<br>line as an executable for interpreting the rest of the file. This is called the<br>shebang, and the bash scripts I use start with one:<br>#!/usr/bin/env bash.
In order to do this with a Dockerfile, though, we need a program which will conditionally run<br>docker build and then docker run the resulting image. Thankfully docker build is already<br>conditional and won't rebuild anything unless necessary, so we can always run it.
#!/usr/bin/env bash<br>NAME=$(basename "$1")<br>docker build -t "$NAME" - "$1" > /dev/null<br>shift # Remove script name from arguments<br>docker run --rm --name "$NAME" "$NAME" "$@"
With this saved as dockerfile-shebang.sh, we can add it as the shebang in a Dockerfile.
#!/path/to/dockerfile-shebang.sh
FROM alpine:latest<br>ENTRYPOINT ["echo"]
Saving this as echo.dockerfile and running chmod +x echo.dockerfile provides the user experience<br>we're after:
$ ./echo.dockerfile Hello, world!<br>Hello, world!
It's Dockerfile-Shebang!
I have wrapped up this utility into an executable, dockerfile-shabang.<br>You can find it at<br>github.com/JakeWharton/dockerfile-shebang.
The implementation is a bit more complicated than above for a few usability and correctness<br>concerns:
Builds can be slow, so a message will be displayed if the container is currently being built.
If the build step fails, its entire output will be displayed to aid in debugging.
Most importantly, there's a mechanism for passing arguments to the docker run command for<br>mounting volumes, setting environment variables, and any other container-level flags.
A real-world invocation looks something like:
$ ./tool.dockerfile -v /tanker/backups:/backups -e UID=1000 -- /backups/path/to/file.txt
While I've been using Docker to wrap tools for a while, I've only been using this shebang for a<br>week. If you're feeling similar usability pain around Dockerfiles, try it out, let me know if it<br>works, and let me know of any use cases you have which aren't covered.
— Jake Wharton