If you maintain an existing A+ course, and it uses a Python virtual
“virtualenv”), this section describes how to convert it to the Docker
An A+ course not using Docker does not have docker-compile.sh and
docker-up.sh scripts. Instead, the course is typically compiled with
commands source venv/bin/activate and make html. A Python virtualenv is
more like a Python package manager compared to Docker; each virtualenv installs
a specific version of Python and specific version of libraries. A+ Docker
containers also have that, but moreover, they have all the software
preconfigured and ready to use. In contrast, developing a virtualenv A+ course
with also A+ and mooc-grader installed on your computer with virtualenv requires
extra manual configuration steps. Therefore the aim of A+ Docker containers is
to make course development and deployment as easy as possible.
From now on, this text assumes that you have your current A+ course under
git version control.
First, cd to the current directory of your course. Create a new git branch
for the Docker version.
git branch docker
git checkout docker
It is recommended to add a to-do document for making the Docker version, for
example, Docker-TODO.txt in the main directory.
This is the to-do list for making a Docker version of this course.
- copy files from aplus-manual
- update conf.py
- check RST files
- modify exercise graders
Some people and teams prefer to have their task lists in Trello. Do what suits you best.
Create a directory old and move all the current files into it. This
directory will have all the course material which is not yet converted to the
Docker version. The idea is to move files back from there in small groups and
test them. This way you will easily see what is done, and the rest of the files
will not cause Sphinx compilation errors or other trouble.
git mv -k * old
Note that the hidden subdirectory .git, and files .gitignore and
.gitmodules will not be moved, as they should.
Commit and push to Github, Aalto Gitlab or whatever git service you have. The
--set-upstream option is used only this time; it allows you to later say
just git push and it will automatically push your commits to the docker
branch on the remote git service.
git commit -m "First commit of Docker version"
git push --set-upstream origin docker
There is a minimal code template for a new A+ course using Docker. For your
information, the A+ learning management system has its own
Github page, and one of the projects there is
It has one branch, master, which contains this A+ manual. (The repository
used to have more branches for different kinds of examples.)
If your course has only exercises and defines the main index.yaml
configuration file, you do not need any RST files, Makefile for Sphinx nor
the a-plus-rst-tools submodule.
If your course has the index.rst file, use a similar structure for the course
as the A+ manual has. Download the ZIP file of the branch from Github and extract it to
your course directory. Read the README.md file.
(When A+ 1.3 is used,) add directory _data to the .gitignore file of your course. That
directory is a write-enabled directory for A+ and mooc-grader which can always
be removed. (Starting with A+ 1.4, the _data directory is usually not used
anymore. Instead, the run-aplus-front and run-mooc-grader containers should
write their data to a data volume, which is set in the docker-compose.yml file.)
Add the latest A+ RST tools as a git submodule.
echo _data >> .gitignore
git submodule add https://github.com/Aalto-LeTech/a-plus-rst-tools
git submodule init
git submodule update
Add directory old to exclude_patterns in file conf.py. This way
Sphinx will not compile material which is in the old directory.
Note: If your course has custom Sphinx directives, don’t worry. This chapter
will describe later how to include them into the Docker version of your course.
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build', '_data', 'exercises/solutions', 'old']
Finally, add all new files, commit and push.
git add *
git commit -m "Added A+ course-template"
Congratulations! Now you have all the initial git voodoo done. You have a new
branch on your course repository, which has a working copy of the A+ manual and
your current course material in the old directory. You can compile the
material and run A+ and mooc-grader locally as specified in the introductory
Your course might have custom Sphinx directives. If you have those, they are
probably now in the directory old/extensions the .py files. Some of them
might even require A+ RST tools, meaning that they have lines such as from
a_plus_rst_tools import aplus_nodes. This section describes how to include
those to the Docker version of your course.
a_plus_rst_tools import aplus_nodes
Currently, the A+ manual has two custom directives in the extensions
subdirectory: bootstrap_styled_topic.py and div.py. Let’s assume the
custom Sphinx directives of your course are currently in the directory
If there are Sphinx directives (.py files) with similar name both in
extensions and old/extensions, check whether they differ. That can
be done with your text editor, or with the command
diff extensions/NAME.py old/extensions/NAME.py in the shell; see
man diff or the GNU Diffutils page.
diff extensions/NAME.py old/extensions/NAME.py
For those files which differ, you need to know which one is more recent
and who has modified the file. You might like to try
git blame extensions/NAME.py and git blame old/extensions/NAME.py.
The git blame command shows for each line of a file when and who has
changed it. This might help contacting the authors in case you have not
written the Sphinx directives yourself. Finally decide whether to keep
the A+ manual version, your version, or merge manually the files.
git blame extensions/NAME.py
git blame old/extensions/NAME.py
Custom Sphinx directives not matching step 1 can just be moved to the
right place, e.g. git mv old/extensions/NAME.py extensions/NAME.py.
git mv old/extensions/NAME.py extensions/NAME.py
For the custom Sphinx directives which have a couple of
from a_plus_rst_tools import in them, chances are you have a symbolic
link a-plus-rst-tools in the
old/extensions directory, which points to the a_plus_rst_tools
subdirectory. That latter directory may have some specific, maybe old
version of A+ RST tools. This kind of hack has been made because normally
A+ RST tools exists as directory a-plus-rst-tools. This is an invalid
Python module name, and therefore the directory has been renamed to
a_plus_rst_tools in order to import Python functions from it in the
custom Sphinx directive. Moreover, a symbolic link a-plus-rst-tools has
been created to it, because A+ RST is cloned from Github by default that
from a_plus_rst_tools import
% cd old/extensions
% ls -l
-rw-r--r-- 1 atilante domain users 1273 Jun 5 13:07 aplus_exercise.py
drwxr-xr-x 5 atilante domain users 4096 Jun 5 13:31 a_plus_rst_tools/
lrwxrwxrwx 1 atilante domain users 16 Jun 5 13:07 a-plus-rst-tools -> a_plus_rst_tools/
-rw-r--r-- 1 atilante domain users 4346 Jun 5 13:07 aplus_submit.py
-rw-r--r-- 1 atilante domain users 2715 Jun 5 13:07 bootstrap_button_collapse.py
-rw-r--r-- 1 atilante domain users 3487 Jun 5 13:07 bootstrap_panel_table.py
-rw-r--r-- 1 atilante domain users 1628 Jun 5 13:07 bootstrap_styled_topic.py
-rw-r--r-- 1 atilante domain users 3147 Jun 5 13:07 div.py
drwxr-xr-x 2 atilante domain users 4096 Jun 5 13:45 __pycache__/
-rw-r--r-- 1 atilante domain users 5060 Jun 5 13:07 sql_submit.py
-rw-r--r-- 1 atilante domain users 1809 Jun 5 13:07 submit_no_tests.py
-rw-r--r-- 1 atilante domain users 1116 Jun 5 13:07 yaml_extras.py
Likely you want to use the latest A+ RST tools with your custom Sphinx
directives. In that case, create a symbolic link from the new
ln -s ../a-plus-rst-tools a_plus_rst_tools
As a later development step, you may want to check whether the functionality
of your custom Sphinx directives is actually included in the latest A+ RST
Next you will have to merge old/conf.py to conf.py. Copy lines from
the former to the latter. Run ./docker-compile.sh to ensure that nothing
Could not import extension my_directive (exception: No module named 'my_directive')
Makefile:60: recipe for target 'html' failed
make: *** [html] Error 1
You have my_directive in conf.py in the list extensions, but Sphinx
cannot find it. Have you moved the file to the right directory? Sphinx can
only find custom directives from directories which are declared in conf.py
with sys.path.append. For example, if you need to place your directive
into directory extensions/mydir, put the following into conf.py: