Use (B)ridge (M)arkup (L)anguage with Docker
This post describes how BML is used in combination with Docker.
Introduction
The purpose of the Bridge Bidding Markup Language (BML) is to offer an easy way of documenting contract bridge bidding systems. The file(s) created are supposed to be easy to read for both human and machines.
Please have a look at the BML User Guide for more information.
You can see the code here: GitHub BML project.
Please note that this is not a Docker tutorial.
BML is installed with utilities that need Python to run. And one of the utilities creates LaTeX files which need to be processed by a LaTeX program in combination with Perl. And there is a Makefile distributed with the BML source in order to facilitate running those programs but that needs make. Since installing all those programs may be difficult for a user, I thought: “Why not Dockerize it?”.
That may also serve as good starting point for a web site where you can upload your BML file(s) and receive the generated files. Something like that is ideal using Node.js in combination with Docker.
Analysis
The Docker image can be based on node:alpine since that is a minimal image already containing Node.js.
So the programs we need are:
- Perl
- LaTeX
- Make
- Python (and pip)
- BML utilities
Alpine Linux
Thanks to the Alpine Linux package repository it was easy to find the package names for the programs.
The Alpine Linux package installer is called apk
.
Perl
The command apk perl
will add perl.
LaTeX
In order to keep the Docker image small I just want to install the
LaTeX packages needed for BML. Well, that is feasible since
the expected BML test TeX files contain already all the possible TeX
\usepackage{}
directives.
So the idea is to create PDF files from those expected test TeX files after the minimal installation just to verify that everything is there.
There are several LaTeX distributions like TeX Live and MiKTeX. Since MiKTeX is fairly small and has a Docker image I tried that first but I could not get it working.
After some searching I found TinyTeX, even smaller than MiKTeX. And very simple to install. In fact you have to download a script that will do the rest of the installation:
wget -qO- "https://yihui.org/tinytex/install-unx.sh" | sh -s - --admin --no-path
Since wget is not installed by default the command apk wget fontconfig
freetype gnupg
will install the prerequisites for TinyTeX.
Make
The command apk make
will add make.
Python
The command apk python3 py3-pip py3-setuptools
will install:
- python 3
- pip 3
- setuptools (needed to install BML on Docker)
BML utilities
The command pip install bridge-markup
should do the trick. Can only be done
after python
and setuptools
have been installed.
Implementation
You should try to minimize the number of layers so one must try to keep the number of commands small by chaining commands (cmd1 && cmd2 && …) if possible.
BML Dockerfile
This is the (simplified) BML Dockerfile (see also the reference):
FROM node:alpine
LABEL Description="Dockerized BML" Vendor="Gert-Jan Paulissen"
# 1) Install:
# - perl, wget and libraries needed for TinyTeX (https://yihui.org/tinytex/)
# - make
# - python3
# - su-exec
# 2) Create user bml and show its details
RUN apk update && apk upgrade && apk add --no-cache perl wget fontconfig freetype gnupg make python3 py3-pip py3-setuptools su-exec &&\
addgroup -S bml && adduser -S -G bml bml && getent passwd bml
# This is bml root directory
WORKDIR /bml
COPY . .
# Install TinyTeX as bml
USER bml
# Prepare to install TinyTeX system wide (see https://yihui.org/tinytex/faq/)
RUN wget -qO- "https://yihui.org/tinytex/install-unx.sh" | sh -s - --admin --no-path
# Back to root to continue with some tasks
USER root
# 1) install TinyTeX in /usr/local/bin
# 2) install BML and test that the executables are there
# 3) modify permissions for /bml
RUN ~bml/.TinyTeX/bin/*/tlmgr path add &&\
pip3 install -e . && which bml2bss bml2html bml2latex bss2bml bml_makedepend &&\
chown -R bml:bml . && chmod -R 755 .
USER bml
# 1) Install missing LaTeX packages (by looking at errors from next statement)
# 2) Generate some PDFs to test a complete LaTeX installation
RUN tlmgr install dirtree listliketab parskip pbox txfonts &&\
latexmk -pdf -output-directory=/tmp /bml/test/expected/example.tex /bml/test/expected/example-tree.tex
ENTRYPOINT ["/bml/entrypoint.sh"]
# This is the place where input files are expected and where we run make from
WORKDIR /bml/files
# Default command
CMD ["make", "-f", "/bml/bml.mk", "help"]
First RUN
The first RUN command installs (as user root) perl, make, python3 and su-exec. The latter is a utility to run commands as another user when one is logged in as root.
And this RUN creates a group and user bml that should run the container.
Second RUN
As user bml get the TinyTeX shell script and prepare to install it system wide.
Third RUN
Back to user root again to:
- install TinyTeX in /usr/local/bin
- install BML and test that the executables are there
- modify permissions for /bml
Fourth (and last) RUN
As user bml:
- install missing LaTeX packages
- generate some PDFs to test a complete LaTeX installation
We are now sure that LaTeX works for any generated TeX file since all the possible variations of LaTeX packages needed have been tested.
ENTRYPOINT and CMD
The ENTRYPOINT and CMD:
- ENTRYPOINT [“/bml/entrypoint.sh”]
- CMD [“make”, “-f”, “/bml/bml.mk”, “help”]
This means that by default the container will start with:
/bml/entrypoint.sh make -f /bml/bml.mk help
You can not (or better, almost not) change the ENTRYPOINT, but you can easily change the CMD allowing you to do whatever needed on the container.
Best practices prescribe not to run the container as root so I have created a user bml that will run the container, but there may be situations where you need to run as another user (nor root, nor bml). In that case you start the container as root first with the wanted user id and group id passed as environment variables:
docker run -u 0:0 -e UID=<uid wanted> -e GID:<gid wanted> ...
Please note that -u 0:0
means that the container will start with user id (uid) 0 and
group id (gid) 0, which is root.
This is the entrypoint.sh
script:
#!/bin/sh
test -z "$DEBUG" || { id && set -x; }
# current user and group id
uid=`id -u`
gid=`id -g`
# wanted user and group id (default to current)
UID=${UID:-$uid}
GID=${GID:-$gid}
# user and group id of current user equal to wanted user?
if [ "$uid" -eq "$UID" -a "$gid" -eq "$GID" ]
then
# just spawn the command
exec "$@"
else
# must create a host user and group, add it to group bml (owner of software) and spawn if that succeeds
addgroup --gid $GID host_user && \
adduser --disabled-password --gecos "" --ingroup host_user --no-create-home --uid $UID host_user && \
adduser host_user bml && \
exec su-exec host_user "$@"
fi
If the current and wanted user (and group) are the same then the command is
just spawned as the current user. If not, the wanted user is created with name
host_user
and the command is spawned by that user. Please note that su-exec
will only work as the current user is root.
WORKDIR
The container working directory for BML is /bml/files. This directory can be mapped to a Docker volume or a bind mount, a file or directory on the host machine that is mounted into a container. This setup allows us to generate BML output on a host machine using a Docker container.
Usage
The following command creates a file test/data/example.pdf from the test/data/example.bml file:
make -f docker.mk run CMD="make -f ../bml.mk example.pdf"
Please note that by default the container /bml/files is mapped to the (full path of the) test/data folder as indicated by the make variable BML_FILES.
For more help just run:
make -f docker.mk run
Conclusion
I hope that this simple Docker guide has give you some appetite. And I must say that I was happy to combine new (Docker) and old (make, LaTeX) software to create something that I can use for the next step: creating a web interface for BML.
Stay tuned!