Use Docker to Run (almost) any Executables
Or, how to Hugo without installing it, with the help of container technologies like Docker or Podman.

Recently I’ve been testing Podman on my backup laptop to see if it can really replace Docker. As part of the fun I tried to always use a docker image whenever possible, including using Hugo to update my blog. Normally using Hugo involves printing to the stdout, reading/writing to the filesystem, and binding to a port for serving files. Thus by following the steps reproduced here to run Hugo, it should be possible to run pretty much any executable / binary / CLI tools with Docker or Podman in the same way. And it is much less cumbersome than you would imagine.

The difference between Podman and Docker does not matter for the usage covered in this post. I will use docker for all the commands instead of podman because Docker still is more popular. I’ll also assume you already have the background knowledge on Docker, so that I can skip my own (probably wrong) understanding of containers.

TLDR

Alias this, then run your usual hugo commands.

alias hugo='docker run -v $(pwd):/src --rm -p 1313:1313 klakegg/hugo:ext'

Or, an example for getting an interactive mysql session:

alias mysql="docker run -it --rm mysql:8.0 mysql"

Pull the Image

To run a container, you need to have an image (which is the software plus its dependencies). Chances are that the tool you want to use has an official Docker image, or some trust-worthy people has built one. With Hugo, this image is not official but recommended by the official guide. I prefer the extended version for Hugo, thus:

docker pull klakegg/hugo:ext

Where klakegg/hugo refers to the various Hugo images, while ext specifies for the latest extended version. What you got is a container image, which has Hugo installed inside it.

Run from a Container

With a normally installed Hugo, you simply type hugo [COMMAND] in the terminal to run commands. With dockerized Hugo, it’s:

 docker run [OPTIONS] klakegg/hugo:ext [COMMAND]

Where docker run starts a container, using the image klakegg/hugo:ext, with the specified [OPTIONS] necessary for running the [COMMAND].

For simple usage like checking the version, usually you do hugo version. With Docker, it is:

docker run --rm klakegg/hugo:ext version

The --rm option tells docker to remove the container once it’s done. Since all that we need from this container is to print the version, having it removed saves the trouble of doing it later.

The version command is passed to the container for the program inside it to handle. In this case the program is Hugo, thus it will just work as if we are running hugo version to print the version info:

hugo v0.88.0-ACC5EB5B+extended linux/amd64 BuildDate=2021-09-02T11:51:42z VendorInfo=hugoguru

Similarly, docker run --rm klakegg/hugo:ext --help prints the full lengthy help info just like hugo --help.

Access the Filesystem

Obviously we don’t pull an image just to print its version. We need it to create files on the computer. However, if you simply run docker run --rm klakegg/hugo:ext new site <sitename>, it will not work because for containers, the filesystem is contained. An additional option is necessary to mount the host filesystem to the container for it to write to:

docker run -v $(pwd):/src --rm klakegg/hugo:ext new site <sitename>

The -v $(pwd):/src mounts the current working directory of the host to the path /src of the container (which happens to be its default working directory). Thus it now works just as if you are running hugo new site <sitename>. For creating the site folder to a different path, change the $(pwd) part with a specified path. For using other images, the default working directory may not be /src and should also be changed accordingly.

Exactly how to find out the default working directory varies by images. With klakegg/hugo:ext, and other images that come with a /bin/sh (this usually is the case), you can do docker run --rm --entrypoint /bin/sh klakegg/hugo:ext -c pwd to find out.

Once the site directory is created, just cd into it for creating new posts:

cd <sitename>
docker run -v $(pwd):/src --rm klakegg/hugo:ext new posts/hello-world.md

Bind to a Port

A container also has its network contained. Thus, normal run of docker run -v $(pwd):/src --rm klakegg/hugo:ext server -D will not give you an accessible demo site for previewing on the default port 1313. Instead, manually port forwarding is necessary with the option -p <host port>:<container port>.

With this image, the default port that Hugo exposes to is 1313. Thus the following command will have it forwarded to the same port on the host.

docker run -p 1313:1313 -v $(pwd):/src --rm klakegg/hugo:ext server -D

Sometimes the port on the host is not available, or you just want to have it on a different port. With some other images, you can simply change the host port, such as -p 1314:1313. With Hugo, you will also need to explicitly tell it to serve on the new port (hugo server -p 1314), otherwise many of the links will be broken. Thus,

docker run -v $(pwd):/src --rm -p 1314:1314 klakegg/hugo:ext server -D -p 1314

Alias

It’s cumbersome to type all these commands to get simple things done. If only the shell can automatically replace hugo with all these long commands…

This is when we use alias. Edit your .zshrc or .bashrc file to include this line:

alias hugo='docker run -v $(pwd):/src --rm -p 1313:1313 klakegg/hugo:ext'

Note that you must use single quotes, to make sure $(pwd) will be evaluated to where you run the command.

Reload your shell, and not things just work as if you have installed Hugo locally.

But… Why?

Running things in containers usually are good for the following reasons:

  1. Better dependency management:

    • Nothing will mess with the tools already installed, even when they depend on different versions of other tools
    • Different versions of a same tool can coexist, so that you can avoid problems from version incompatibilities
  2. Better security and resource management:

    • A container is not a virtual machine, but it will be much harder for the thing running inside a container to access files on the host (unless you mount everything, which you should not)
    • Additional Docker options can be used to limit the resources available (e. g., --cpus='0.5' makes it only use 50% of one thread), so that even if the docker image came with a mining malware, it won’t instantly burn your computer
  3. Better portability:

    • You can just copy all your container images, go to a new computer with a different Linux distro (or even OS), and expect them to work (as long as Docker or Podman can be installed)

While most of the above mentioned advantages are not very relevant with Hugo, I picked it as the example because it’s a typical tool that covers most of the aspects of running an executable with Docker.

More Examples

Here are some more practical examples that I actually used.

I created a backup for a MongoDB 5.0 instance while keeping my locally installed mongodump version 4.2 (note the use of bash commands (pwd, id -u, and date), which made it adaptive):

docker run --rm \                               # remove container when done
   -v $(pwd):/workdir/ -w /workdir/ \           # mount current dir to container
   -u $(id -u) \                                # specify uid to avoid permission issues
   mongo:5.0 \                                  # use same version of docker
   mongodump \                                  # use mongodump instead of the default
   --uri 'mongodb://host:port/db' \             # uri to db, must work even in container
   --archive=filename_$(date +%Y%m%d).archive \ # specify filename with current date
   --gzip                                       # compress the dump to save space

I ran the following to restore a MySQL 5.7 database from a .sql file dumped by someone else after failed to do so with a GUI MySQL client probably due to some driver compatibility issues (note the use of a pipe and the docker flag -i):

cat backup.sql | docker run -i --rm mysql:5.7 mysql -h <host> -P <port> -u <user> -p<password> -D <database>

Last modified on 2023-01-06