This guide uses Docker Compose to run Moodle with Linux DevOps best practices.
For Docker installation, see Docker.
Bitnami provides well-maintained Moodle Docker images with all dependencies included.
mkdir moodle-docker && cd moodle-docker
Create docker-compose.yml:
version: '3.8'
services:
moodle:
image: bitnami/moodle:4.5
container_name: moodle
restart: unless-stopped
ports:
- '8080:8080'
- '8443:8443'
environment:
- MOODLE_DATABASE_HOST=db
- MOODLE_DATABASE_PORT_NUMBER=3306
- MOODLE_DATABASE_USER=moodle_user
- MOODLE_DATABASE_NAME=moodle_db
- MOODLE_DATABASE_PASSWORD_FILE=/run/secrets/db_password
- SMTP_HOST=smtp.example.com
- SMTP_PORT=587
- SMTP_USER=smtp_user
- SMTP_PASSWORD_FILE=/run/secrets/smtp_password
- MOODLE_USERNAME=admin
- MOODLE_PASSWORD_FILE=/run/secrets/moodle_admin_password
- MOODLE_EMAIL=admin@example.com
volumes:
- 'moodle_data:/bitnami/moodle'
- 'moodledata_data:/bitnami/moodledata'
depends_on:
- db
secrets:
- db_password
- smtp_password
- moodle_admin_password
db:
image: bitnami/mariadb:10.11
container_name: moodle-db
restart: unless-stopped
environment:
- MARIADB_USER=moodle_user
- MARIADB_DATABASE=moodle_db
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/mariadb_root_password
volumes:
- 'mariadb_data:/bitnami/mariadb'
secrets:
- mariadb_root_password
volumes:
moodle_data:
driver: local
moodledata_data:
driver: local
mariadb_data:
driver: local
secrets:
db_password:
file: ./secrets/db_password
smtp_password:
file: ./secrets/smtp_password
moodle_admin_password:
file: ./secrets/moodle_admin_password
mariadb_root_password:
file: ./secrets/mariadb_root_password
mkdir -p secrets
openssl rand -base64 32 > secrets/db_password
openssl rand -base64 32 > secrets/mariadb_root_password
openssl rand -base64 32 > secrets/moodle_admin_password
echo "your_smtp_password" > secrets/smtp_password # Replace with actual SMTP password
chmod 600 secrets/*
docker compose up -d
Access Moodle at http://localhost:8080 or https://localhost:8443.
If you prefer using official Moodle images:
version: '3.8'
services:
moodle:
image: moodlehq/moodle-php-apache:latest
container_name: moodle-custom
restart: unless-stopped
ports:
- "80:80"
environment:
- MOODLE_DATABASE_HOST=db
- MOODLE_DATABASE_USER=moodleuser
- MOODLE_DATABASE_PASSWORD=moodlepass
- MOODLE_DATABASE_NAME=moodledb
- MOODLE_ADMIN_USER=admin
- MOODLE_ADMIN_PASSWORD=securepassword
- MOODLE_SITE_NAME="My Moodle Site"
volumes:
- moodledata:/var/www/moodledata
depends_on:
- db
networks:
- moodle-network
db:
image: mariadb:10.11
container_name: moodle-db-custom
restart: unless-stopped
environment:
- MYSQL_DATABASE=moodledb
- MYSQL_USER=moodleuser
- MYSQL_PASSWORD=moodlepass
- MYSQL_ROOT_PASSWORD=rootpass
volumes:
- db_data:/var/lib/mysql
- ./conf/mariadb_custom.cnf:/etc/mysql/conf.d/custom.cnf
networks:
- moodle-network
volumes:
db_data:
moodledata:
networks:
moodle-network:
driver: bridge
For production use, set up a reverse proxy with Nginx:
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: moodle-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- moodle
networks:
- moodle-network
moodle:
image: bitnami/moodle:4.5
container_name: moodle
restart: unless-stopped
expose:
- "8080"
environment:
- MOODLE_DATABASE_HOST=db
- MOODLE_DATABASE_PORT_NUMBER=3306
- MOODLE_DATABASE_USER=moodle_user
- MOODLE_DATABASE_NAME=moodle_db
- MOODLE_DATABASE_PASSWORD_FILE=/run/secrets/db_password
- MOODLE_USERNAME=admin
- MOODLE_PASSWORD_FILE=/run/secrets/moodle_admin_password
- MOODLE_EMAIL=admin@example.com
- MOODLE_SITE_NAME="My Moodle Site"
volumes:
- 'moodle_data:/bitnami/moodle'
- 'moodledata_data:/bitnami/moodledata'
depends_on:
- db
networks:
- moodle-network
secrets:
- db_password
- moodle_admin_password
db:
image: bitnami/mariadb:10.11
container_name: moodle-db
restart: unless-stopped
environment:
- MARIADB_USER=moodle_user
- MARIADB_DATABASE=moodle_db
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/mariadb_root_password
volumes:
- 'mariadb_data:/bitnami/mariadb'
networks:
- moodle-network
secrets:
- mariadb_root_password
volumes:
moodle_data:
driver: local
moodledata_data:
driver: local
mariadb_data:
driver: local
networks:
moodle-network:
driver: bridge
secrets:
db_password:
file: ./secrets/db_password
moodle_admin_password:
file: ./secrets/moodle_admin_password
mariadb_root_password:
file: ./secrets/mariadb_root_password
Create nginx.conf:
events {
worker_connections 1024;
}
http {
upstream moodle_backend {
server moodle:8080;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://moodle_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Increase timeouts for long operations
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
}
}
# Backup database
docker exec moodle-db mysqldump -u moodle_user -p moodle_db > backup_$(date +%Y%m%d_%H%M%S).sql
# Backup data volumes
docker run --rm -v moodledata_data:/data -v $(pwd)/backup:/backup alpine tar czf /backup/moodledata_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .
# Stop current containers
docker compose down
# Pull latest images
docker compose pull
# Start updated containers
docker compose up -d
latestdocker logs -f moodleAny questions?
Feel free to contact us. Find all contact information on our contact page.