This is a professional-grade, multi-container Moodle deployment designed for ease of maintenance and stability. It is optimized for non-profit organizations using GitOps-ish-style deployments (e.g., Komodo).
This setup uses a shared custom image for the PHP-based services.
app: Runs PHP-FPM to serve Moodle requests.cron: Runs the same image but executes the Moodle cron and ad-hoc task loop.web: A standard Caddy server that serves static assets from the shared code volume and proxies PHP requests toapp.db: A PostgreSQL database container.
- Stateless Architecture: The
/var/www/htmldirectory is ephemeral and wiped on every container restart, ensuring a "clean slate" and preventing configuration drift. - Declarative Plugin Management: Define plugins in your environment variables via
MOODLE_PLUGINS, and they are automatically installed on boot using Moosh. - Stateless Configuration:
config.phpis baked into the image and reads all settings from environment variables. - Modular Entrypoint: Uses
run-partsto execute idempotent step scripts in/docker-entrypoint.d/. - Patched Source: Moodle core code is patched during the build process.
- Forced Settings: Pre-configure any Moodle setting via
MOODLE_CFG_orMOODLE_PLG_environment variables.
-
Configure Environment:
cp .env.example .env # Edit .env with your specific settings -
Launch:
docker-compose up -d
-
Initial Access: Access your Moodle site at the URL specified in
MOODLE_URL. The first boot will automatically run the installation.
Security Warning: The environment variables MOODLE_ADMIN_USER, MOODLE_ADMIN_PASS, and MOODLE_ADMIN_EMAIL are only used for the initial installation. Once the site is up and you have logged in, remove these variables from your .env file or CI/CD secrets to prevent accidental resets or credential exposure.
If you wish to build the image locally instead of pulling from GHCR, modify your docker-compose.yml:
- Comment out the
image: ghcr.io/...lines forappandcron. - Uncomment the
build:blocks. - Run:
docker-compose up -d --build
You can override and "lock" settings directly from your docker-compose.yml or .env file using these prefixes:
Core settings are automatically locked in the UI when defined.
MOODLE_CFG_theme=boost-> Forces the 'boost' theme.MOODLE_CFG_lang=de-> Forces German language.MOODLE_CFG_smtphosts=smtp.example.com:587-> Sets the SMTP server.
Use the format MOODLE_PLG_pluginname__settingname. These are also automatically locked in the UI.
MOODLE_PLG_auth_ldap__host_url=ldaps://ldap.example.com-> Forces the LDAP host.
The cron service handles both scheduled tasks and ad-hoc tasks. You can scale these via environment variables:
MOODLE_CRON_COUNT(Default: 1): Parallelcron.phpinstances.MOODLE_ADHOC_TASK_COUNT(Default: 0): Paralleladhoc_task.phpinstances.
We provide an idempotent helper script to manage issuers via JSON:
- Prepare a JSON config (see
scripts/oauth2-config.example.json). - Run the script inside the container:
docker compose exec app php /opt/moodle/scripts/manage_oauth2_issuer.php /path/to/your-config.json
Alternatively, any variable starting with MOODLE_OAUTH2_CONFIG_ will be processed on boot:
environment:
MOODLE_OAUTH2_CONFIG_JSON: '{"name":"MyIDP",...}'In this architecture, the container's /opt/moodle/code directory is the Source of Truth. Every time the app (Leader) container starts, it:
- Restores the patched core code from the image to the shared volume (using
rsync --delete). - Performs Moodle installation or upgrades (Scripts 20-40).
- Installs any plugins defined in
MOODLE_PLUGINS(Script 50). - Signals Readiness via a
.readyfile (Script 99).
Follower services (cron, web) will wait for this .ready signal before starting their main processes. This ensures they only run when the codebase is fully synchronized and plugins are installed.
Instead of manually uploading plugins, define them in your docker-compose.yml:
environment:
MOODLE_PLUGINS: "theme_moove mod_checklist https://github.com/example/plugin/archive/master.zip"
MOODLE_AUTO_UPGRADE: "true"Plugins can be specified by their Moodle directory name (e.g., theme_moove) or by a direct download URL. The entrypoint uses Moosh to download and install them automatically.
Note: It is highly recommended to set MOODLE_AUTO_UPGRADE=true when using MOODLE_PLUGINS to ensure that any database migrations required by the new plugins are executed automatically on boot.
WARNING: Any files manually added to /var/www/html (e.g., via the Moodle UI or manual upload) will be deleted on the next container restart. Always use MOODLE_PLUGINS for persistent plugin management.
latest: Most recent Moodle on PHP 8.4.lts: Most recent Moodle on PHP 8.2 (LTS).<MAJOR>(e.g.,501): Most recent Moodle of that major on LTS PHP (8.2).<MAJOR>-php<VERSION>: Specific versions (e.g.,501-php8.3).
To update, simply change the image tag or MOODLE_VERSION and restart.
If you are running behind a public-facing reverse proxy (e.g., Traefik, Nginx, Cloudflare):
- Set
MOODLE_REVERSE_PROXY=true. - If your proxy provides SSL, set
MOODLE_SSL_PROXY=true.
moodle_code: Shared Moodle source code volume (managed by app sync).moodle_data: Themoodledatadirectory for uploads and cache.db_data: Persistent PostgreSQL data.