Open edX® Consulting

I am a 20-year veteran web developer specializing in the Open edX® platform. Thousands of organizations around the world have used my Open edX® blog articles to turn their online education vision into reality. In fact, my how-to guides and tutorials on production installation, platform scaling, custom theming, and configuration have been viewed more than fifty thousand times.

I am based in Mexico City. I provide personalized service and fast response to clients located around the world. All of my customers have my personal mobile phone number and can reach me any time by voice, whatsapp or email. My Portfolio, Client List and Recommendations are all available on this site.

  • Free initial consultations in English and Spanish.
  • Are you in planning stages of an Open edX® project?
  • Are you looking for a low-risk deployment solution and an Open edX® service provider with a proven track record?
  • Do you need affordable full-service administration and technical support?

Services That I Provide to the Open edX® Community


Production Installation & Configuration of Open edX®

I have completed more than 50 production installations for clients around the world. I publish technical articles that fully describe all of my methods for preparing your Open edX® platform for production use, including off-site data backup, setting up SMTP email, adding SSL certificates, and taking care of SEO.

I am an expert in configuring the Open edX platform for a variety of common use cases including multi-language and e-commerce enabled platforms of widely varying scale. Open edX® is a mature and stable platform that is also highly configurable. Many customization requests can be implemented via the platform's many configuration parameters. However, configuring the Open edX® platform is a knowledge and experience-intensive endeavor. Properly managing your configuration is also something of a trade craft which you can read more about, here, Open edX Configuration Management Tutorial

Custom Theming

You can customize the appearance of your Open edX® platform by creating and implementing a custom theme. In addition to creating a turn-key custom theming solution for your organization, I have also worked with many organizations around the world to train their inhouse graphic and web designers on how to re-style the look & feel of the platform as well as how to add custom content. You can read more, here, Open edX Custom Theming Tutorial and here, Styling Open EdX Tips and Tricks

Scaling and Capacity Planning

I work with clients around the globe to ensure that their users' experiences are performant and reliable. Open edX® is massively scalable, as you can see from edx.org, one of the largest MOOC sites in the world, which runs on Open edX®. The Open edX® platform leverages considerable open source community know-how and best practices in order to maximize its configurability and scalability. Mind you however, scaling is a highly technical topic, and even more so in the case of a complex platform like Open edX®. You can read more, here, Scaling Open edX

Administration & Devops

I provide front-line support for all versions of the Open edX® platform to clients around the world. I also provide technical training to organizations' inhouse IT teams in order to help them prepare for common production live support situations.

Custom Programming

Most clients never require any custom programming. The Open edX® platform is highly configurable, and, it is extensible via its pluggable XBlock architecture. In the vast majority of cases, my clients find the custom features they require from existing XBlocks which I can typically install for them in only a few hours. If you're one of the exceptions to his rule however then let's talk. I have an extensive portfolio of low-level programming projects using Python-Django and ReactJS.

Advisory & Training

I advise project teams around the world, providing expert advise on technical best practices, project and risk management, and relevant context about technical strategies within the Open edX® platform.


Open edX® Client List

Open edX® Code Samples & Publications

These are a combination of managed GitHub repositories that I use for instructional purposes, combined with repositories belonging to open source organizations to which I am the maintainer. Most of these also demonstrate best practices for software management including pre-commit, linting techniques, and devops methods for automating unit testing, documentation and CI-CD operations for build, deploy and code dependency package updates.

Repository
cookiecutter-openedx/cookiecutter-openedx-devops

Powered by Cookiecutter <https://github.com/cookiecutter/cookiecutter>, Cookiecutter Open edX Devops Tools is a community maintained project template for jumpstarting production-ready, Kubernetes <https://kubernetes.io/>-based installations of the Open edX <https://openedx.org/>_ online learning management system running on AWS <https://aws.amazon.com/>_ cloud infrastructure that is built and managed with fully parameterized Terraform <https://www.terraform.io/>_ and Github Actions <https://docs.github.com/en/actions>_ automation scripts. The template offers a number of generation options, we invite you to check the dedicated docs <https://github.com/cookiecutter-openedx/cookiecutter-openedx-devops/tree/main/doc>_ pages to learn more about each of them.

Full Stack
⭐🍴👁🐛
5018503
Last commit: Sep 2024
Total commits: 875

HCL (76.3%)
Shell (15.4%)
Smarty (6.7%)
cookiecutter-openedx/openedx-plugin-example

An Open edX plugin showcasing a curated collection of code samples for extending and modifying the stock functionality of an Open edX installation using the built-in plugin architecture. This plugin can be installed in any Open edX installation version Juniper or later, independent of the installation method. This plugin currently runs in native builds, tutor installations, and Cookiecutter-openedx Kubernetes, amongst other proprietary installation methodologies. Open edX official documentation recommends that you leverage its plugin architecture rather than fork its code repositories. This project provides more than two dozen code examples to help you accomplish this. Usage:

pip install openedx-plugin-example

Technical features and coding techniques that are showcased in this repo include:

  • Iterate and introspect structured course content
  • Custom oauth backend that seamlessly integrates with Open edX third party authentication
  • Custom url to auto-enroll students in a course
  • Automatically detect and adapt to the browser OS locale setting
  • Usage examples of Open edX's extensive library of Python decorators
  • Open edX Django configuration settings
  • Open edX Django urls
  • Open edX Django logging
  • Open edX Django signals
  • Open edX Django RestFramework custom api
  • Django app setup
  • Django models
  • Django and Mako templating
  • Django static assets
  • Django Admin
  • Django middleware
  • Django manage.py custom commands
  • Django Waffle flags
  • Python environment variables
  • How to bundle multiple plugins in a single pip package
  • How to redirect Open edX urls in lms and cms to endpoints created in this plugin
  • How to automatically initialize Django model data during app startup
  • Adding unit tests to plugin code
  • Semantic version control
  • Pre-commit with linting by flake8 and black, both of which are configured to match the opinionated styling that you'll find in Open edX repositories
  • pip configuration, requirements, constraints, setup.py, pyproject.toml
  • Publishing to PyPi
Full Stack
⭐🍴👁🐛
199192
Last commit: May 2024
Total commits: 140

Python (90.7%)
HTML (7.6%)
Makefile (1.0%)
Full Stack
⭐🍴👁🐛
2520
Last commit: May 2022
Total commits: 3

cookiecutter-openedx/tutor-contrib-k8s-deploy-tasks

tutor plugin to manage deployment tasks that are exclusively (or mostly) specific to Kubernetes deployments.

  • oauth misconfiguration. tutor relies on an environment variable ENABLE_HTTPS to determine the protocol of the production oauth client for applications that rely on the LMS oauth service. For Kubernetes installations however, this value needs to be set to false which results in ./manage.py lms create_dot_application misconfiguring the oauth client for some, but not all, of these oauth clients. This plugin reconfigures the oauth clients of cms, discovery, ecommerce and credentials to use https protocol for redirect URI.
  • Nutmeg upgrade and initializataion tasks There are a few manage.py tasks that need to run for platforms that are upgrading to Nutmeg or newer from Maple or older. This plugin runs those tasks for you. For more information see Open edX Nutmeg Release <https://edx.readthedocs.io/projects/open-edx-release-notes/en/latest/nutmeg.html>_
  • Missing user profile records. User accounts created with manage.py lack a corresponding auth_userprofile record, which causes a 500 exception during login for that account. Adds a blank record in cases where a record is missing.
  • MFE misconfiguration. tutor-mfe relies on the value of ENABLE_HTTPS when generating the dict MFE_CONFIG, which in the case of k8s deployments will result in the browser blocking content due to "Mixed content". This plugin overrides the results of tutor-mfe openedx-lms-production-settings, replacing protocol produced by logic relying on ENABLE_HTTPS (assumed to result in 'http') with the hard-coded value 'https'.
  • Xblock storage configuration. creates this custom storage configuration <./tutork8s_deploy_tasks/patches/openedx-common-settings>_ designed to leverage this custom kubernetes ExternalService <https://github.com/lpm0073/cookiecutter-openedx-devops/blob/main/%7B%7Bcookiecutter.github_repo_name%7D%7D/terraform/environments/modules/kubernetes_ingress_clb/manifests/proxy-service.yml.tpl>_ and ingress <https://github.com/lpm0073/cookiecutter-openedx-devops/blob/main/%7B%7Bcookiecutter.github_repo_name%7D%7D/terraform/environments/modules/kubernetes_ingress_clb/manifests/ingress-scorm-proxy-service.yml.tpl>_ created by Cookiecutter for supporting AWS S3 storage for Xblocks.
Cloud
⭐🍴👁🐛
1615
Last commit: Jun 2025
Total commits: 99

Python (71.2%)
JavaScript (16.4%)
Makefile (12.4%)
cookiecutter-openedx/cookiecutter-openedx-plugin

An Open edX Plugin that implements customizations for deployment to Kubernetes. Implements the following:

  • Hooks for openedx Django Signals for 'user_logged_in', 'user_logged_out', 'register_user', 'course_enrollment_created', 'certificate_created' and more. Demonstrates both the legacy, and the newer methodology for subscribing to and listening for the signals.
  • Scaffolding for waffle flag setup, including Django model initializations. These are currently only used to enable Django Signals.
  • A custom badges backend that is compatible with django-storages backend for Amazon S3
Full StackCloud
⭐🍴👁🐛
3030
Last commit: Apr 2023
Total commits: 20

Python (94.4%)
Makefile (5.6%)
cookiecutter-openedx/edx-oauth2-wordpress-backend
Open edX OAuth2 Backend for Wordpress ===================================== .. image:: https://img.shields.io/static/v1?label=pypi&style=flat-square&color=0475b6&message=edx-oauth2-wordpress-backend :alt: PyPi edx-oauth2-wordpress-backend :target: https://pypi.org/project/edx-oauth2-wordpress-backend/ .. image:: https://img.shields.io/badge/hack.d-Lawrence%20McDaniel-orange.svg :target: https://lawrencemcdaniel.com :alt: Hack.d Lawrence McDaniel .. image:: https://img.shields.io/static/v1?logo=discourse&label=Discussions&style=flat-square&color=ff0080&message=OpenEdx :alt: Open edX Discussions :target: https://discuss.openedx.org/ .. image:: https://img.shields.io/static/v1?label=WP-Oauth&style=flat-square&color=1054ff&message=Server :alt: WP Oauth :target: https://wp-oauth.com/ | Overview -------- An Open edX oauth2 backend for `Wordpress `_ `WP OAuth Server `_. - `Python Social Auth custom backend implentation `_ - `WP Oauth Wordpress Plugin Documentation `_ This is a strongly-typed implementation that leverages an in-depth knowledge of the WP Oauth return objects to generate verbose, informative log data in `lms.log <./doc/lms.log>`_ that will help you to quickly get third party authentication working on your Open edX installation. Usage ----- An example implementation for an Open edX installation named https://stepwisemath.ai/ 1. add this package to your project's requiremets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ add this package to your project's requiremets.txt, or install it from the command line. .. code-block:: shell pip install edx-oauth2-wordpress-backend 2. subclass WPOpenEdxOAuth2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Subclass oauth2_wordpress.wp_oauth.WPOpenEdxOAuth2, and configure for your Wordpress oauth provider. .. code-block:: python from oauth2_wordpress.wp_oauth import WPOpenEdxOAuth2 class StepwiseMathWPOAuth2(WPOpenEdxOAuth2): # This defines the backend name and identifies it during the auth process. # The name is used in the URLs /login/ and /complete/. # # This is the string value that will appear in the LMS Django Admin # Third Party Authentication / Provider Configuration (OAuth) # setup page drop-down box titled, "Backend name:", just above # the "Client ID:" and "Client Secret:" fields. name = "stepwisemath-oauth" # note: no slash at the end of the base url. Python Social Auth # might clean this up for you, but i'm not 100% certain of that. # # the following will create an authorization url of https://stepwisemath.ai/wp-json/moserver/authorize BASE_URL = "https://stepwisemath.ai" PATH = "wp-json/moserver/" AUTHORIZATION_ENDPOINT = "authorize" TOKEN_ENDPOINT = "token" USERINFO_ENDPOINT = "resource" 3. configure your Open edX lms application ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: yaml ADDL_INSTALLED_APPS: - "oauth2_wordpress" THIRD_PARTY_AUTH_BACKENDS: - "oauth2_wordpress.wp_oauth.StepwiseMathWPOAuth2" ENABLE_REQUIRE_THIRD_PARTY_AUTH: true add these settings to django.conf: .. list-table:: WP Oauth setup :widths: 50 100 :header-rows: 1 * - Key - Value * - WPOAUTH_BACKEND_BASE_URL - https://stepwisemath.ai * - WPOAUTH_BACKEND_CLIENT_ID - see: https://stepwisemath.ai/wp-admin/admin.php?page=mo_oauth_server_settings * - WPOAUTH_BACKEND_CLIENT_SECRET - see: https://stepwisemath.ai/wp-admin/admin.php?page=mo_oauth_server_settings * - SCOPE - basic email profile * - GRANT_TYPE - Authorization Code * - REDIRECT_URI - https://web.stepwisemath.ai/auth/complete/stepwisemath-oauth 4. Configure a new Oauth2 client from the lms Django Admin console ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. image:: https://raw.githubusercontent.com/lpm0073/edx-oauth2-wordpress-backend/main/doc/django-admin-1.png :width: 100% :alt: Open edX Django Admin Add Provider Configuration (OAuth) .. image:: https://raw.githubusercontent.com/lpm0073/edx-oauth2-wordpress-backend/main/doc/django-admin-2.png :width: 100% :alt: Open edX Django Admin Add Provider Configuration (OAuth) Cookiecutter openedx_devops deployment .. code-block:: shell tutor config save --set OPENEDX_WPOAUTH_BACKEND_BASE_URL="${{ secrets.WPOAUTH_BACKEND_BASE_URL }}" \ --set OPENEDX_WPOAUTH_BACKEND_CLIENT_ID="${{ secrets.WPOAUTH_BACKEND_CLIENT_ID }}" \ --set OPENEDX_WPOAUTH_BACKEND_CLIENT_SECRET="${{ secrets.WPOAUTH_BACKEND_CLIENT_SECRET }}" WP Oauth Plugin Configuration ----------------------------- This plugin enables your Open edX installation to authenticate against the WP Oauth plugin provider in your Wordpress web site, configured as follows: .. image:: https://raw.githubusercontent.com/lpm0073/edx-oauth2-wordpress-backend/main/doc/wp-oauth-config.png :width: 100% :alt: WP OAuth Server configuration page Sample lms log output --------------------- .. code-block:: shell 2022-10-06 20:17:08,832 INFO 19 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/auth/login/stepwisemath-oauth/", "context": {"user_id": null, "path": "/auth/login/stepwisemath-oauth/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/login", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {\"auth_entry\": [\"login\"], \"next\": [\"/dashboard\"]}, \"POST\": {}}", "time": "2022-10-06T20:17:08.832684+00:00", "event_type": "/auth/login/stepwisemath-oauth/", "event_source": "server", "page": null} 2022-10-06 20:17:09,230 INFO 19 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:216 - AUTHORIZATION_URL: https://stepwisemath.ai/oauth/authorize [pid: 19|app: 0|req: 2/19] 192.168.4.4 () {68 vars in 1889 bytes} [Thu Oct 6 20:17:08 2022] GET /auth/login/stepwisemath-oauth/?auth_entry=login&next=%2Fdashboard => generated 0 bytes in 430 msecs (HTTP/1.1 302) 9 headers in 922 bytes (1 switches on core 0) 2022-10-06 20:17:38,485 INFO 7 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/auth/complete/stepwisemath-oauth/", "context": {"user_id": null, "path": "/auth/complete/stepwisemath-oauth/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://stepwisemath.ai/", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {\"redirect_state\": [\"pdbIKIcEbhjVr3Kon5VXUWWiy5kuX921\"], \"code\": [\"q0antmap4qfamd6pe24jh75pdprahpdiyitmut0o\"], \"state\": [\"pdbIKIcEbhjVr3Kon5VXUWWiy5kuX921\"], \"iframe\": [\"break\"]}, \"POST\": {}}", "time": "2022-10-06T20:17:38.484675+00:00", "event_type": "/auth/complete/stepwisemath-oauth/", "event_source": "server", "page": null} 2022-10-06 20:17:38,496 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:223 - ACCESS_TOKEN_URL: https://stepwisemath.ai/oauth/token 2022-10-06 20:17:40,197 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:230 - USER_QUERY: https://stepwisemath.ai/oauth/me 2022-10-06 20:17:40,197 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:363 - user_data() url: https://stepwisemath.ai/oauth/me?access_token=jx2zql9fw2jx9s7tayik4ybfjrmuhb7m5csb1mtl 2022-10-06 20:17:41,965 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:368 - user_data() response: { "ID": "7", "display_name": "Test McBugster", "user_email": "test@stepwisemath.ai", "user_login": "testaccount", "user_nicename": "testaccount", "user_registered": "2022-10-06 19:57:56", "user_roles": [ "administrator" ], "user_status": "0" } 2022-10-06 20:17:41,966 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:269 - get_user_details() received wp-oauth user data response json dict: { "ID": "7", "display_name": "Test McBugster", "user_email": "test@stepwisemath.ai", "user_login": "testaccount", "user_nicename": "testaccount", "user_registered": "2022-10-06 19:57:56", "user_roles": [ "administrator" ], "user_status": "0" } 2022-10-06 20:17:41,966 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:317 - get_user_details() processing response object 2022-10-06 20:17:41,966 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:241 - user_details.setter: new value set { "date_joined": "2022-10-06 19:57:56", "email": "test@stepwisemath.ai", "first_name": "Test", "fullname": "Test McBugster", "id": 7, "is_staff": true, "is_superuser": true, "last_name": "McBugster", "refresh_token": "", "scope": "", "token_type": "", "user_status": "0", "username": "testaccount" } 2022-10-06 20:17:41,967 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:345 - get_user_details() returning: { "date_joined": "2022-10-06 19:57:56", "email": "test@stepwisemath.ai", "first_name": "Test", "fullname": "Test McBugster", "id": 7, "is_staff": true, "is_superuser": true, "last_name": "McBugster", "refresh_token": "", "scope": "", "token_type": "", "user_status": "0", "username": "testaccount" } 2022-10-06 20:17:41,972 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:269 - get_user_details() received extended get_user_details() return dict: { "access_token": "jx2zql9fw2jx9s7tayik4ybfjrmuhb7m5csb1mtl", "date_joined": "2022-10-06 19:57:56", "email": "test@stepwisemath.ai", "expires_in": 3600, "first_name": "Test", "fullname": "Test McBugster", "id": 7, "is_staff": true, "is_superuser": true, "last_name": "McBugster", "refresh_token": "", "scope": "", "token_type": "", "user_status": "0", "username": "testaccount" } 2022-10-06 20:17:41,973 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:241 - user_details.setter: new value set { "access_token": "jx2zql9fw2jx9s7tayik4ybfjrmuhb7m5csb1mtl", "date_joined": "2022-10-06 19:57:56", "email": "test@stepwisemath.ai", "expires_in": 3600, "first_name": "Test", "fullname": "Test McBugster", "id": 7, "is_staff": true, "is_superuser": true, "last_name": "McBugster", "refresh_token": "", "scope": "", "token_type": "", "user_status": "0", "username": "testaccount" } 2022-10-06 20:17:41,973 INFO 7 [oauth2_wordpress.wp_oauth] [user None] [ip 192.168.6.26] wp_oauth.py:290 - get_user_details() returning extended get_user_details() return dict: { "access_token": "jx2zql9fw2jx9s7tayik4ybfjrmuhb7m5csb1mtl", "date_joined": "2022-10-06 19:57:56", "email": "test@stepwisemath.ai", "expires_in": 3600, "first_name": "Test", "fullname": "Test McBugster", "id": 7, "is_staff": true, "is_superuser": true, "last_name": "McBugster", "refresh_token": "", "scope": "", "token_type": "", "user_status": "0", "username": "testaccount" } [pid: 7|app: 0|req: 2/20] 192.168.4.4 () {70 vars in 2136 bytes} [Thu Oct 6 20:17:38 2022] GET /auth/complete/stepwisemath-oauth/?redirect_state=pdbIKIcEbhjVr3Kon5VXUWWiy5kuX921&code=q0antmap4qfamd6pe24jh75pdprahpdiyitmut0o&state=pdbIKIcEbhjVr3Kon5VXUWWiy5kuX921&iframe=break => generated 0 bytes in 3549 msecs (HTTP/1.1 302) 9 headers in 612 bytes (1 switches on core 0) 2022-10-06 20:17:42,211 INFO 19 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/register", "context": {"user_id": null, "path": "/register", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://stepwisemath.ai/", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2022-10-06T20:17:42.211436+00:00", "event_type": "/register", "event_source": "server", "page": null} [pid: 19|app: 0|req: 3/21] 192.168.4.4 () {70 vars in 1796 bytes} [Thu Oct 6 20:17:42 2022] GET /register => generated 37606 bytes in 177 msecs (HTTP/1.1 200) 8 headers in 600 bytes (1 switches on core 0) 2022-10-06 20:17:42,527 INFO 7 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/stepwise/api/v1/configuration/prod", "context": {"user_id": null, "path": "/stepwise/api/v1/configuration/prod", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2022-10-06T20:17:42.527217+00:00", "event_type": "/stepwise/api/v1/configuration/prod", "event_source": "server", "page": null} [pid: 7|app: 0|req: 3/22] 192.168.4.4 () {68 vars in 1755 bytes} [Thu Oct 6 20:17:42 2022] GET /stepwise/api/v1/configuration/prod => generated 167 bytes in 41 msecs (HTTP/1.1 200) 6 headers in 189 bytes (1 switches on core 0) 2022-10-06 20:17:42,617 INFO 19 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/api/user/v2/account/registration/", "context": {"user_id": null, "path": "/api/user/v2/account/registration/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {\"next\": [\"/dashboard\"], \"email\": [\"test@stepwisemath.ai\"], \"name\": [\"Test McBugster\"], \"username\": [\"testaccount\"], \"password\": \"********\", \"level_of_education\": [\"\"], \"gender\": [\"\"], \"year_of_birth\": [\"\"], \"mailing_address\": [\"\"], \"goals\": [\"\"], \"social_auth_provider\": [\"Stepwise\"], \"terms_of_service\": [\"true\"]}}", "time": "2022-10-06T20:17:42.616767+00:00", "event_type": "/api/user/v2/account/registration/", "event_source": "server", "page": null} 2022-10-06 20:17:42,620 INFO 7 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/api/user/v1/validation/registration", "context": {"user_id": null, "path": "/api/user/v1/validation/registration", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {\"name\": [\"Test McBugster\"], \"username\": [\"testaccount\"], \"password\": \"********\", \"email\": [\"test@stepwisemath.ai\"], \"terms_of_service\": [\"false\"]}}", "time": "2022-10-06T20:17:42.619453+00:00", "event_type": "/api/user/v1/validation/registration", "event_source": "server", "page": null} [pid: 7|app: 0|req: 4/23] 192.168.4.4 () {74 vars in 1928 bytes} [Thu Oct 6 20:17:42 2022] POST /api/user/v1/validation/registration => generated 205 bytes in 85 msecs (HTTP/1.1 200) 8 headers in 282 bytes (1 switches on core 0) 2022-10-06 20:17:42,719 INFO 7 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/api/user/v1/validation/registration", "context": {"user_id": null, "path": "/api/user/v1/validation/registration", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {\"name\": [\"Test McBugster\"], \"username\": [\"testaccount\"], \"password\": \"********\", \"email\": [\"test@stepwisemath.ai\"], \"terms_of_service\": [\"false\"]}}", "time": "2022-10-06T20:17:42.719504+00:00", "event_type": "/api/user/v1/validation/registration", "event_source": "server", "page": null} [pid: 7|app: 0|req: 5/24] 192.168.4.4 () {74 vars in 1928 bytes} [Thu Oct 6 20:17:42 2022] POST /api/user/v1/validation/registration => generated 205 bytes in 102 msecs (HTTP/1.1 200) 8 headers in 282 bytes (1 switches on core 0) 2022-10-06 20:17:42,816 INFO 7 [tracking] [user None] [ip 192.168.6.26] logger.py:41 - {"name": "/api/user/v1/validation/registration", "context": {"user_id": null, "path": "/api/user/v1/validation/registration", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {\"name\": [\"Test McBugster\"], \"username\": [\"testaccount\"], \"password\": \"********\", \"email\": [\"test@stepwisemath.ai\"], \"terms_of_service\": [\"false\"]}}", "time": "2022-10-06T20:17:42.816042+00:00", "event_type": "/api/user/v1/validation/registration", "event_source": "server", "page": null} [pid: 7|app: 0|req: 6/25] 192.168.4.4 () {74 vars in 1928 bytes} [Thu Oct 6 20:17:42 2022] POST /api/user/v1/validation/registration => generated 205 bytes in 77 msecs (HTTP/1.1 200) 8 headers in 282 bytes (1 switches on core 0) 2022-10-06 20:17:43,160 INFO 19 [audit] [user 53] [ip 192.168.6.26] models.py:2753 - Login success - user.id: 53 2022-10-06 20:17:43,221 INFO 19 [tracking] [user 53] [ip 192.168.6.26] logger.py:41 - {"name": "edx.user.settings.changed", "context": {"user_id": null, "path": "/api/user/v2/account/registration/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": {"old": null, "new": "en", "truncated": [], "setting": "pref-lang", "user_id": 53, "table": "user_api_userpreference"}, "time": "2022-10-06T20:17:43.220899+00:00", "event_type": "edx.user.settings.changed", "event_source": "server", "page": null} 2022-10-06 20:17:43,239 INFO 19 [tracking] [user 53] [ip 192.168.6.26] logger.py:41 - {"name": "edx.user.settings.changed", "context": {"user_id": null, "path": "/api/user/v2/account/registration/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "", "session": "a3f4ac2a5bf97f717f5745984059891b", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": {"old": false, "new": true, "truncated": [], "setting": "is_active", "user_id": 53, "table": "auth_user"}, "time": "2022-10-06T20:17:43.238965+00:00", "event_type": "edx.user.settings.changed", "event_source": "server", "page": null} /openedx/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py:1416: RuntimeWarning: DateTimeField Registration.activation_timestamp received a naive datetime (2022-10-06 20:17:43.246811) while time zone support is active. warnings.warn("DateTimeField %s received a naive datetime (%s)" 2022-10-06 20:17:43,254 INFO 19 [common.djangoapps.student.models] [user 53] [ip 192.168.6.26] models.py:938 - User testaccount (test@stepwisemath.ai) account is successfully activated. 2022-10-06 20:17:43,255 INFO 19 [openedx_events.tooling] [user 53] [ip 192.168.6.26] tooling.py:160 - Responses of the Open edX Event : [] 2022-10-06 20:17:43,261 INFO 19 [audit] [user 53] [ip 192.168.6.26] register.py:295 - Login success on new account creation - testaccount [pid: 19|app: 0|req: 4/26] 192.168.4.4 () {74 vars in 1881 bytes} [Thu Oct 6 20:17:42 2022] POST /api/user/v2/account/registration/ => generated 79 bytes in 1145 msecs (HTTP/1.1 200) 15 headers in 3254 bytes (1 switches on core 0) 2022-10-06 20:17:44,014 INFO 7 [tracking] [user 53] [ip 192.168.6.26] logger.py:41 - {"name": "/auth/complete/stepwisemath-oauth/", "context": {"user_id": 53, "path": "/auth/complete/stepwisemath-oauth/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "testaccount", "session": "4b87c052d7ba72c52f84c82737834d90", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2022-10-06T20:17:44.014681+00:00", "event_type": "/auth/complete/stepwisemath-oauth/", "event_source": "server", "page": null} /openedx/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py:1416: RuntimeWarning: DateTimeField User.date_joined received a naive datetime (2022-10-06 19:57:56) while time zone support is active. warnings.warn("DateTimeField %s received a naive datetime (%s)" 2022-10-06 20:17:44,100 INFO 7 [tracking] [user 53] [ip 192.168.6.26] logger.py:41 - {"name": "edx.user.settings.changed", "context": {"user_id": 53, "path": "/auth/complete/stepwisemath-oauth/", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "testaccount", "session": "4b87c052d7ba72c52f84c82737834d90", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": {"old": "2022-10-06T20:17:42.674048+00:00", "new": "2022-10-06 19:57:56", "truncated": [], "setting": "date_joined", "user_id": 53, "table": "auth_user"}, "time": "2022-10-06T20:17:44.100229+00:00", "event_type": "edx.user.settings.changed", "event_source": "server", "page": null} [pid: 7|app: 0|req: 7/27] 192.168.4.4 () {66 vars in 3727 bytes} [Thu Oct 6 20:17:43 2022] GET /auth/complete/stepwisemath-oauth/? => generated 0 bytes in 150 msecs (HTTP/1.1 302) 10 headers in 721 bytes (1 switches on core 0) 2022-10-06 20:17:44,375 INFO 19 [tracking] [user 53] [ip 192.168.6.26] logger.py:41 - {"name": "/dashboard", "context": {"user_id": 53, "path": "/dashboard", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "testaccount", "session": "4b87c052d7ba72c52f84c82737834d90", "ip": "192.168.6.26", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "host": "web.stepwisemath.ai", "referer": "https://web.stepwisemath.ai/register", "accept_language": "en-US,en;q=0.9,es-MX;q=0.8,es-US;q=0.7,es;q=0.6", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2022-10-06T20:17:44.374973+00:00", "event_type": "/dashboard", "event_source": "server", "page": null}
Full Stack
⭐🍴👁🐛
1211
Last commit: Feb 2024
Total commits: 121

Python (91.0%)
Makefile (5.1%)
Shell (3.9%)
Full Stack
⭐🍴👁🐛
0200
Last commit: Apr 2024
Total commits: 214

Python (97.9%)
Makefile (2.1%)
Cloud
⭐🍴👁🐛
1013
Last commit: Jun 2025
Total commits: 50

Shell (70.6%)
JavaScript (29.4%)
Private repositorylpm0073/openedx_devops

This repository contains Terraform code and Github Actions workflows to deploy and manage a Tutor <https://docs.tutor.overhang.io/>_ Kubernetes-managed production installation of Open edX that will automatically scale up, reliably supporting several hundred thousand learners.

Full StackCloud
⭐🍴👁🐛
0102
Last commit: May 2026
Total commits: 543

HCL (60.7%)
Shell (33.5%)
Smarty (5.6%)
Full Stack
⭐🍴👁🐛
0100
Last commit: Sep 2024
Total commits: 166

Python (97.1%)
Makefile (2.9%)

These are articles that I've published. Usually from my personal blog, but every now and then from other sources.

Article
Getting Started With Open edX Plugin Architecture

Since at least August-2020 it’s become possible to implement custom code for the Open edX platform without forking the edx-platform repository. Not only is it possible but it’s considered best practice to organize both your custom code as well as any platform modifications into separate pip-installable projects. This article, which includes practical code exercises, will

Getting Started With Open edX Plugin Architecture
Wordpress oAuth Provider for Open edX

WordPress is a great solution for adding marketing and e-commerce functionality to your Open edX project. See how you can also leverage WordPress as an upstream oAuth provider to your Open edX installation.

Wordpress oAuth Provider for Open edX
The Open edX Example Plugin
26 Code Samples To Make You A Better Open edX Plugin Programmer
Managing Sensitive Data in CI Workflows With Kubernetes
Running Open edX At Scale With Kubernetes

The rapid adoption of Tutor as a build and deployment solution for Open edX has also opened the door to using Kubernetes. But what is Kubernetes and what problem does it solve for an Open edX platform? Well, plenty! Read on to see what, why, and how you can begin leveraging this next-generation system management

Running Open edX At Scale With Kubernetes
Introducing Cookiecutter Open edX

The Cookiecutter Open edX project maintains an integrated suite of open source tools for deploying and managing Open edX at scale on Kubernetes using fully automated Build and Deploy CI workflows.

Introducing Cookiecutter Open edX
Setting Up AWS S3 for Open edX

There are many benefits to moving your user data from the Ubuntu file system to AWS S3 cloud storage. It’s also pretty easy to do since Django handles the technical details, and the guys at edX have parameterized everything so that you only need to make some adjustments to the files in /edx/etc/.

Setting Up AWS S3 for Open edX
Scaling MongoDB for Open edX

Learn how to scale your Open edX platform by migrating the MongoDB database service to its own remote Ubuntu server running on AWS EC2.

Scaling MongoDB for Open edX
Scaling Memcached and Redis for Open edX

Videos from my YouTube Channel, Full Stack With Lawrence

Video
The Open edX® Example Plugin: Your One-stop-shop For Code Samples!
Open edX® Plugin Coding Tip #1 - Iterating Course Content

Learn the Python code pattern to reliably reproduce the data that drives the Course Outline in Course Management Studio. Source Code: https://github.com/lpm0073/youtube-26-coding-tips/tree/main/coding-tip-01 https://github.com/cookiecutter-openedx/openedx-plugin-example/tree/youtube-26-coding-tips/openedx_plugin_cms

Open edX® Plugin Coding Tip #1 - Iterating Course Content
26 Coding Tips To Make You A Better Open edX® Programmer
Open edX® Plugin Coding Tip #2 - Custom oAuth Backend for Wordpress

Lawrence and Clifford Okorie show you how to create a Wordpress oAuth provider and a custom oauth backend for Open edX using plugin technology. Source Code: https://github.com/cookiecutter-openedx/openedx-plugin-example/blob/main/openedx_plugin/wordpress_oauth2_backend.py Getting Started With Open edX Plugins: - https://blog.lawrencemcdaniel.com/getting-started-with-open-edx-plugin-architecture/ - https://blog.lawrencemcdaniel.com/the-open-edx-example-plugin/

Open edX® Plugin Coding Tip #2 - Custom oAuth Backend for Wordpress
Open edX® Plugin Coding Tip #3 - Python Decorators

Learn 10 commonly used Python decorators that will improve your code in terms of performance, security and readability. Source Code: https://github.com/openedx/edx-platform About: In this video Lawrence and Clifford Okorie show you how to create a Wordpress oAuth provider and a custom oauth backend for Open edX using plugin technology. Getting Started With Open edX Plugins: - https://blog.lawrencemcdaniel.com/getting-started-with-open-edx-plugin-architecture/ - https://blog.lawrencemcdaniel.com/the-open-edx-example-plugin/ - https://blog.lawrencemcdaniel.com/how-to-customize-the-login-behavior-of-open-edx/

Open edX® Plugin Coding Tip #3 - Python Decorators
Open edX Conference 2023 Kubernetes Talk By Lawrence McDaniel

About: At the 2023 Open edX annual conference, Full Stack developer, community blogger and Youtube content creator Lawrence McDaniel presents an overview of the state of the Open edX community's transition to running production workloads on Kubernetes. About the event: https://con.openedx.org/ https://openlearning.mit.edu/events/open-edx-conference-2023 https://openedx.org/events/open-edx-conference-2023-2/

Open edX Conference 2023 Kubernetes Talk By Lawrence McDaniel

FAQ


The Open edX software, inclusive of all system software and third party libraries, is free to download and free to use. A new named release comes out every six months, in June and December. The project is stewarded by the Axim Collaborative, a non-profit founded by Harvard and MIT, and maintained by a community of contributors from edX and from organizations and universities around the world. The community does not charge fees for access to their regular software updates. Your only costs will be external consulting costs, if any, for the installation and configuration, and recurring cloud computing costs. My blog article, "How Much Does Open edX Cost?" includes summaries of itemized projects costs that have been voluntarily submitted by user in the Open edX community over the last few years.

Yes, there are several. The best place to start looking is the official Open edX Service Providers Marketplace managed by the Axim Collaborative. In addition, I have worked with or collaborated with several for whom I can vouch, including (in no particular order)

You are also welcome to contact me directly, via email at lpm0073@gmail.com

The answer depends a host of factors, but there are some generalizations on which you can safely rely.

  • Use a platform provider if you're just getting started and you lack technical support and your budget permits. All Open edX providers will leave you in complete control of your data, so you'll always have the option of migrating the platform to your own private installation at a later date.
  • Otherwise, run your own installation with Tutor, the community's official distribution of Open edX, and the only supported way to install it since the older native build scripts were retired. It is also the quickest way to evaluate the platform on a tight budget. Running your own makes the most sense if
    • you're a startup and you are actively deciding on the feature set you want to offer customers.
    • you have a team in place who is competent in supporting enterprise Django platforms and Docker.
    • you intend to customize the software.
    • you do not need customizations, and, your user base will be small.

(Using AWS EC2 server types as a reference)

t3.large (2 vCPUs with 8gb of RAM) is the smallest server size on which Open edX will run reliably. I generally use this server size during development and for very small implementations intended for no more than a few dozen users. For any other size rollout I generally use t3.xlarge servers (4 vCPUs with 16gb of RAM) in conjunction with the platform scaling principals that I outline in my blog article, "Scaling Open edX". The Open edX platform is infinitely scalable, as you can plainly see from edx.org. However, scaling is a complex topic -- you've been warned.

Open edX is designed to "easily" integrate with a variety of third party authentication protocols including SAML, oAuth and LTI. In simplest cases such as "Login with Facebook" and "Login with Google" for example, you can get things working in less than an hour using nothing more than the yml configuration files for the LMS and CMS. However, educational institutions frequently required far deeper levels of integration such as Grade Syncing and User Profile Syncing to name two common examples which, while entirely technically feasible, usually require thorough exploration with a trained professional from the Open edX community.

Open edX stores its data in multiple formats including MySQL, mongoDB and the Ubuntu file system. You can read my blog article, "Open edX Complete Backup Solution" to get a better sense of what's involved in backing up the data.

Yes. However, I would strongly encourage you to consider using AWS to host your production platform. I've successfully installed Open edX on all major cloud platforms: AWS, Azure, Google Compute Cloud; as well as at other smaller providers including Digital Ocean and at some regional cloud providers. Tutor runs the platform in Docker containers, so the server's Linux distribution matters far less than it did with the older native builds, and I've always gotten the platform to work eventually. Net-net, I've found the alternative providers to be good, lower-cost alternatives to AWS during development activities, and AWS to be preferable for production.

Yes, barring a few rare outlying circumstances. Native installations no longer receive upgrades, so moving to Tutor is the way forward for any platform that still runs one. I have successfully migrated platforms in both directions.

You should almost always use the most recent named release; the rare exception being that your organization is desperately awaiting the release of a new feature. The Open edX community publishes its named releases here, at "Open edX Release Notes".

Open edX is a highly stable, well-behaved platform once you've got it up and running. The Open edX community's engineers take testing really seriously. With that in mind, and assuming minimal or no customizations to the platform, you can reasonably assume that your platform will essentially be maintenance free other than activities related to scaling (only if your user base grows) and upgrading to each new named release, every six months.

If on the other hand, you plan to actively develop new features on the platform then the following might be of interest to you. Open edX is architected as a traditional enterprise-grade Python-Django web application. It is very large and very complex, and it makes use of the entire family of enterprise scaling technologies that one could possibly encounter in a Django project. Its newer user interfaces, the micro-frontends, are built with ReactJS. There is a substantial learning curve, even if your team are experienced Django/ReactJS folks. The most common technologies that I find myself using are as follows:

  • Ubuntu command line and bash scripting
  • Git / GitHub
  • Docker, Tutor and Kubernetes
  • basic TCP/IP networking stuff
  • Lots of AWS cloud services stuff
  • Python / Django -- albeit with a lot more emphasis on Django
  • JSON and YML file formats
  • ReactJS
  • Bootstrap
  • MySQL -- mostly from the query tool provided in MySQL Workbench

Additionally, Open edX makes uses of a broad range of libraries from Pip and NPM and Ubuntu's apt-get; many of which you'll need to become more familiar.