Skip to content

Commit 8867d5c

Browse files
committed
Initial commit: Moodle 4.5 Docker Compose stack
Production-ready stack with: - PHP 8.3 + Apache (custom Dockerfile) - MariaDB 11 (optimized configuration) - Valkey 9 (Redis-compatible sessions + cache) - Ofelia cron scheduler (1 minute frequency) - Multi-network architecture (frontend/backend isolation) - Traefik-ready with SSL labels (disabled by default) - Complete documentation and quick-start guide Stack Components: - Moodle 4.5 (MOODLE_405_STABLE) - PHP 8.3 with all required extensions (sodium, opcache, redis, etc.) - MariaDB 11 with InnoDB optimization and UTF8MB4 - Valkey 9 with AOF persistence and LRU eviction - Ofelia for Docker-native cron execution Features: - No unmaintained Bitnami images - Custom Dockerfile from php:8.3-apache-bookworm - Optimized MariaDB config (innodb_buffer_pool_size, etc.) - Optimized Valkey config (maxmemory, persistence) - Automatic cron execution every 1 minute - Health checks on all services - Environment-based configuration (.env) - Multi-network security isolation Documentation: - README.md: Complete setup and operations guide with badges - QUICKSTART.md: 10-minute deployment guide - claudedocs/ARCHITECTURE_SUMMARY.md: Architecture decisions - claudedocs/PRD_Moodle_4.5_Docker_Stack.md: Full requirements - LICENSE: MIT CI/CD: - GitHub Actions workflow for Docker build validation - PHP extension verification - Service health checks - Security scanning with Trivy - Markdown linting Deployment: 1. Clone Moodle: git clone -b MOODLE_405_STABLE git://git.moodle.org/moodle.git 2. Configure: cp .env.example .env (generate secure passwords) 3. Build: docker compose build 4. Deploy: docker compose up -d 5. Install: http://localhost:8080 or CLI installer 6. Configure Valkey MUC cache for optimal performance Production ready for skilled teams comfortable with Docker and Linux. Designed for simple production deployment without development complexity.
0 parents  commit 8867d5c

File tree

14 files changed

+4120
-0
lines changed

14 files changed

+4120
-0
lines changed

.env.example

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Moodle 4.5 Docker Stack - Environment Configuration
2+
# Copy this file to .env and customize with your values
3+
# IMPORTANT: Generate secure passwords for production!
4+
5+
# ============================================================================
6+
# Project Configuration
7+
# ============================================================================
8+
COMPOSE_PROJECT_NAME=moodle45
9+
10+
# Web ports (adjust if needed, bind to 127.0.0.1 for local access only)
11+
MOODLE_DOCKER_WEB_PORT=8080
12+
MOODLE_DOCKER_WEB_PORT_SSL=8443
13+
14+
# ============================================================================
15+
# Moodle Configuration
16+
# ============================================================================
17+
MOODLE_SITE_URL=http://localhost:8080
18+
# For production with Traefik: https://moodle.example.com
19+
20+
# ============================================================================
21+
# Database Configuration (MariaDB 11)
22+
# ============================================================================
23+
DB_TYPE=mariadb
24+
DB_HOST=database
25+
DB_PORT=3306
26+
DB_NAME=moodle
27+
DB_USER=moodleuser
28+
DB_PREFIX=mdl_
29+
30+
# SECURITY: Generate secure passwords!
31+
# Example: openssl rand -base64 32
32+
DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD_HERE
33+
DB_ROOT_PASSWORD=CHANGE_ME_SECURE_ROOT_PASSWORD_HERE
34+
35+
# ============================================================================
36+
# Valkey Configuration (Redis-compatible cache & sessions)
37+
# ============================================================================
38+
VALKEY_HOST=valkey
39+
VALKEY_PORT=6379
40+
41+
# SECURITY: Generate secure password!
42+
VALKEY_PASSWORD=CHANGE_ME_SECURE_VALKEY_PASSWORD_HERE
43+
44+
# ============================================================================
45+
# Notes
46+
# ============================================================================
47+
# 1. Never commit .env file to version control (add to .gitignore)
48+
# 2. Generate strong passwords: openssl rand -base64 32
49+
# 3. For production, use actual domain in MOODLE_SITE_URL
50+
# 4. Traefik labels in compose.yml are commented out by default

.github/workflows/docker-build.yml

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
name: Docker Build & Validate
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main ]
8+
workflow_dispatch:
9+
10+
env:
11+
MOODLE_BRANCH: MOODLE_405_STABLE
12+
13+
jobs:
14+
validate-compose:
15+
name: Validate Docker Compose
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Validate docker-compose.yml syntax
23+
run: |
24+
docker compose config > /dev/null
25+
echo "✅ Docker Compose syntax is valid"
26+
27+
- name: Check required files
28+
run: |
29+
files=(".env.example" "compose.yml" "docker/moodle/Dockerfile" "config/moodle-config.php")
30+
for file in "${files[@]}"; do
31+
if [ ! -f "$file" ]; then
32+
echo "❌ Missing required file: $file"
33+
exit 1
34+
fi
35+
done
36+
echo "✅ All required files present"
37+
38+
- name: Verify .gitignore excludes secrets
39+
run: |
40+
if grep -q "^\.env$" .gitignore && grep -q "^moodle/$" .gitignore; then
41+
echo "✅ .gitignore properly configured"
42+
else
43+
echo "❌ .gitignore missing critical entries"
44+
exit 1
45+
fi
46+
47+
- name: Check for leaked secrets
48+
run: |
49+
if git ls-files | xargs grep -l "CHANGE_ME" | grep -v ".env.example"; then
50+
echo "❌ Found placeholder passwords in tracked files"
51+
exit 1
52+
fi
53+
echo "✅ No leaked secrets detected"
54+
55+
build-moodle-image:
56+
name: Build Moodle Image
57+
runs-on: ubuntu-latest
58+
needs: validate-compose
59+
60+
steps:
61+
- name: Checkout code
62+
uses: actions/checkout@v4
63+
64+
- name: Set up Docker Buildx
65+
uses: docker/setup-buildx-action@v3
66+
67+
- name: Build Moodle image
68+
uses: docker/build-push-action@v5
69+
with:
70+
context: ./docker/moodle
71+
file: ./docker/moodle/Dockerfile
72+
push: false
73+
tags: moodle:test
74+
cache-from: type=gha
75+
cache-to: type=gha,mode=max
76+
77+
- name: Verify PHP version
78+
run: |
79+
docker build -t moodle:test ./docker/moodle
80+
PHP_VERSION=$(docker run --rm moodle:test php -v | head -n 1)
81+
echo "PHP Version: $PHP_VERSION"
82+
if echo "$PHP_VERSION" | grep -q "PHP 8.3"; then
83+
echo "✅ PHP 8.3 verified"
84+
else
85+
echo "❌ Expected PHP 8.3"
86+
exit 1
87+
fi
88+
89+
- name: Verify required PHP extensions
90+
run: |
91+
REQUIRED_EXTS="sodium mysqli gd intl zip opcache redis ldap mbstring xml"
92+
for ext in $REQUIRED_EXTS; do
93+
if docker run --rm moodle:test php -m | grep -q "^$ext$"; then
94+
echo "✅ Extension found: $ext"
95+
else
96+
echo "❌ Missing required extension: $ext"
97+
exit 1
98+
fi
99+
done
100+
101+
- name: Check PHP configuration
102+
run: |
103+
docker run --rm moodle:test php -i | grep -E "max_input_vars|memory_limit|upload_max_filesize"
104+
105+
# Verify max_input_vars >= 5000
106+
MAX_INPUT_VARS=$(docker run --rm moodle:test php -r "echo ini_get('max_input_vars');")
107+
if [ "$MAX_INPUT_VARS" -ge 5000 ]; then
108+
echo "✅ max_input_vars = $MAX_INPUT_VARS (>= 5000)"
109+
else
110+
echo "❌ max_input_vars = $MAX_INPUT_VARS (need >= 5000)"
111+
exit 1
112+
fi
113+
114+
test-stack-startup:
115+
name: Test Stack Startup
116+
runs-on: ubuntu-latest
117+
needs: build-moodle-image
118+
119+
steps:
120+
- name: Checkout code
121+
uses: actions/checkout@v4
122+
123+
- name: Clone Moodle
124+
run: |
125+
git clone -b ${{ env.MOODLE_BRANCH }} --depth 1 git://git.moodle.org/moodle.git
126+
echo "✅ Moodle cloned successfully"
127+
128+
- name: Create .env file
129+
run: |
130+
cp .env.example .env
131+
# Set test passwords (not for production!)
132+
sed -i 's/CHANGE_ME_SECURE_PASSWORD_HERE/test_password_123/g' .env
133+
sed -i 's/CHANGE_ME_SECURE_ROOT_PASSWORD_HERE/test_root_456/g' .env
134+
sed -i 's/CHANGE_ME_SECURE_VALKEY_PASSWORD_HERE/test_valkey_789/g' .env
135+
136+
- name: Build and start services
137+
run: |
138+
docker compose build --no-cache
139+
docker compose up -d
140+
echo "✅ Services started"
141+
142+
- name: Wait for services to be healthy
143+
run: |
144+
echo "Waiting for services to be healthy..."
145+
timeout 120 bash -c 'until docker compose ps | grep -q "healthy"; do sleep 2; done'
146+
docker compose ps
147+
echo "✅ Services are healthy"
148+
149+
- name: Check service health
150+
run: |
151+
services=("moodle" "database" "valkey")
152+
for service in "${services[@]}"; do
153+
if docker compose ps "$service" | grep -q "Up"; then
154+
echo "✅ $service is running"
155+
else
156+
echo "❌ $service is not running"
157+
docker compose logs "$service"
158+
exit 1
159+
fi
160+
done
161+
162+
- name: Test web access
163+
run: |
164+
sleep 10 # Give Moodle time to initialize
165+
HTTP_CODE=$(docker compose exec -T moodle curl -s -o /dev/null -w "%{http_code}" http://localhost/)
166+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "303" ]; then
167+
echo "✅ Moodle web access working (HTTP $HTTP_CODE)"
168+
else
169+
echo "❌ Moodle web access failed (HTTP $HTTP_CODE)"
170+
docker compose logs moodle
171+
exit 1
172+
fi
173+
174+
- name: Test database connection
175+
run: |
176+
docker compose exec -T database mysql -uroot -ptest_root_456 -e "SHOW DATABASES;" | grep -q "moodle"
177+
echo "✅ Database connection working"
178+
179+
- name: Test Valkey connection
180+
run: |
181+
docker compose exec -T valkey valkey-cli -a test_valkey_789 PING | grep -q "PONG"
182+
echo "✅ Valkey connection working"
183+
184+
- name: Test Ofelia cron
185+
run: |
186+
sleep 65 # Wait for cron to run at least once
187+
if docker compose logs ofelia | grep -q "Job executed"; then
188+
echo "✅ Ofelia cron is executing"
189+
else
190+
echo "⚠️ Warning: Ofelia cron not executed yet (may need more time)"
191+
docker compose logs ofelia
192+
fi
193+
194+
- name: Show service logs
195+
if: always()
196+
run: |
197+
echo "=== Moodle Logs ==="
198+
docker compose logs --tail=50 moodle
199+
echo "=== Database Logs ==="
200+
docker compose logs --tail=50 database
201+
echo "=== Valkey Logs ==="
202+
docker compose logs --tail=50 valkey
203+
echo "=== Ofelia Logs ==="
204+
docker compose logs --tail=50 ofelia
205+
206+
- name: Cleanup
207+
if: always()
208+
run: |
209+
docker compose down -v
210+
rm -rf moodle
211+
212+
security-scan:
213+
name: Security Scan
214+
runs-on: ubuntu-latest
215+
needs: build-moodle-image
216+
217+
steps:
218+
- name: Checkout code
219+
uses: actions/checkout@v4
220+
221+
- name: Build Moodle image
222+
run: docker build -t moodle:scan ./docker/moodle
223+
224+
- name: Run Trivy vulnerability scanner
225+
uses: aquasecurity/trivy-action@master
226+
with:
227+
image-ref: 'moodle:scan'
228+
format: 'table'
229+
exit-code: '0' # Don't fail on vulnerabilities (informational only)
230+
severity: 'CRITICAL,HIGH'
231+
232+
markdown-lint:
233+
name: Markdown Lint
234+
runs-on: ubuntu-latest
235+
236+
steps:
237+
- name: Checkout code
238+
uses: actions/checkout@v4
239+
240+
- name: Lint Markdown files
241+
uses: DavidAnson/markdownlint-cli2-action@v14
242+
with:
243+
globs: '*.md'

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Environment configuration (contains secrets)
2+
.env
3+
4+
# Moodle code (managed via git clone separately)
5+
moodle/
6+
7+
# Backups (if created locally)
8+
backups/
9+
10+
# Logs
11+
*.log
12+
13+
# OS files
14+
.DS_Store
15+
Thumbs.db
16+
17+
# IDE files
18+
.vscode/
19+
.idea/
20+
*.swp
21+
*.swo
22+
*~
23+
24+
# Docker volumes (managed by Docker)
25+
# Not needed in .gitignore as they're not in the project directory
26+
27+
# Temporary files
28+
tmp/
29+
temp/
30+
*.tmp

.markdownlint.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"default": true,
3+
"MD013": {
4+
"line_length": 120,
5+
"code_blocks": false,
6+
"tables": false
7+
},
8+
"MD033": false,
9+
"MD041": false
10+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Netresearch GmbH & Co. KG
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)