Web-app-moodle Role¶
Description: Open-source learning management system for creating, managing, and delivering online courses and e-learning
Variables¶
author: Kevin Veen-Birkenbach
description: Open-source learning management system for creating, managing, and delivering online courses and e-learning
license: Infinito.Nexus Community License (Non-Commercial)
company: Kevin Veen-Birkenbach https://www.veen.world
galaxy_tags: [‘moodle’, ‘docker’, ‘education’, ‘cms’, ‘e-learning’]
issue_tracker_url: https://s.infinito.nexus/issues
min_ansible_version: 2.9
platforms: [{‘name’: ‘ArchLinux’, ‘versions’: [‘all’]}, {‘name’: ‘Debian’, ‘versions’: [‘all’]}, {‘name’: ‘EL’, ‘versions’: [‘all’]}, {‘name’: ‘Fedora’, ‘versions’: [‘all’]}, {‘name’: ‘Ubuntu’, ‘versions’: [‘all’]}]
README¶
Moodle¶
Description¶
Ignite the learning experience with Moodle, a powerful and versatile platform for online education that energizes classrooms and fosters interactive learning. Moodle delivers a comprehensive set of tools for creating, managing, and sharing educational content, supporting collaboration among educators and learners alike.
Overview¶
This role deploys Moodle using Docker, automating the setup of both the Moodle application and its underlying MariaDB database. It integrates with an NGINX reverse proxy to ensure secure and efficient web access and uses persistent storage to safeguard your data and configuration.
Cosmos¶
The diagram places Moodle in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
flowchart LR
subgraph deps [Dependencies]
dep_svc_ai_litellm["svc-ai-litellm 🐳🐝"]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_svc_db_mariadb["svc-db-mariadb 🐳🐝"]
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
end
subgraph role [web-app-moodle 🐳🐝]
svc_litellm["litellm"]
svc_sso["sso"]
svc_logout["logout"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_email["email"]
svc_ldap["ldap"]
svc_mariadb["mariadb"]
svc_moodle["moodle"]
svc_nginx["nginx"]
svc_cron["cron"]
svc_css["css"]
svc_prometheus["prometheus"]
svc_tor["tor"]
svc_container_backup["container_backup"]
svc_openwebui["openwebui"]
svc_hermes["hermes"]
svc_openclaw["openclaw"]
svc_flowise["flowise ❌"]
end
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_mariadb -. "0..1" .-> svc_mariadb
dep_svc_db_openldap -. "0..1" .-> svc_ldap
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_hermes
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_openclaw
dep_web_app_openwebui -. "0..1" .-> svc_openwebui
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_stalwart -. "0..1" .-> svc_email
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logoutSolid 1:1 edges are fixed relationships; dashed 0..1 edges are
conditional (enabled only in matching deployments); red 0..0 edges
are turned off in this role. Node markers show the role’s deploy modes
(💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly
turned off, and ⚙️ an Ansible role dependency declared in
meta/main.yml.
Features¶
Comprehensive e-Learning Platform: Offers an extensive array of features including course management, assessment tools, and collaborative resources.
Customizable Interface: Tailor the look and feel of your learning environment with numerous themes and plugins.
Scalable Deployment: Leverage Docker for a portable and scalable installation that adapts as your user base grows.
Robust Data Management: Secure and reliable storage of both the Moodle application and user data through Docker volumes.
Secure Web Access: Configured to work seamlessly behind an NGINX reverse proxy for enhanced security and performance.
Single Sign-On (SSO) / OpenID Connect (OIDC): Seamless integration with external identity providers for centralized authentication.
Quick Setup¶
Development¶
Clone, set up the workstation, and deploy Moodle onto the local stack:
git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-moodle full_cycle=false
Production¶
Run the published image to provision the inventory and deploy Moodle to a managed server (the mounted volume persists the inventory):
APP=web-app-moodle
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
docker run --rm -it \
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
ghcr.io/infinito-nexus/core/debian bash -c '
INVENTORY=/etc/infinito.nexus/inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host "$HOST" \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv'
MCP server¶
The role exposes a Model Context Protocol surface through the
webservice_mcp protocol plugin, baked into the image at
public/webservice/mcp and served by the NGINX sidecar.
Property |
Value |
|---|---|
Endpoint |
|
Container URL |
|
Transport |
Streamable HTTP (JSON-RPC 2.0
over |
Auth |
` Authorization: Bearer <token>`, a Moodle web-service token |
Subject |
the |
Default state |
off; |
Provisioning runs in tasks/utils/mcp.yml: it copies the plugin into
the code volume, runs admin/cli/upgrade.php, enables
enablewebservices, appends mcp to webserviceprotocols,
creates the infinito_mcp external service, attaches the read-only
functions, mints the permanent token and stores it under
users.administrator.tokens['web-app-moodle'].
Tool categories exposed by the external service:
site metadata (
core_webservice_get_site_info)course catalogue (
core_course_get_courses,core_enrol_get_users_courses)user lookup (
core_user_get_users_by_field)calendar events (
core_calendar_get_calendar_events)
All of them are read-only; mcp.tools.mutating_tools_enabled is
false and no write function is attached.
Default state¶
Off. mcp.enabled is true only while web-app-hermes,
web-app-openclaw or web-app-openwebui is part of the deployment.
Canonical origin¶
Moodle compares every request against $CFG->wwwroot. A probe that
reaches the endpoint over the internal HTTP origin is answered with a
redirect to the canonical HTTPS URL rather than with JSON, so callers
must send the canonical Host header and
X-Forwarded-Proto: https.
How to disable¶
Remove the MCP client roles, or pin mcp.enabled: false for this
role. The web-service token is then not issued and the protocol plugin
stays unconfigured.
Outbound HTTP policy¶
Moodle ships a cURL blocklist that rejects every request to
10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, and only
permits ports 80 and 443. Both settings gate
\core\files\curl_security_helper, which auth_oidc consults for
the Keycloak token exchange and \core\http_client consults for the
AI provider request. The gateway answers on 192.168.80.0/24, and on
an onion node container_extra_hosts pins the Keycloak authority to
the Docker bridge gateway, which NETWORK_DOCKER_ADDRESS_POOLS draws
from 10.208.0.0/12. With the upstream defaults the OIDC login
obtains no token and the AI provider cannot reach the gateway.
tasks/01_manager_ops.yml therefore rewrites
curlsecurityblockedhosts to MOODLE_EGRESS_BLOCKED_HOSTS and
extends curlsecurityallowedport with the port of
MOODLE_LITELLM_CHAT_ENDPOINT when the litellm service is
enabled. Loopback, 0.0.0.0 and the cloud metadata address stay
blocked; the RFC1918 ranges are opened. The Docker address pool and the
bridge gateway that serves the pinned Keycloak authority both live
inside them.
This is an accepted trade-off, not an oversight. Moodle offers no
allowlist, so a single internal host cannot be permitted without
unblocking the range it sits in. The cost is that any account holding a
URL-fetching capability, such as block/rss_client:myaddinstance or a
repository/url instance, can make the server issue requests to
sibling containers. Grant those capabilities only to roles you trust
with that reach, or keep the upstream blocklist and accept that SSO
login and the AI surface do not work.
Image source¶
This role builds its own Moodle image from upstream Moodle source on top
of the official php:8.3-fpm base and installs the Composer
dependencies that source declares. The NGINX sidecar serves the
public directory of the code volume and hands every other path to
Moodle’s router.
Updates¶
The image carries the Moodle release pinned as
services.moodle.app_version in meta/services.yml. On start the
entrypoint compares that release with the one recorded in the code
volume. When the image is newer, it copies the image’s tree next to the
old one and swaps them once the copy is complete. An image that is older
than the recorded release refuses to start. tasks/01_manager_ops.yml
then runs admin/cli/upgrade.php, which migrates the database.
Plugins installed through the web interface live only in the code volume
and are not carried over to the next release. Declare a plugin under
meta/addons/ so the image ships it.
Further Resources¶
moodlehq/moodle-docker (extension list reference)
Persona contract opt-outs¶
This role declares PERSONA_ADMINISTRATOR_BLOCKED in
templates/playwright.env.j2. Moodle’s site administrator is created
by install_database.php in tasks/01_manager_ops.yml and
realigned on every pass, both from the administrator user, so the
native form and the identity provider carry one secret. In the SSO
variant tasks/04_oidc.yml sets alternateloginurl so
/login/index.php bounces to the OIDC plugin and rewrites every
mdl_user row to auth='oidc', so no form accepts either secret;
the SSO-off variants in meta/variants.yml have no OIDC chain to
substitute. One env file is rendered per role, not per variant, so the
flag has to hold for the worst case.
Biber keeps a runnable journey:
files/playwright/test-ldap-variant.js drives the native LDAP-bind
login and files/playwright/test-profile-readonly.js asserts the
locked profile fields. The path back to the administrator persona is a
deploy step that hands the site administrator a credential the persona
env already carries.
Credits¶
Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).