On a number of occasions the question of tutor as a replacement for devstack has come up. Since quite some time now I’m mostly using Tutor as my devstack, so I figured I’d share how I’m using it in the hopes that it will help other people.
There are many ways in which I’ve come to really enjoy using Tutor as a devstack replacement. There are some things it enables that were much harder with the old devstack.
I’ll start with some key facts about Tutor, some that I discovered late. Some or none of these may be news to you as they were at some point for me:
- Tutor is a regular Python package.
- Each major version deals with a new release of Open edX, so v16 is for Palm, v15 was for Olive and so on.
- You shouldn’t try to install Palm with v15 or vice versa.
- Upgrading between releases should be as simple as, install new version of Tutor and run
tutor launch
again. - New features of tutor itself can land between major releases, but often new Tutor features also come with a new major release
- The version for the master branch is called tutor nightly, and it installs and is configured separately.
- It is entirely pluggable using actions and hooks
- Actions let you do things as a response to tutor events
- Hooks like you change existing configuration, add commands, add more docker images, services etc.
- It uses Jinja2 internally and config variables are plugged into templates to produce Dockerfiles, settings files etc.
- You don’t need to create a whole python package for a plugin, you can just create one file and put it in the right place.
Tutor can handle 3 types of deployments, dev
for development (a devstack replacement), local
, which runs a production install via docker-compose on your local system, and k8s
which installs to Kubernetes.
There are command that apply globally, such as config
, mounts
, plugins
etc. and then there are command that can be run for a specific environment. For instance, you can use tutor config
to set up a config, tutor plugins
to install, enable/disable plugins and tutor mounts
to handle mount points. These will all update your config file. Then using this singular config file, you can use tutor dev
to launch a devstack, tutor local
to launch a local docker compose based production deployment and tutor k8s
to deploy to Kubernetes. This means if you want to test a client’s setup locally, you can just use their config file to launch a devstack (make sure to not use the same DB etc).
When you run tutor config save
tutor will load your config file, and from it build all required configuration, such as Caddyfiles, edx-platform yaml and Django settings.py files, Dockerfiles for each service, docker-compose files for local and dev deployments, Kubernetes config etc. Then running tutor dev/local/k8s will use this config to deploy and launch services. After they are built, you can go and edit them. This is especially useful during development, since you can go edit any file directly and the changes should reflect.
If you want tutor to use a particular config file, just put it in an empty directory, and run tutor -r path/to/dir
and it will build the config in that directory. You can also specify this path using the TUTOR_ROOT
environment variable.
I tend to create new tutor environments quite often. So I have a folder for all instances, for each instance I create a subfolder for its config. I also use pyenv
+ virtualenv to create a virtulenv for each version. I have this set in an .envrc
file that is used by a tool called direnv to automatically load environment variables when you enter a directory and clear them when you leave it. So I can just cd into the config directly, and the everything is set up to use that tutor environment! You can use this to run multiple devstacks at once.
Here is a sample .envrc file for my palm environment:
export TUTOR_ROOT=$PWD
layout pyenv palm
Here the first line sets the tutor root to the current directory, and the next line tells direnv to use the palm python virtualenv from pyenv. You can also do this without pyenv but I won’t go into details yet, but that link has more. This way you can create a new tutor environment for each ticket and then delete it when no longer needed.
Common devstack operations
Use your own version of edx-platform, especially with nightly
Use tutor mounts
for this, and point it to your edx-platform
directory and others as needed. This will then use that directory, changes are automatically picked up just like with the old devstack (if using tutor dev
).
Install a python package / xblock to test
If you just want to quickly test it, not need to go through the huge process of adding it to the config and rebuilding images. Just directly install the package in the docker container. You can run tutor dev exec lms bash
to get a bash prompt in the lms, or just directly do pip install here. I’ve created a quick plugin that helps me quickly test PRs and MRS here. It will use the PR to get the name of the branch that should be installed and calls pip install on that in the lms and cms.
Test a theme
The tutor docs are pretty good here.
Develop MFEs
This is a bit painful with both devstacks. You can mount the MFE and it should automatically hot-reload sometimes, but the second you start dealing with custom themes overriding component etc it doesn’t work. When you install a package from a local directory it creates a symlink, so changes should automatically be picked up. For example, if you are testing a branding package intalled locally then you can edit the theme and it should update in your app. However, if it’s running in a container, the symlink is not valid in there (unless you install using a relative path and also mount that path inside the container).
So I prefer running MFEs outside of docker. Each MFE contains a .env.development file that is designed to work with the regular devstack, not tutor. However, you can modify it to work with tutor as well. Just replace localhost:18000
→ local.overhang.io:8000
and localhost:18010
→ studio.local.overhang.io:8001
.
This isn’t enough still, because you need to access the MFE using apps.local.overhang.io
and that work work just yet. So create the following webpack.dev.config.js
file in the MFE dir:
const { createConfig } = require('@edx/frontend-build');
const config = createConfig('webpack-dev');
config.cache = { type: 'filesystem' }; // This isn't needed but really speeds up rebuilds!
config.devServer.allowedHosts = [
'apps.local.overhang.io',
];
module.exports = config;
It’s possible that such a file already exists, in which case, just add the allowedHosts bit from above.
I hope this helps!