# moodle-docker turns a Moodle checkout into a container stack with one env var and one script

> moodle-docker is the container setup that Moodle's own developers and testers use, pairing a PHP webserver with any supported database and a Selenium container for Behat. The quick start is a sequence of environment variables and a bin/moodle-docker-compose wrapper, and the whole thing is compose file overlays rather than a hand written stack.

**moodlehq/moodle-docker** — A docker environment for moodle developers

- Repository: https://github.com/moodlehq/moodle-docker
- Stars: 579 · Forks: 345
- Language: Shell
- License: GPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/moodlehq-moodle-docker

## It is a testing environment, and the README says so

moodle-docker is described as Docker configuration aimed at Moodle developers and testers to easily deploy a testing environment for Moodle. That framing matters, and it explains the design choices you will find throughout the repository.

The feature list is built around what a tester needs rather than what an operator needs. All supported database servers are covered, with PostgreSQL, MySQL, Microsoft SQL Server and Oracle XE named. There is Behat and Selenium configuration for Firefox and Chrome, a catch-all SMTP server with a web interface for reading messages using Mailpit, all PHP extensions enabled and configured for external services such as solr and ldap, all supported PHP versions, and a zero-configuration approach. The list ends with a note that it is backed by automated tests.

Two of those entries are about the environment rather than Moodle. The external services extensions, solr and ldap, are the ones that are painful to configure by hand, and the fact that they are preconfigured is a real time saving for anyone testing features that talk to them.

The zero-configuration claim is the interesting one, and it is achieved by making the developer supply only what varies. You say where the Moodle code is and which database you want, and everything else is decided by the repository.

## The quick start is a sequence of exports and two bin scripts

The whole quick start fits in one block, and the order matters.

```bash
# Change ./moodle to your /path/to/moodle if you already have it checked out
export MOODLE_DOCKER_WWWROOT=./moodle

# Choose a db server (Currently supported: pgsql, mariadb, mysql, mssql, oracle)
export MOODLE_DOCKER_DB=pgsql

# Get Moodle code, you could select another version branch (skip this if you already got the code)
git clone -b [MOODLE_BRANCH] https://github.com/moodle/moodle.git $MOODLE_DOCKER_WWWROOT

# Ensure customized config.php for the Docker containers is in place
cp config.docker-template.php $MOODLE_DOCKER_WWWROOT/config.php

```

```bash
# Start up containers
bin/moodle-docker-compose up -d

# Wait for DB to come up (important for oracle/mssql)
bin/moodle-docker-wait-for-db

# Work with the containers (see below)
# [..]

# Shut down and destroy containers
bin/moodle-docker-compose down
```

Three things are worth pulling out. First, the bin/moodle-docker-compose wrapper is not a docker compose alias, it is a script in the repository that layers the right compose files together depending on the environment variables you exported. Second, the wait step exists for a reason the comment makes explicit, it matters for Oracle and SQL Server because those databases take much longer to become ready than PostgreSQL or MySQL. Third, the config.php is generated from a template in the repository, config.docker-template.php, and copying it is what points Moodle at the containerised database.

The clone step is marked as skippable, so if you already have a checkout you only export the path. The branch placeholder is a literal, [MOODLE_BRANCH], which is what lets you run several versions at once.

## Running several versions means naming the project and moving the port

Moodle is a platform with long lived branches, and a developer usually needs more than one version running at the same time. The repository supports that with two environment variables.

```bash
# Define a project name; it will appear as a prefix on container names.
export COMPOSE_PROJECT_NAME=moodle34

# Use a different public web port from those already taken
export MOODLE_DOCKER_WEB_PORT=1234
```

COMPOSE_PROJECT_NAME is a standard Docker Compose variable, and the README explains what it does here: the name appears as a prefix on container names, so each instance is isolated from the others at the Compose level. MOODLE_DOCKER_WEB_PORT is the repository's own variable, and it changes the public port so two instances do not fight over the same one. The README links to the Compose environment variable reference for more on that.

The general pattern is the same throughout the repository. The environment variables select the shape of the stack, and the bin/ scripts read them and assemble the right compose files. It is a small indirection layer over Docker Compose, and it is what makes the zero-configuration claim work, because the composition logic lives in one place rather than being restated in every project.

## Behat, PHPUnit and manual testing all run inside the same container

The three test workflows are documented separately and each one is a pair of commands, which is what makes them easy to add to a CI pipeline.

For Behat, the environment is initialised with admin/tool/behat/cli/init.php, and the tests run through the same script with a tag filter, for example --tags=@auth_manual. The sample output in the README shows a passing run reporting two scenarios and fifteen steps.

```bash
# Initialize behat environment
bin/moodle-docker-compose exec webserver php admin/tool/behat/cli/init.php
# [..]

# Run behat tests
bin/moodle-docker-compose exec -u www-data webserver php admin/tool/behat/cli/run.php --tags=@auth_manual
```

For PHPUnit, the same pattern with the phpunit CLI, and the sample output shows two passing tests. Coverage is available by building the component configuration first and then running with pcov enabled:

```bash
# Build component configuration
bin/moodle-docker-compose exec webserver php admin/tool/phpunit/cli/util.php --buildcomponentconfigs
# Execute tests for component
bin/moodle-docker-compose exec webserver php -d pcov.enabled=1 -d pcov.directory=. vendor/bin/phpunit --configuration reportbuilder --coverage-text
```

For manual testing, there is a single install script that creates the database and an administrator account with a fixed password of test and the email admin@example.com.

Two operational details are documented and easy to miss. The behat faildump directory is exposed at http://localhost:8000/_/faildumps/, which is where the failure artifacts land. And Mailpit is listening on http://localhost:8000/_/mail, so email that Moodle sends, including password reset messages, is readable in a browser rather than going to a real inbox. The browser for Behat is switched with MOODLE_DOCKER_BROWSER, and the README notes that you need to recreate the containers with bin/moodle-docker-compose if you change it.

## The configuration is a stack of compose overlays, not one file

The top level entries show how the configuration is assembled. There is a base.yml, and then a long list of overlays with names that map directly to the features described in the README: db.pgsql.yml, db.mariadb.yml, db.mysql.yml, db.mysql.5.6.yml, db.mysql.5.7.yml, db.mssql.yml, db.oracle.yml, each with a .port.yml variant, plus selenium.chrome.yml, selenium.debug.yml, service.mail.yml, behat-faildump.yml, phpunit-external-services.yml, mlbackend.yml, moodle-app.yml, moodle-app-dev.yml, and a few mock files.

The .port.yml pattern is the interesting one. Every database has a variant that publishes a fixed port instead of an ephemeral one, which is what you need when something outside Docker, such as a database client on your machine, has to reach the database. The mock files, bbb-mock.yml and matrix-mock.yml, are for testing the composition itself without pulling real services.

Because the configuration is a set of overlays merged by the bin scripts, the effective stack is not something you can read in one file. That is the cost of the zero-configuration approach, and it is worth knowing before you need to debug a port conflict or add a service.

The rest of the tree supports the same idea. There is a .gitpod.yml and a .gitpod/ directory, so a Gitpod based workspace is a supported way of getting a working environment, a config.docker-template.php that generates the container aware config.php, a bin/ directory with the wrapper scripts, and a tests/ directory.

The licence is GPL-3.0, with a LICENSE file at the repository root, so the compose files and scripts carry copyleft obligations if you redistribute modified versions. That is a licensing note rather than legal advice, and it is worth reading properly if you plan to fork rather than use.

## Where a plain local PHP install is the better answer

The limitation of this environment is that it is a container boundary around a platform that is itself designed to run in many ways, and that adds friction in a few specific situations.

If you are developing a plugin for a single site rather than for Moodle core, the container stack is often more than you need. A local PHP install with its own database gives you the same code, and you avoid the layer where your editor, your debugger and your profiler have to see through a container. The same argument applies to profiling: the tooling that reads the containerised database and the containerised filesystem is more awkward than reading a local one.

The second limitation is the fixed admin credentials. The manual testing script creates an admin account with the password test and the email admin@example.com, and the README notes that the admin username is admin by default unless you pass --adminuser. That is fine for a throwaway site and wrong for anything shared, and the README even warns about the related problem of Moodle logging you out continuously if the browser holds cookies for a different site URL.

The alternative approach to the same goal is to use the official Moodle Docker image, moodlehq/moodle, which the README itself points at in the context of the App images. The difference in approach is that this repository is a development environment with a specific set of compose overlays for testing, while the official image is a single application image you would run as a service. If you want a Moodle to test against rather than a Moodle to develop, the official image is the shorter path, and if you want to write code against Moodle itself, this repository is the right one.

The maintenance picture is current. The last push was on 2026-10-19 and the repository is not archived, and the CI badge points at a workflow running on the main branch. The repository has no GitHub releases, so the compose files are consumed from master rather than from a tagged artefact, which is a reasonable choice for a development environment that tracks Moodle core.

## Conclusion

Adopt moodle-docker if you work on Moodle core or on plugins that need a throwaway site with a real database and a browser for Behat, since it wires up the awkward parts that each project otherwise reimplements. Do not adopt it as a production deployment path, because it is described as a testing environment for developers and testers and it ships an install script that creates an admin account with a fixed password. Verify first which database you need and that the machine has the memory for it, since the SQL Server option alone calls for 3.25GB of RAM, and check the compose overlays you are layering, because the configuration is a set of merged YAML files rather than one file you can read end to end.

## FAQ

### How do I start a Moodle site with moodle-docker?

Export MOODLE_DOCKER_WWWROOT pointing at your Moodle checkout and MOODLE_DOCKER_DB with the database you want, copy config.docker-template.php to the checkout as config.php, then run bin/moodle-docker-compose up -d and bin/moodle-docker-wait-for-db before working with the containers.

### Which databases does moodle-docker support?

The README names PostgreSQL, MySQL, Microsoft SQL Server and Oracle XE, and the quick start sets the choice with MOODLE_DOCKER_DB, where the currently supported values are pgsql, mariadb, mysql, mssql and oracle. The compose tree has a separate file for each, plus a .port.yml variant that publishes a fixed port.

### How do I run several Moodle versions at once with moodle-docker?

Set COMPOSE_PROJECT_NAME to a name that becomes a prefix on the container names, and set MOODLE_DOCKER_WEB_PORT to a free public port. Both variables have to be set before running any of the quick start steps, and the README notes you have to set COMPOSE_PROJECT_NAME to refer to the instance you expect.

### How do I run Behat or PHPUnit tests in moodle-docker?

For Behat, initialise with admin/tool/behat/cli/init.php and run with admin/tool/behat/cli/run.php, for example filtered by --tags=@auth_manual. For PHPUnit, initialise with admin/tool/phpunit/cli/init.php and run vendor/bin/phpunit, and coverage needs the component configuration built with admin/tool/phpunit/cli/util.php --buildcomponentconfigs first.

### How do I see the email Moodle sends in a moodle-docker environment?

Mailpit is the catch-all SMTP server and its web interface is at http://localhost:8000/_/mail, so email that Moodle sends, including password reset messages, is readable in a browser instead of going to a real inbox. The behat faildump directory is exposed at http://localhost:8000/_/faildumps/.

## Sources

- [Issues](https://github.com/moodlehq/moodle-docker/issues)
- [License: GPL-3.0](https://github.com/moodlehq/moodle-docker/blob/main/LICENSE)
- [moodlehq/moodle-docker on GitHub](https://github.com/moodlehq/moodle-docker)
- [README](https://github.com/moodlehq/moodle-docker/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/moodlehq-moodle-docker
