A lightweight Moodle Docker image built on Alpine Linux. (~100MB)
Repository: https://github.com/erseco/alpine-moodle
Key Features
- Built on the lightweight image https://github.com/erseco/alpine-php-webserver
- Compact Docker image size (~100MB)
- Uses PHP 8.3 FPM for better performance, lower cpu usage & memory footprint
- Includes Composer.
- Supports Moodle <5.1 and >=5.1 (detects /public directory automatically).
- Includes Redis session handler support.
- Includes optional container-side SQLite support for ultra-lightweight development/demo setups. The required Moodle SQLite patches (MDL-88218) are applied automatically during the image build for supported Moodle versions (5.0+).
- Includes Moosh CLI for Moodle management.
- Configurable via environment variables (see Dockerfile).
- Support for HA installations: php-redis, php-ldap (also with self-signed certs)
- Multi-arch support: 386, amd64, arm/v6, arm/v7, arm64, ppc64le, s390x
- Optimized for 100 concurrent users
- Optimized to only use resources when there's traffic (by using PHP-FPM's ondemand PM)
- Uses
runitinstead ofsupervisordto reduce memory footprint - Cron jobs run every 180 seconds by runit
- Sample
docker compose.ymlwith PostgreSQL and Redis - Configuration via
ENVvariables - Services (
Nginx,PHP-FPMrun under a non-privileged user (nobody) for improved security - Logs are sent to container's STDOUT (
docker logs -f <container>) - Extensible via pre/post configuration hooks
- Follows the KISS principle (Keep It Simple, Stupid) to make it easy to understand and adjust the image to your needs
- Change default credentials: Always override
MOODLE_USERNAMEandMOODLE_PASSWORDwith secure values. - SQLite mode is development/demo only: Enable it with
MOODLE_DATABASE_TYPE=sqlite3. It skips the external database wait logic and stores the database in/var/www/moodledata/sqlite/moodle.sqliteby default. Do not use this mode in production. - SQLite patches: The image automatically applies the experimental SQLite database driver patches from ateeducacion/moodle during the Docker build. Patches are available for Moodle 5.0, 5.1, and main (5.2+). Older Moodle versions do not have SQLite support and the build will print a warning.
- Moodle ≥ 5.1: The script automatically reconfigures Nginx to serve files from
/public. - Moodle < 5.1: A compatibility patch is applied to
publicpaths.phpto support port mapping inside the container. - PostgreSQL volumes with
postgres:alpine: This repository uses the floatingpostgres:alpinetag, and recent PostgreSQL 18+ image variants expect the named volume to be mounted at/var/lib/postgresqlinstead of/var/lib/postgresql/data. If you already have a PostgreSQL volume created with older compose files, follow the official PostgreSQL Docker image documentation in thePGDATAsection before starting the updated stack to avoid confusion or accidental data reset.
Moodle 5.1 introduces a new /public directory for all web-accessible files.
If you are upgrading from 5.0 or earlier:
- Back up critical data:
config.phpmoodledata/directory- Database dump
- Clean old files: Remove any remaining files under
/var/www/html/to prevent stale or conflicting code. - Restore configuration: Copy back
config.phpand custom plugins/themes to the new codebase. - Run dependencies: The container will automatically execute
composer installwhen/publicis detected.
From Docker Hub:
docker compose upLog in using the credentials defined by environment variables.
Ultra-lightweight SQLite demo/dev mode:
docker run -p 80:8080 \
-e MOODLE_DATABASE_TYPE=sqlite3 \
-v moodledata:/var/www/moodledata \
erseco/alpine-moodleNo external database required. SQLite patches are pre-applied in the image. This mode is for development, demos, and CI smoke testing only.
From GHCR:
services:
moodle:
image: ghcr.io/erseco/alpine-moodle
# rest of your configIn certain situations, you might need to run commands as root within your Moodle container, for example, to install additional packages. You can do this using the docker compose exec command with the --user root option. Here's how:
docker compose exec --user root moodle shDefine the ENV variables in docker compose.yml file
| Variable Name | Default | Description |
|---|---|---|
| LANG | en_US.UTF-8 | |
| LANGUAGE | en_US:en | |
| SITE_URL | http://localhost | Sets the public site url |
| REVERSEPROXY | false | See Reverse Proxy Configuration. |
| SSLPROXY | false | See Reverse Proxy Configuration. |
| REDIS_HOST | Set the host of the redis instance. Ej. redis | |
| REDIS_PASSWORD | Redis server password for authentication. | |
| REDIS_USER | Redis ACL username (Redis 6+). Requires REDIS_PASSWORD (container will fail fast if set without it). | |
| DB_TYPE | pgsql | mysqli - pgsql - mariadb |
| MOODLE_DATABASE_TYPE | Optional override for DB_TYPE. Set to sqlite3 to enable the single-container development/demo mode. |
|
| DB_HOST | postgres | DB_HOST Ej. db container name |
| DB_PORT | 5432 | Postgres=5432 - MySQL=3306 |
| DB_NAME | moodle | |
| DB_USER | moodle | |
| DB_SQLITE_PATH | /var/www/moodledata/sqlite/moodle.sqlite | SQLite database file path used when MOODLE_DATABASE_TYPE=sqlite3 or DB_TYPE=sqlite3. |
| DB_FETCHBUFFERSIZE | Set to 0 if using PostgresSQL poolers like PgBouncer in 'transaction' mode | |
| DB_DBHANDLEOPTIONS | false | Set to true if using PostgresSQL poolers like PgBouncer which does not support sending options |
| DB_HOST_REPLICA | Database hostname of the read-only replica database | |
| DB_PORT_REPLICA | Database port of replica, left it empty to be same as DB_PORT | |
| DB_USER_REPLICA | Database login username of replica, left it empty to be same as DB_USER | |
| DB_PASS_REPLICA | Database login password of replica, left it empty to be same as DB_PASS | |
| DB_PREFIX | mdl_ | Database prefix. WARNING: don't use numeric values or moodle won't start |
| MY_CERTIFICATES | none | Trusted LDAP certificate or chain getting through base64 encode |
| MOODLE_EMAIL | user@example.com | |
| MOODLE_LANGUAGE | en | |
| MOODLE_SITENAME | New-Site | |
| MOODLE_USERNAME | moodleuser | |
| MOODLE_PASSWORD | PLEASE_CHANGEME | |
| SMTP_HOST | smtp.gmail.com | |
| SMTP_PORT | 587 | |
| SMTP_USER | your_email@gmail.com | |
| SMTP_PASSWORD | your_password | |
| SMTP_PROTOCOL | tls | |
| MOODLE_MAIL_NOREPLY_ADDRESS | noreply@localhost | |
| MOODLE_MAIL_PREFIX | [moodle] | |
| AUTO_UPDATE_MOODLE | true | Set to false to disable performing update of Moodle (e.g. plugins) at docker start |
| DEBUG | false | |
| client_max_body_size | 50M | |
| post_max_size | 50M | |
| upload_max_filesize | 50M | |
| max_input_vars | 5000 | |
| memory_limit | 256M | PHP memory limit. Increase if encountering memory errors with moosh or large operations |
| PRE_CONFIGURE_COMMANDS | Commands to run before starting the configuration | |
| POST_CONFIGURE_COMMANDS | Commands to run after finished the configuration | |
| RUN_CRON_TASKS | true | Set to false to disable the moodle cron job from running automatically |
When using a reverse proxy (e.g., Caddy, Traefik, nginx-proxy-manager) that handles SSL termination, you might encounter issues like missing CSS or a "too many redirects" error. This is a common problem and can be solved with the correct configuration.
-
Missing CSS: If you set
SITE_URLto your internal Docker IP (e.g.,http://172.19.0.1:89), Moodle will generate URLs with this internal address. When your browser, accessing the site viahttps://moodle.example.com, tries to load these assets, it will block them due to mixed content (trying to loadhttpcontent on anhttpspage). -
ERR_TOO_MANY_REDIRECTS: If you setSITE_URLto your public HTTPS address (e.g.,https://moodle.example.com) but don't configure the proxy settings correctly, Moodle might get confused about the protocol and enter a redirect loop.
The key is to tell Moodle that it's behind an SSL-terminating proxy. You can do this with the following environment variable settings:
SITE_URL: Set this to your public, external URL with thehttpsscheme (e.g.,https://moodle.example.com).REVERSEPROXY: Set this tofalsefor most reverse proxy setups. Set totrueonly if your Moodle site is intentionally accessible from multiple different base URLs (for example, if users access the site using different domain names or protocols). In a typical reverse proxy scenario where all users access Moodle through a single public URL, this should remainfalse. See Moodle's documentation on reverse proxies for more details.SSLPROXY: Set this totrue. This tells Moodle to trust theX-Forwarded-Protoheader from your proxy and understand that the connection is secure, even though the internal connection to the Docker container is over HTTP.
Here's an example of a docker-compose.yml file and a Caddyfile for a reverse proxy setup.
docker-compose.yml
services:
moodle:
image: erseco/alpine-moodle
restart: unless-stopped
environment:
SITE_URL: https://moodle.example.com
REVERSEPROXY: "false"
SSLPROXY: "true"
# ... other environment variables
# No ports need to be exposed externally, as Caddy will connect
# to the container directly via the Docker network.
volumes:
- moodledata:/var/www/moodledata
- moodlehtml:/var/www/html
depends_on:
- postgres
# ... other services (postgres, etc.)
volumes:
moodledata:
moodlehtml:Caddyfile
moodle.example.com {
reverse_proxy moodle:8080 {
header_up Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
}By using this configuration, you can avoid the common pitfalls of running Moodle behind a reverse proxy. For more details, see issue #101.
---
services:
postgres:
image: postgres:alpine
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=moodle
- POSTGRES_USER=moodle
- POSTGRES_DB=moodle
volumes:
- postgres:/var/lib/postgresql
moodle:
image: erseco/alpine-moodle
restart: unless-stopped
environment:
MOODLE_USERNAME: moodleuser
MOODLE_PASSWORD: PLEASE_CHANGEME
ports:
- 80:8080
volumes:
- moodledata:/var/www/moodledata
- moodlehtml:/var/www/html
depends_on:
- postgres
volumes:
postgres: null
moodledata: null
moodlehtml: nullUse SQLite when you want the lightest possible local setup and do not want to run PostgreSQL or MariaDB alongside Moodle.
services:
moodle:
image: erseco/alpine-moodle
restart: unless-stopped
environment:
MOODLE_DATABASE_TYPE: sqlite3
MOODLE_USERNAME: moodleuser
MOODLE_PASSWORD: PLEASE_CHANGEME
ports:
- 8080:8080
volumes:
- moodledata:/var/www/moodledata
volumes:
moodledata: nullNotes:
- SQLite mode automatically skips external database dependency checks.
- The SQLite database file defaults to
/var/www/moodledata/sqlite/moodle.sqlite. - Existing PostgreSQL/MariaDB configurations remain the default and are unchanged.
- The required Moodle SQLite patches (MDL-88218) are applied automatically during the image build for Moodle 5.0, 5.1, and main/5.2+. Older versions will print a build-time warning and sqlite3 mode will not be available.
This image includes Moosh — a powerful CLI tool to manage Moodle installations. You can invoke any Moosh command using:
docker compose exec moodle moosh <command>Examples:
docker compose exec moodle moosh plugin-listdocker compose exec moodle moosh plugin-install mod_attendanceYou can force the installation of unsupported plugins with the
--forceoption.If you encounter memory errors during plugin installation, increase the
memory_limitenvironment variable (default: 256M) in your docker-compose.yml. For example:memory_limit: 512M
NOTE:There is a bug in moosh and the first installation is not working, so we recommend calling again the install function with the
--deleteflag option or use themodule-reinstalloption: eg:docker compose exec moodle moosh plugin-install --delete theme_almondbor calldocker compose exec moodle moosh module-reinstall theme_almondb
Backup course with provided id. By default, logs and grade histories are excluded.
Example: Backup course id=3 into default .mbz file in /opt/moosh/ directory from container:
docker compose exec moodle moosh course-backup 3Create a new Moodle user. Provide one or more arguments to create one or more users.
Example: create user "testuser" with the all the optional values
docker compose exec moodle moosh user-create --password pass --email me@example.com --digest 2 --city Valverde --country ES --institution "IES Garoé" --department "Technology" --firstname "first name" --lastname name testuserDelete user(s) from Moodle. Provide one or more usernames as arguments. Example: delete user testuser
docker compose exec moodle moosh user-delete testuserThese examples can be included directly in POST_CONFIGURE_COMMANDS to automate plugin installation, backups, or any Moosh-supported functionality.
Using Moosh promotes the DRY (Don't Repeat Yourself) principle and leverages a powerful toolset for Moodle administration.
For the full list of commands, visit: https://moosh-online.com/commands/
You can define commands to be executed before and after the configuration of Moodle using the PRE_CONFIGURE_COMMANDS and POST_CONFIGURE_COMMANDS environment variables. These can be useful for tasks such as installing additional packages or running scripts.
environment:
PRE_CONFIGURE_COMMANDS: "cat config-dist.php"
POST_CONFIGURE_COMMANDS: |
moosh plugin-list
moosh plugin-install --delete theme_almondb
moosh plugin-install --delete theme_almondbCalling docker compose build uses the latest version of Moodle from the main branch. If you need to use a specific Moodle version, you can specify it using the MOODLE_VERSION build argument.
To use a specific version, edit the build section for the moodle service in your docker compose.yml file:
moodle:
image: erseco/alpine-moodle
build:
context: .
args:
MOODLE_VERSION: v4.5.3 # Replace with your desired versionYou can find the list of available version tags at: https://github.com/moodle/moodle/tags
Moodle includes a tool to create test scenarios under Admin > Development > Create testing scenarios. To enable it, run the following command, or add it in POST_CONFIGURE_COMMANDS:
php admin/tool/generator/cli/runtestscenario.phpThis tool allows generating all necessary elements for manual testing using .feature file syntax.
Install Additional Alpine Packages (as root):
docker compose exec --user root moodle sh -c "apk update && apk add nano"Manual Database Upgrade:
docker compose exec moodle php admin/cli/upgrade.phpAccess Logs:
docker compose logs -f moodle