Production-ready Docker Compose stack for Moodle 4.5 LMS with PHP 8.3, MariaDB 11, and Valkey 9.
┌─────────────────────────────────────────┐
│ Moodle 4.5 Stack │
├─────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ │
│ │ Moodle │ │ Ofelia │ │
│ │ PHP 8.3 │ │ (Cron) │ │
│ │ Apache │ └──────────┘ │
│ └─────┬────┘ │
│ │ │
│ ┌─────┴──────┐ ┌────────────┐ │
│ │ MariaDB │ │ Valkey 9 │ │
│ │ 11 │ │ (Sessions │ │
│ │ │ │ + Cache) │ │
│ └────────────┘ └────────────┘ │
└─────────────────────────────────────────┘
- Moodle: PHP 8.3 + Apache on Debian Bookworm (available at
ghcr.io/netresearch/moodle-docker/moodle:latest) - MariaDB 11: Database server with optimized configuration
- Valkey 9: Redis-compatible cache and session storage
- Ofelia: Docker-native cron scheduler for Moodle tasks
The Moodle image is automatically built and published to GitHub Container Registry:
# Pull the latest image
docker pull ghcr.io/netresearch/moodle-docker/moodle:latest
# Or use in compose.yml instead of building locally
services:
moodle:
image: ghcr.io/netresearch/moodle-docker/moodle:latest
# ... rest of configurationAvailable tags:
latest- Latest build from main branchmain- Latest build from main branchmain-<sha>- Specific commit from main branch
The compose.yml includes both image: and build: directives:
docker compose up- Pulls pre-built image (fast)docker compose build- Builds locally (for customization)
- Docker Engine 20.10.15+
- Docker Compose V2.5.0+
- Git
- 4GB+ RAM (8GB+ recommended for production)
- 50GB+ disk space
# 1. Clone Moodle code
make clone-moodle
# 2. Create .env file
make setup
# Edit .env and set secure passwords!
# 3. Start the stack
make start
# Check status
make status
# View all available commands
make helpClick to expand manual setup instructions
# Clone Moodle 4.5 stable branch (shallow clone)
git clone -b MOODLE_405_STABLE --depth 1 git://git.moodle.org/moodle.git# Copy environment template
cp .env.example .env
# Generate secure passwords
openssl rand -base64 32 # Use for DB_PASSWORD
openssl rand -base64 32 # Use for DB_ROOT_PASSWORD
openssl rand -base64 32 # Use for VALKEY_PASSWORD
# Edit .env with your values
nano .envRequired changes in .env:
DB_PASSWORD: Set secure passwordDB_ROOT_PASSWORD: Set secure passwordVALKEY_PASSWORD: Set secure passwordMOODLE_SITE_URL: Update for production (e.g.,https://moodle.example.com)
# Pull pre-built image and start all services
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f moodleFor production deployments with Traefik reverse proxy:
# 1. Configure Traefik variables in .env
MOODLE_DOMAIN=moodle.example.com
MOODLE_SITE_URL=https://moodle.example.com
TRAEFIK_NETWORK=traefik
TRAEFIK_CERTRESOLVER=letsencrypt
# 2. Start with Traefik
make start-traefik
# Or manually:
docker compose -f compose.yml -f compose.traefik.yml up -dTraefik features:
- ✅ Automatic HTTPS with Let's Encrypt
- ✅ No exposed ports (Traefik handles routing)
- ✅ HTTP to HTTPS redirect
- ✅ Custom domain support
Option A: Web Installer (Recommended)
- Open browser:
http://localhost:8080(or your configured URL) - Follow installation wizard
- Database settings will be auto-detected from environment variables
Option B: CLI Installer
docker compose exec moodle php admin/cli/install.php \
--lang=en \
--wwwroot=http://localhost:8080 \
--dataroot=/var/moodledata \
--dbtype=mariadb \
--dbhost=database \
--dbname=moodle \
--dbuser=moodleuser \
--dbpass=YOUR_DB_PASSWORD \
--prefix=mdl_ \
--fullname="My Moodle Site" \
--shortname="Moodle" \
--adminuser=admin \
--adminpass=Admin123! \
[email protected] \
--non-interactive \
--agree-licenseAfter installation:
- Go to: Site Administration → Plugins → Caching → Configuration
- Click Add instance under Redis
- Configure:
- Server:
valkey:6379 - Password: Your
VALKEY_PASSWORDfrom.env - Database:
1(sessions use database 0) - Serializer: PHP (or igbinary if available)
- Server:
- Click Save changes
- Map Application cache to the Valkey store
# Start services
docker compose up -d
# Stop services
docker compose down
# Restart single service
docker compose restart moodle
# View logs
docker compose logs -f [service_name]
# Execute commands
docker compose exec moodle bash# Run Moodle cron manually
docker compose exec moodle php admin/cli/cron.php
# Enable maintenance mode
docker compose exec moodle php admin/cli/maintenance.php --enable
# Disable maintenance mode
docker compose exec moodle php admin/cli/maintenance.php --disable
# Purge all caches
docker compose exec moodle php admin/cli/purge_caches.php
# List scheduled tasks
docker compose exec moodle php admin/cli/scheduled_task.php --list# MySQL CLI
docker compose exec database mysql -u root -p
# mysqldump
docker compose exec database mysqldump -u root -p moodle > backup.sql# Valkey CLI
docker compose exec valkey valkey-cli -a YOUR_VALKEY_PASSWORD
# Check memory usage
docker compose exec valkey valkey-cli -a YOUR_VALKEY_PASSWORD INFO memory
# Monitor commands
docker compose exec valkey valkey-cli -a YOUR_VALKEY_PASSWORD MONITOR# 1. Enable maintenance mode
docker compose exec moodle php admin/cli/maintenance.php --enable
# 2. Backup database (recommended)
docker compose exec database mysqldump -u root -p moodle | gzip > moodle-backup-$(date +%Y%m%d).sql.gz
# 3. Update Moodle code
cd moodle
git fetch origin
git checkout MOODLE_410_STABLE # Next version
# 4. Run upgrade
docker compose exec moodle php admin/cli/upgrade.php --non-interactive
# 5. Purge caches
docker compose exec moodle php admin/cli/purge_caches.php
# 6. Disable maintenance mode
docker compose exec moodle php admin/cli/maintenance.php --disableTo enable Traefik reverse proxy with automatic SSL:
- Ensure Traefik is running on your Docker host
- Edit
compose.ymland uncomment Traefik labels undermoodleservice - Update labels with your domain:
traefik.enable: "true" traefik.http.routers.moodle.rule: "Host(`moodle.example.com`)" traefik.http.routers.moodle.entrypoints: "websecure" traefik.http.routers.moodle.tls.certresolver: "letsencrypt"
- Update
.env:MOODLE_SITE_URL=https://moodle.example.com
- Update
config/moodle-config.php:$CFG->cookiesecure = true; $CFG->sslproxy = true;
- Restart:
docker compose up -d
# Check container status
docker compose ps
# Check logs
docker compose logs moodle
# Test internal access
docker compose exec moodle curl -I http://localhost/# Verify database is running
docker compose ps database
# Test connection from Moodle container
docker compose exec moodle ping -c 3 database
# Check database logs
docker compose logs database
# Verify credentials match in .env and config/moodle-config.php# Check Ofelia logs
docker compose logs ofelia
# Manually trigger cron
docker compose exec moodle php admin/cli/cron.php
# Check scheduled tasks status
docker compose exec moodle php admin/cli/scheduled_task.php --list# Fix moodledata permissions
docker compose exec moodle chown -R www-data:www-data /var/moodledata
docker compose exec moodle chmod -R 0750 /var/moodledata
# Fix web root permissions
docker compose exec moodle chown -R www-data:www-data /var/www/html# Check container memory usage
docker stats
# Increase PHP memory limit in docker/moodle/Dockerfile:
# memory_limit = 512M (or higher)
# Rebuild image
docker compose build moodle
docker compose up -d moodlemoodle_docker/
├── docker/
│ ├── moodle/
│ │ └── Dockerfile # Custom PHP 8.3 + Apache image
│ ├── mariadb/
│ │ └── custom.cnf # MariaDB optimization
│ └── valkey/
│ └── valkey.conf # Valkey configuration
├── moodle/ # Git clone of Moodle code
├── config/
│ └── moodle-config.php # Moodle configuration
├── claudedocs/
│ └── PRD_Moodle_4.5_Docker_Stack.md # Product requirements
├── compose.yml # Docker Compose configuration
├── .env # Environment variables (create from .env.example)
├── .env.example # Environment template
├── .gitignore # Git ignore rules
└── README.md # This file
- moodledata: User files, cache, temp files (persistent)
- db_data: MariaDB database files (persistent)
- valkey_data: Valkey data (persistent for sessions)
- frontend: External access (Moodle web interface)
- backend: Internal services (MariaDB, Valkey)
- Change all default passwords in
.env - Never commit
.envto version control - Use strong passwords (32+ characters)
- Enable HTTPS in production (via Traefik or SSL certificates)
- Set
$CFG->cookiesecure = truewhen using HTTPS - Keep Docker images updated:
docker compose pull && docker compose up -d - Regularly update Moodle: check for security releases
- Backend network can be set to
internal: trueif no external access needed
-
Increase resources in
compose.yml:deploy: resources: limits: cpus: '2.0' memory: 2G
-
Adjust MariaDB settings in
docker/mariadb/custom.cnf:innodb_buffer_pool_size: 50-70% of available RAMinnodb_io_capacity: 2000+ for SSD
-
Increase Valkey memory in
docker/valkey/valkey.conf:maxmemory 1gb(or higher based on needs)
-
Enable Valkey MUC for application cache (see setup above)
-
PHP tuning in
docker/moodle/Dockerfile:- Increase
memory_limitto 512M or higher - Adjust
max_execution_timeif needed
- Increase
- Moodle Documentation: https://docs.moodle.org/405/en/
- Moodle Forums: https://moodle.org/forums/
- Docker Documentation: https://docs.docker.com/
- Valkey Documentation: https://valkey.io/
This Docker stack configuration is provided as-is. Moodle is licensed under GPL v3.