├── .gitignore ├── README.md ├── compose.yaml ├── mariadb.service.yaml ├── nginx_proxy.conf └── postgres.service.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *local.yaml 3 | certs/ 4 | saml2/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | moodle-dev-compose 2 | ------------------ 3 | 4 | This project is an alternative way to setup Moodle dev environment using Docker. It is close to LAMP approach from configuring perspective - there is only one database server for all instances of Moodle, and instead of several Apache VirtualHost sites, separate webserver container is used for each Moodle instance. This results in having clean host system (no direct installation of service packages) and provides flexibility to mix versions of PHP for different Moodle instances, as well as easily add other services, such as Memcached or SAML. 5 | 6 | This setup can co-exist on the same host with [moodle-docker](https://github.com/moodlehq/moodle-docker), which is strongly advised to use for running tests. 7 | 8 | ## Features 9 | * Different virtual hostnames (or virtual paths) for each instance of Moodle. 10 | * Uses `moodlehq/moodle-php-apache` Moodle HQ maintained images. 11 | * Shared DB server for all Moodle instances with Phpmyadmin (or Adminer) web UI. 12 | * Mail server container with web UI. 13 | * Easy to extend, enable SSL, add other any other services (see [examples](#other-services)). 14 | 15 | ## Requirements 16 | 17 | You will need: 18 | 19 | * [Docker](https://docs.docker.com/get-docker/). If you don't need GUI, installing only [Docker engine](https://docs.docker.com/engine/install/) is sufficient, but you need [Docker Compose](https://docs.docker.com/compose/install/) installed separately. 20 | * Clone of Moodle repo that you want to work on. 21 | * Make sure you don't run local webserver that is using port 80 (if you need, 22 | change it to different port or use different port in compose file). 23 | * Make sure you don't run local mysql/mariadb server that is using port 3306 (if 24 | you need, change it to different port or use different port in compose file) 25 | 26 | ### Moodle config.php 27 | 28 | Your Moodle config.php should contain (as minimum): 29 | 30 | ``` 31 | dbtype = 'mariadb'; 38 | $CFG->dblibrary = 'native'; 39 | $CFG->dbhost = 'mariadb'; 40 | $CFG->dbname = 'moodle-main'; 41 | $CFG->dbuser = 'moodle'; 42 | $CFG->dbpass = 'moodle'; 43 | $CFG->prefix = 'mdl_'; 44 | $CFG->dboptions = array ( 45 | 'dbpersist' => 0, 46 | 'dbport' => '', 47 | 'dbsocket' => '', 48 | 'dbcollation' => 'utf8mb4_general_ci', 49 | ); 50 | 51 | $CFG->wwwroot = 'http://moodle.local'; 52 | $CFG->dataroot = '/var/www/moodledata'; 53 | $CFG->admin = 'admin'; 54 | 55 | $CFG->directorypermissions = 0777; 56 | 57 | require_once(__DIR__ . '/lib/setup.php'); 58 | ``` 59 | 60 | You may add subdirectory to `dataroot` if you wish, e.g. for using different 61 | dir for different databases when you switch between Moodle versions. 62 | 63 | ### Hostname 64 | 65 | Compose file is defining proxy container that handles hostname mapping to web 66 | container, this makes things simpler as you may add more web containers (e.g. 67 | for different repos, php versions or DBs) using a different hostname for each. 68 | 69 | By default, the only web container is called `moodle.local`. 70 | 71 | To make possible accessing container from your host machine by using 72 | hostname, you need a record in your `/etc/hosts` file pointing to localhost. 73 | 74 | ``` 75 | 127.0.0.1 moodle.local 76 | ``` 77 | 78 | ## Running environment 79 | 80 | Define env variable with location to your Moodle code: 81 | 82 | ```bash 83 | export MOODLE_DOCKER_WWWROOT=/home/ruslan/git/moodle 84 | ``` 85 | 86 | This env var is used for Moodle codebase mount inside the web container. 87 | 88 | Define env variable with PHP version to be used: 89 | 90 | ```bash 91 | export MOODLE_DOCKER_PHP_VERSION=8.1 92 | ``` 93 | 94 | This is used to select `moodlehq/moodle-php-apache` image version to use. 95 | 96 | To start the environment, use `docker compose` command. You need to execute it 97 | from this repo directory: 98 | 99 | ```bash 100 | > docker compose up 101 | ``` 102 | 103 | You can keep the terminal open to see the log, alernatively you may run it in 104 | deaemon mode: 105 | 106 | ```bash 107 | > docker compose up -d 108 | ``` 109 | 110 | You can see the status of containers using: 111 | 112 | ```bash 113 | > docker compose ps 114 | ``` 115 | 116 | Above command amond other things is showing ports mapping from local interface to containers. 117 | 118 | Notice that names of containers specified in compose file are resolved to 119 | their IPs on any container in the set, e.g. you may use `mariadb` in your 120 | moodle `config.php` as DB hostname. 121 | 122 | You may stop the containers using `docker compose stop` or destroy them using 123 | `docker compose down`. It is safe to desroy as you are using volumes, so next 124 | time you start them, all data will be in place within newly created 125 | containers. 126 | 127 | ### Accessing Moodle 128 | 129 | Now when you have database created and config.php configured, you may enter 130 | `http://moodle.local` in your browser on host machine. This will point to your 131 | localhost according to `/etc/hosts` record we created, which will then be 132 | handled by `nginx-proxy` container (notice the port mapping mentioned above). 133 | `nginx-proxy` will recognise hostname `moodle.local` and redirect request to 134 | `moodle-dev-compose-moodle-1` container that will show you Moodle setup screen 135 | in the browser. 136 | 137 | If you need `php` command line (e.g. for setting up Moodle or cron run), you 138 | may access it from web container: 139 | 140 | ```bash 141 | > docker exec -it -u www-data moodle-dev-compose-moodle-1 bash 142 | www-data@0c46f3f2037a:~/html$ 143 | www-data@1d18bc646bf4:~/html$ php admin/cli/upgrade.php 144 | No upgrade needed for the installed version 4.4dev (Build: 20240215) (2024021500). Thanks for coming anyway! 145 | ``` 146 | 147 | ### Database 148 | 149 | #### MariaDB 150 | 151 | By default MariaDB docker image is used. The data is located on 152 | the volume, so recreating container will not cause data loss. 153 | 154 | On the first run, you will probably need to create database that you will use. 155 | You can do that using phpmyadmin included in compose service file, and accessible at 156 | `http://moodle.local:8081` (use user `root` and password `root` for admin 157 | access). User `moodle` with password `moodle` is pre-created (we use it in 158 | moodle config.php). 159 | 160 | Accessing server using DB client is also possible on `localhost:3306`, as container 161 | propagates this port to the host machine. Alternatively, you can access it by 162 | executing shell on running DB container and using `mysql` client: 163 | 164 | ```bash 165 | > docker exec -u mysql -it moodle-dev-compose-mariadb-1 bash 166 | mysql@35e794676852:/$ mysql -u root -p 167 | Enter password: 168 | Welcome to the MariaDB monitor. Commands end with ; or \g. 169 | Your MariaDB connection id is 5 170 | Server version: 10.6.16-MariaDB-1:10.6.16+maria~ubu2004 mariadb.org binary distribution 171 | 172 | Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. 173 | 174 | Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 175 | 176 | MariaDB [(none)]> 177 | ``` 178 | 179 | #### Postgres 180 | 181 | If you prefer Postgres, modify `include` section at the top of `compose.yaml`. 182 | 183 | ``` 184 | include: 185 | # - mariadb.service.yaml 186 | - postgres.service.yaml 187 | ``` 188 | 189 | For postgres service offical Postgres 13 docker image is used. The data is located on 190 | the volume, so recreating container will not cause data loss. 191 | 192 | On the first run, you will probably need to create database and user that you will use. 193 | You can do that using adminer included in compose service file, and accessible at 194 | `http://moodle.local:8082` (use user `postgres` and password `postgres` for 195 | admin access). 196 | 197 | Accessing server using DB client is possible on `localhost:5432`, as container 198 | propagates this port to the host machine. Alternatively, you can access it by 199 | executing shell on running DB container and using `psql` client: 200 | 201 | ```bash 202 | > docker exec -it -u postgres moodle-dev-compose-postgres-1 bash 203 | postgres@6f001003d4c4:/$ createuser -DPRS moodle 204 | Enter password for new role: 205 | Enter it again: 206 | postgres@6f001003d4c4:/$ createdb -O moodle -E UTF-8 moodle-main 207 | postgres@6f001003d4c4:/$ psql 208 | 209 | postgres=# \l 210 | List of databases 211 | Name | Owner | Encoding | Collate | Ctype | Access privileges 212 | ---------------------------+----------+----------+------------+------------+----------------------- 213 | moodle-main | moodlepg | UTF8 | en_US.utf8 | en_US.utf8 | 214 | postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 | 215 | template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres + 216 | | | | | | postgres=CTc/postgres 217 | template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres + 218 | | | | | | postgres=CTc/postgres 219 | (4 rows) 220 | 221 | postgres=# 222 | 223 | ``` 224 | 225 | Moodle `config.php` would need changes: 226 | ``` 227 | $CFG->dbtype = 'pgsql'; 228 | $CFG->dbhost = 'postgres'; 229 | ``` 230 | 231 | ##### Upgrading Postgres to next major version 232 | 233 | I suggest to use https://github.com/tianon/docker-postgres-upgrade image to 234 | perform major release upgrade. For 12 to 13 upgrade I createed a new volume 235 | called `pgdata13`, then stop database gracefully by logging in into container 236 | and executing: 237 | 238 | ``` 239 | docker exec -it -u postgres moodle-dev-compose-postgres-1 bash 240 | postgres@ea13edb262fb:/$ pg_ctl stop 241 | ``` 242 | 243 | Now, when service is gracefully stopped, execute: 244 | ``` 245 | docker run --rm -v pgdata12:/var/lib/postgresql/12/data -v pgdata13:/var/lib/postgresql/13/data tianon/postgres-upgrade:12-to-13 246 | ``` 247 | 248 | This will perform upgrade. Finally, shut down your dev suit, make necessary changes in 249 | compose files and start again. 250 | 251 | 252 | ### Receiving mail 253 | 254 | We start Mailpit container by default that allows to receive email and provides 255 | interface to view it. 256 | 257 | In Moodle configuration file add: 258 | ``` 259 | $CFG->smtphosts = 'mail:1025' 260 | ``` 261 | 262 | Web interface to view emails is available at `http://moodle.local:8025` 263 | 264 | 265 | ## Adding PHP extensions 266 | ### Profiling 267 | 268 | You can add Xhprof extension and use Moodle built-in profiling tool. 269 | 270 | ```bash 271 | > docker exec -it moodle-dev-compose-moodle-1 bash 272 | root@62ba9a041e0f:/var/www/html# pecl install xhprof 273 | WARNING: channel "pecl.php.net" has updated its protocols, use "pecl channel-update pecl.php.net" to update 274 | downloading xhprof-2.2.0.tgz ... 275 | ... 276 | root@62ba9a041e0f:/var/www/html# docker-php-ext-enable xhprof 277 | root@62ba9a041e0f:/var/www/html# php -i | grep xhprof 278 | /usr/local/etc/php/conf.d/docker-php-ext-xhprof.ini, 279 | xhprof 280 | xhprof support => enabled 281 | xhprof.collect_additional_info => 0 => 0 282 | xhprof.output_dir => no value => no value 283 | xhprof.sampling_depth => 0x7fffffff => 0x7fffffff 284 | xhprof.sampling_interval => 100000 => 100000 285 | ``` 286 | Xhprof extension has been installed and enabled, restart the web container. 287 | 288 | ```bash 289 | > docker restart moodle-dev-compose-moodle-1 290 | ``` 291 | 292 | A this point you can navigate to Site administration > Development > 293 | Profiling, enable it and trigger for the content you need to profile. 294 | 295 | Notice, if you destroy container, you will need to repeat above steps on the 296 | new one. 297 | 298 | ## More containers 299 | 300 | Suppose you need another container with a different php version, but for the 301 | same Moodle instance. You can easily add another container either directly to 302 | existing compose file, or by using a seaparate file: 303 | 304 | Create a new file called `compose.local.yaml` containing: 305 | 306 | ``` 307 | services: 308 | moodle83: 309 | image: moodlehq/moodle-php-apache:8.3 310 | volumes: 311 | - moodledata:/var/www/moodledata 312 | - $MOODLE_DOCKER_WWWROOT:/var/www/html 313 | depends_on: 314 | - mariadb 315 | environment: 316 | - VIRTUAL_HOST=moodle83.local 317 | networks: 318 | - devbox 319 | ``` 320 | 321 | Now, stop the existing containers and modify `compose.yaml`, to [`include`](https://docs.docker.com/compose/multiple-compose-files/include/) `compose.local.yaml`: 322 | 323 | ``` 324 | include: 325 | - mariadb.service.yaml 326 | # - postgres.service.yaml 327 | - compose.local.yaml 328 | ``` 329 | 330 | This will bring a new container into play on the next `docker compose up` run. 331 | 332 | Note: if you prefer not to use `include` section in `compose.yaml`, alternative way to use many config 333 | files is: 334 | ```bash 335 | > docker compose -f compose.yaml -f compose.local.yaml up 336 | ``` 337 | 338 | Add the new host to your `/etc/hosts` and you can start using it on `http://moodle83.local`. 339 | 340 | ``` 341 | 127.0.0.1 moodle.local moodle83.local 342 | ``` 343 | 344 | ## Using SSL 345 | 346 | If you need access by `https`, create self-signed certificates named 347 | `localhost`: 348 | 349 | ``` 350 | $ mkdir certs 351 | $ openssl req -x509 -nodes -newkey rsa:2048 -keyout certs/localhost.key -out certs/localhost.crt 352 | ``` 353 | 354 | Then use them as part of nginx-proxy configuration: 355 | 356 | ``` 357 | diff --git a/compose.yaml b/compose.yaml 358 | index 0bc7fb0..934cc58 100644 359 | --- a/compose.yaml 360 | +++ b/compose.yaml 361 | @@ -5,9 +5,11 @@ services: 362 | container_name: nginx-proxy 363 | ports: 364 | - "80:80" 365 | + - "443:443" 366 | volumes: 367 | - /var/run/docker.sock:/tmp/docker.sock:ro 368 | - ./nginx_proxy.conf:/etc/nginx/conf.d/nginx_proxy.conf:ro 369 | + - ./certs:/etc/nginx/certs 370 | networks: 371 | - devbox 372 | moodle: 373 | ``` 374 | 375 | and in the service that you need to make accesible over `https`, add `CERT_NAME` env var: 376 | 377 | ``` 378 | - mariadb 379 | environment: 380 | - VIRTUAL_HOST=moodle.local 381 | + - CERT_NAME=localhost 382 | networks: 383 | - devbox 384 | ``` 385 | 386 | Also in Moodle config make sure `wwwroot` reflects correct protocol: 387 | ``` 388 | $CFG->wwwroot = 'https://moodle.local'; 389 | $CFG->sslproxy = true; 390 | ``` 391 | 392 | ## Other services 393 | 394 | Using docker you can add any service you need. Examples below assume you are 395 | adding configuration to `compose.local.yaml`. 396 | 397 | ### Memcached 398 | 399 | Adding memcached will make your instance running faster. 400 | 401 | Your `compose.local.yaml` should contain: 402 | 403 | ``` 404 | services: 405 | memcached0: 406 | image: memcached:1.4.33 407 | networks: 408 | - devbox 409 | ``` 410 | 411 | In Moodle, navigate to cache configuration and create instance using 412 | `memcached0` as hostname and `11211` as port. 413 | 414 | ### SAML2 415 | 416 | In order to deploy SAML2 IdP, we will be using [kristophjunge/docker-test-saml-idp](https://github.com/kristophjunge/docker-test-saml-idp) docker image. 417 | 418 | You need [auth_saml2](https://github.com/catalyst/moodle-auth_saml2) plugin to be installed in Moodle, it will act as SAML2 service provider (SP). 419 | 420 | Your `compose.local.yaml` should contain: 421 | 422 | ``` 423 | services: 424 | samlidp: 425 | image: kristophjunge/test-saml-idp 426 | environment: 427 | VIRTUAL_HOST: samlidp.local 428 | SIMPLESAMLPHP_SP_ENTITY_ID: http://moodle.local/auth/saml2/sp/metadata.php 429 | SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE: http://moodle.local/auth/saml2/sp/saml2-acs.php/moodle.local 430 | SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE: http://moodle.local/auth/saml2/sp/saml2-logout.php/moodle.local 431 | SIMPLESAMLPHP_ADMIN_PASSWORD: admin1 432 | SIMPLESAMLPHP_SECRET_SALT: salt 433 | ports: 434 | - "8080:8080" 435 | - "8443:8443" 436 | volumes: 437 | - ./saml2/authsources.php:/var/www/simplesamlphp/config/authsources.php 438 | networks: 439 | devbox: 440 | aliases: 441 | - samlidp.local 442 | ``` 443 | 444 | Those `SIMPLESAMLPHP_SP_*` values can be identified from SP metadata output (`http://moodle.local/auth/saml2/sp/metadata.php`). 445 | 446 | You also need `saml2/authsources.php` containing user accounts, which is mounted 447 | in container, use example below as starting point: 448 | 449 | ``` 450 | [ 455 | 'core:AdminPassword', 456 | ], 457 | 458 | 'example-userpass' => [ 459 | 'exampleauth:UserPass', 460 | 'samlu1:samlu1pass' => [ 461 | 'uid' => ['samlu1'], 462 | 'eduPersonAffiliation' => ['group1'], 463 | 'email' => 'samluser1@example.com', 464 | 'firstName' => 'Saml', 465 | 'lastName' => 'User 1', 466 | 'customOrg' => 'Company 1', 467 | ], 468 | 'samlu2:samlu2pass' => [ 469 | 'uid' => ['samlu2'], 470 | 'eduPersonAffiliation' => ['group2'], 471 | 'email' => 'samluser2@example.com', 472 | 'firstName' => 'Saml', 473 | 'lastName' => 'User 2', 474 | 'customOrg' => 'Company 2', 475 | ], 476 | ], 477 | ]; 478 | ``` 479 | 480 | When you start containers, navigate to 481 | `http://samlidp.local:8080/simplesaml/module.php/core/frontpage_federation.php`, 482 | you will find metadata link you can use in `auth_saml2` `idpmetadata` setting 483 | to complete setup (or you can use metadata XML that you can retrieve on the 484 | same page). 485 | 486 | If you get `Invalid metadata at 487 | http://samlidp.local:8080/simplesaml/saml2/idp/metadata.php` error, most 488 | likely CURL security settings do not permit URL access, check 489 | `curlsecurityblockedhosts` and `curlsecurityallowedport` config settings in 490 | Moodle. If you get `Setting secure cookie on plain http is not allowed`, untick `cookiesecure` in Moodle settings. 491 | 492 | Once configured (you need to add field mapping as well, for example snippet 493 | above you may need to map `firstName`, `lastName` and `email`), you should be 494 | able to login to Moodle using one of accounts defined in `authsources.php`. 495 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | include: 2 | - mariadb.service.yaml 3 | # - postgres.service.yaml 4 | # - compose.local.yaml 5 | 6 | services: 7 | nginx-proxy: 8 | image: nginxproxy/nginx-proxy 9 | ports: 10 | - "80:80" 11 | volumes: 12 | - /var/run/docker.sock:/tmp/docker.sock:ro 13 | - ./nginx_proxy.conf:/etc/nginx/conf.d/nginx_proxy.conf:ro 14 | networks: 15 | - devbox 16 | moodle: 17 | image: moodlehq/moodle-php-apache:$MOODLE_DOCKER_PHP_VERSION 18 | volumes: 19 | - moodledata:/var/www/moodledata 20 | - $MOODLE_DOCKER_WWWROOT:/var/www/html 21 | depends_on: 22 | - mariadb 23 | environment: 24 | - VIRTUAL_HOST=moodle.local 25 | networks: 26 | - devbox 27 | mail: 28 | image: axllent/mailpit:latest 29 | ports: 30 | - 8025:8025 31 | networks: 32 | - devbox 33 | volumes: 34 | moodledata: 35 | name: "moodledata" 36 | 37 | networks: 38 | devbox: 39 | driver: bridge 40 | ipam: 41 | driver: default 42 | config: 43 | - subnet: 172.31.0.0/16 44 | -------------------------------------------------------------------------------- /mariadb.service.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | mariadb: 3 | image: mariadb:10.6 4 | environment: 5 | - MYSQL_ROOT_PASSWORD=root 6 | - MYSQL_USER=moodle 7 | - MYSQL_PASSWORD=moodle 8 | volumes: 9 | - mysqldata:/var/lib/mysql 10 | ports: 11 | - 3306:3306 12 | networks: 13 | - devbox 14 | phpmyadmin: 15 | image: phpmyadmin/phpmyadmin 16 | environment: 17 | - PMA_HOST=mariadb 18 | - PMA_VERBOSE=mariadb 19 | - UPLOAD_LIMIT=256M 20 | depends_on: 21 | - mariadb 22 | ports: 23 | - 8081:80 24 | networks: 25 | - devbox 26 | volumes: 27 | mysqldata: 28 | name: "mysqldata" 29 | -------------------------------------------------------------------------------- /nginx_proxy.conf: -------------------------------------------------------------------------------- 1 | server_tokens off; 2 | client_max_body_size 1G; 3 | client_body_buffer_size 256M; 4 | proxy_read_timeout 600s; 5 | -------------------------------------------------------------------------------- /postgres.service.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: postgres:13 4 | environment: 5 | - POSTGRES_PASSWORD=postgres 6 | volumes: 7 | - pgdata13:/var/lib/postgresql/data 8 | ports: 9 | - 5432:5432 10 | networks: 11 | - devbox 12 | adminer: 13 | image: adminer 14 | environment: 15 | - ADMINER_DESIGN=nette 16 | - ADMINER_DEFAULT_SERVER=postgres 17 | ports: 18 | - 8082:8080 19 | networks: 20 | - devbox 21 | volumes: 22 | pgdata13: 23 | name: "pgdata13" 24 | --------------------------------------------------------------------------------