Building Django documentation in container
Table of Contents
Just like running tests, building documentation can have dependencies that we might not want to install on our workstation or laptop. Using containers we isolate the software installation to the container image, and we can run processes privilege-separated from our user account.
Building Django documentation
Django project's documentation about building documentation says
Django's documentation uses the Sphinx documentation system, which in turn is based on docutils.
and it goes on to recommend
pip install Sphinx
as a way to get Sphinx.
My workstation has only minimal software set installed to get work done and it all gets installed via rpm. I could install the python-sphinx — but I prefer not to have extra software on my root filesystem.
The Django documentation then goes on suggesting
so if we are to use container, we have to install the make package there as well.
Minimal container image
A reasonable start will therefore be
FROM fedora RUN dnf install -y make python-sphinx && dnf clean all
We can then build image
docker build -t django-docs docs[ ... ] Installing : python-sphinx-1.2.3-4.fc23.noarch 43/44 Installing : make-1:4.0-5.1.fc23.x86_64 44/44 [ ... ] Successfully built 5367375bc73a
Giving container access
With the container image build, we can attempt to build the HTML documentation:
docker run --rm -ti -u nobody --security-opt=label:disable \ -v $PWD/docs:/django-docs:ro django-docs make -C /django-docs htmlmake: Entering directory '/django-docs' [ ... ] OSError: [Errno 30] Read-only file system: '/django-docs/_build' Makefile:49: recipe for target 'html' failed make: *** [html] Error 1 make: Leaving directory '/django-docs'
We run the container as UID
with SELinux type
spc_t to allow the processes
to access the content of our Django repository working tree
(likely in our home directory)
that we mount read-only as
As we can see, the
make attempts to create
_build/ directory to store the output.
Since the filesystem is mounted read-only, that operation fails.
Let's create the target directory and make it available to the documentation building processes:
mkdir -p docs/_build
chmod o+wt,g+s docs/_build
By using the sticky bit with
chmod +t, we allow
nobody user in the container to create
the output directory there.
With the setgid bit, any content that the container will
create will have parent's group, making it easy to manipulate
(and delete) the directory structure from outside of the container,
with our account which created the directory.
We can now run the make html in the container, bind-mounting the target location as read-write:
docker run --rm -ti -u nobody --security-opt=label:disable \ -v $PWD/docs:/django-docs:ro -v $PWD/docs/_build:/django-docs/_build \ django-docs make -C /django-docs htmlmake: Entering directory '/django-docs' sphinx-build -b djangohtml -n -d /django-docs/_build/doctrees -D language= . /django-docs/_build/html Making output directory... Running Sphinx v1.2.3 Sphinx version error: This project needs at least Sphinx v1.3 and therefore cannot be built with this version. Makefile:49: recipe for target 'html' failed make: *** [html] Error 1 make: Leaving directory '/django-docs'
The package version is lower than what the documentation-building tools expect. Since Fedora 23 which we use as our base image does not have newer version of Sphinx packaged (yet), using
RUN dnf install -y 'python-sphinx >= 1.3'
would not help us. Let's use the pip install to install the software instead:
RUN dnf install -y make python-pip && dnf clean all RUN pip install 'Sphinx >= 1.3'
Note that we had to install python-pip via rpm to get pip in the first place.
Re-running the docker run command now shows it's using Sphinx 1.4.1 and it will end with
build succeeded, 2 warnings. Build finished. The HTML pages are in /django-docs/_build/html. make: Leaving directory '/django-docs'
Since we've bind-mounted the directory
docs/_build/ to the
container, that's where we will find subdirectory with
the rendered HTML pages, just like we would if we had run
make directory on the host machine.
We can make the docker run invocation
a little simpler
by declaring the UID to use in the
as well as the command to run, which includes
umask to make the directories writeable
by group — useful to be able to the purge the directory
with our user when we no longer need it.
The final docs/Dockerfile could then be
FROM fedora # The python-sphinx in Fedora 23 is older than what Django master expects # RUN dnf install -y make python-sphinx && dnf clean all RUN dnf install -y make python-pip && dnf clean all RUN pip install sphinx RUN ( echo '#!/bin/bash' ; echo 'umask 0002 ; make -C /django-docs "$@"' ) > /bin/django-makedocs RUN chmod a+x /bin/django-makedocs ENV PYTHONDONTWRITEBYTECODE 1 USER nobody ENTRYPOINT [ "/bin/django-makedocs" ]
After building the container image with
docker build -t django-docs docs
HTML documentation can be generated by running
mkdir -p docs/_build
chmod g+s,o+wt docs/_build
docker run --rm -ti --security-opt=label:disable -v $PWD/docs:/django-docs:ro -v $PWD/docs/_build:/django-docs/_build django-docs htmlmake: Entering directory '/django-docs' sphinx-build -b djangohtml -n -d _build/doctrees -D language= . _build/html Running Sphinx v1.4.1 making output directory... WARNING: while setting up extension djangodocs: directive 'versionadded' is already registered, it will be overridden WARNING: while setting up extension djangodocs: directive 'versionchanged' is already registered, it will be overridden loading translations ... not available for built-in messages loading pickled environment... not yet created loading intersphinx inventory from https://docs.python.org/3/objects.inv... loading intersphinx inventory from http://initd.org/psycopg/docs/objects.inv... loading intersphinx inventory from https://django-formtools.readthedocs.io/en/latest/objects.inv... loading intersphinx inventory from https://pythonhosted.org/six/objects.inv... loading intersphinx inventory from http://sphinx-doc.org/objects.inv... building [mo]: targets for 0 po files that are out of date building [djangohtml]: targets for 363 source files that are out of date updating environment: 363 added, 0 changed, 0 removed reading sources... [100%] topics/testing/tools looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [100%] topics/testing/tools generating indices... genindex py-modindex writing additional pages... search copying images... [100%] intro/_images/admin14t.png copying downloadable files... [100%] ref/contrib/gis/install/geodjango_setup.bat copying static files... done copying extra files... done dumping search index in English (code: en) ... done dumping object inventory... done writing templatebuiltins.js... build succeeded, 2 warnings. Build finished. The HTML pages are in _build/html. make: Leaving directory '/django-docs'
The steps above were working on Fedora 23 with docker-1.10.3-16.gita41254f.fc23.x86_64 and Django 1.9.x and master (before 1.10) branches.