💾 Persistence & Volumes
Moodle stores state in three different places. Choosing which ones to persist determines how your backups work and how painful upgrades are.
What each path contains
| Path inside the container | Contents | Should you persist it? |
|---|---|---|
/var/www/html | Moodle PHP code + config.php + installed plugins/themes | Depends — see below |
/var/www/moodledata | User-generated data: file uploads, sessions, cache, question attachments, backups | Yes, always |
| Database volume (Postgres / MariaDB) | Courses, users, grades, config, everything metadata | Yes, always |
Recommended setup
services:
postgres:
image: postgres:alpine
volumes:
- postgres:/var/lib/postgresql # (1)
moodle:
image: ruseler/alpine-moodle
volumes:
- moodledata:/var/www/moodledata # (2)
- moodlehtml:/var/www/html # (3)
volumes:
postgres:
moodledata:
moodlehtml:
- Database files. Back up with
pg_dump. See the note on PostgreSQL 18+ below. - Uploads, sessions, file storage. Back up with a cold copy or
tar. - Moodle code, plugins, themes and
config.php. See the upgrade tradeoff below.
The moodlehtml tradeoff
Mounting /var/www/html as a named volume preserves installed plugins, custom themes, and config.php between container restarts. The cost is that upgrading Moodle by changing the image tag alone does not work, because the old code is kept on the volume (#102, #103).
You have two patterns:
- Persistent (default)
- Ephemeral
Pros: plugins and themes survive restarts, config.php keeps customisations.
Cons: to upgrade Moodle you need to remove the moodlehtml volume, start the new image, then re-apply plugins/themes. Always back up first.
docker compose down
docker volume rm <project>_moodlehtml
docker compose pull
docker compose up -d
Pros: upgrading Moodle is just docker compose pull && docker compose up -d.
Cons: you must re-install plugins and re-apply theme customisations on every rebuild, or install them via POST_CONFIGURE_COMMANDS on every start.
services:
moodle:
volumes:
- moodledata:/var/www/moodledata
# no moodlehtml volume
Pair this with idempotent Moosh commands:
environment:
POST_CONFIGURE_COMMANDS: |
moosh plugin-list
moosh plugin-install --delete mod_attendance
#103 tracks a finer-grained approach that would persist only config.php, themes and modules. Until that lands, pick the pattern that matches your risk tolerance.
PostgreSQL 18+ volume path
Since PostgreSQL 18, the official postgres image expects the named volume at /var/lib/postgresql, not /var/lib/postgresql/data (#133). If you pull postgres:alpine today you are on 18+.
# Correct for postgres:18+ (and postgres:alpine today)
volumes:
- postgres:/var/lib/postgresql
If you already have an existing volume created with the old path, follow the PGDATA migration notes on Docker Hub before switching. Mounting the old path on a new major version can silently overwrite your data.
Permissions
The container runs as the non-privileged nobody user (UID 65534). Named Docker volumes get the right ownership automatically. Bind mounts do not — if you mount a host directory, make sure it is writable by UID 65534:
sudo chown -R 65534:65534 ./moodledata ./moodlehtml
See #2, #6, #117 for historical permission problems.
Backups
Minimal backup plan for a PostgreSQL + named-volumes deployment:
# 1. Database
docker compose exec -T postgres pg_dump -U moodle moodle | gzip > moodle-db.sql.gz
# 2. Moodle data (uploads, sessions, file storage)
docker run --rm \
-v <project>_moodledata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/moodledata.tar.gz -C /data .
# 3. (Optional) Moodle code + plugins + config.php
docker run --rm \
-v <project>_moodlehtml:/html:ro \
-v "$PWD":/backup \
alpine tar czf /backup/moodlehtml.tar.gz -C /html .
Restore is the reverse: recreate the volumes, extract the tarballs back into them, and run pg_restore.
Put the site into maintenance mode before taking a cold backup:
docker compose exec moodle php admin/cli/maintenance.php --enable
# ... run backup ...
docker compose exec moodle php admin/cli/maintenance.php --disable
This documentation is a fork of erseco/alpine-moodle. The upstream documentation is available here.