On-premise Installation
This is a description on how to install Autotech Development ABs Docker-based Moodle distribution on your own servers. On-premise in this instance doesn’t necessarily mean your local servers, you are free to host it in a cloud of your choice. On-premise is more “not in our cloud” than anything else.
Prerequisites
The following steps assumes that the following prerequisites are fullfilled:
Ubuntu 22.04 LTS
Docker (installed from the Official repository https://docs.docker.com/engine/install/ubuntu/)
A database server (database setup is not covered by this guide)
The possibility to connect to https://registry.autotech.se on port 443/TCP
See also
This guide assumes you have direct access to the registry. It’s also possible to set this up on server without direct access to the registry. More about that in the On-premise Secure Environments chapter.
Warning
Installation on other distributions than Ubuntu LTS is not supported by our normal installation fee. To help with any troubles that arises we will have to charge extra.
Note
It should be possible to use cloud managed databases, at least PostgreSQL, e.g. Digital Ocean, Azure, AWS, etc. It is verified to work with Digital Ocean and Azure.
System Preparation
User & Group
The container uses a less privileged user to run PHP-FPM and access files. It has the following UID & GID:
- UID:
2020- GID:
2020
Start by creating the group:
sudo groupadd -g 2020 sparkfore
Then create the user:
sudo useradd -s /sbin/nologin -u 2020 -g sparkfore sparkfore
Folders
Moodle needs to have access to persistent storage which we can mount into the container so our next step is to prepare for these folders.
sudo mkdir -p /data/moodledata
Set the correct ownership
sudo chown 2020:2020 /data/moodledata
Make shure that the permissions are correct
sudo chmod 755 /data/moodledata
Tip
This is our prefered method of organizing the data. It is possible to use a custom layout if the paths in docker-compose.yml is change to match the custom layout.
SystemD Services
We are using a couple of SystemD services to mange the startup process of our Docker Compose configuration. A required one that pulls the latest images and starts (and stops) the Docker Compose and an optional one that manages scheduled restarts (which will automatically update to the latest version).
Preparations
If you are running this in a virtualized environment we recommend that you install haveged. We have discovered that the start and stop times of Docker can be severely effected if it is missing.
sudo apt-get install haveged
Managing Docker Compose (required)
We will start by creating the service file and populating it with content. Our first step is to open the file in an editor,
sudo nano /etc/systemd/system/docker-compose.service
Tip
Nano is our prefered editor due to its simplicity but feel free to substitute it with your preference, e.g. vi, vim or whatever you like.
Next step is to populate the content of the file. The required content of the file is provided below but you will need to change <registry-user> and <registry-password> to the credentials you’ve received from us.
[Unit]
Description=Docker Compose container starter
After=docker.service network-online.target
Requires=docker.service network-online.target
[Service]
WorkingDirectory=/etc/docker-compose
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/usr/bin/docker login -u <registry-user> -p <registry-password> https://registry.autotech.se
ExecStartPre=/usr/bin/docker compose pull --quiet --ignore-pull-failures
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose pull --quiet --ignore-pull-failures
ExecReload=/usr/bin/docker compose up -d --remove-orphans
[Install]
WantedBy=docker.service
Now that we’ve created the file let’s make sure that it has the correct ownership and permissions:
sudo chown root:root /etc/systemd/system/docker-compose.service
sudo chmod 644 /etc/systemd/system/docker-compose.service
Reload SystemD to make it aware of the new service
sudo systemctl daemon-reload
Our last step is now to enable the service so that it autostarts after reboots.
sudo systemctl enable /etc/systemd/system/docker-compose.service
Caution
Do not try to start the service yet. It is still missing some requirements, mainly the Docker Compose file.
Scheduled Restarts (optional)
We are using the timer fucntionality of SystemD to schedule automatic updates of the images. If you want to implement it this way or do it manually is entirely up to you, this is not a requirement in any way.
We will begin with creating a service-file for the timer so open it up in an editor.
sudo nano /etc/systemd/system/docker-compose-reload.service
The contents of this requires no adaptation and can be used as is since it’s only reloading the docker-compose.service we created earlier.
[Unit]
Description=Refresh images and update containers
[Service]
Type=oneshot
ExecStart=/bin/systemctl reload docker-compose.service
Make sure it has the correct owner and permissions
sudo chown root:root /etc/systemd/system/docker-compose-reload.service
sudo chmod 644 /etc/systemd/system/docker-compose-reload.service
Now it is time to create the actual timer that’s responsible for running our newly created docker-compose-reload.service. To create a timer for docker-compose-reload.service we will create docker-compose-reload.timer.
sudo nano /etc/systemd/system/docker-compose-reload.service
You can use the provided timer example as it is if you want to. If there is something you might want to change it is the actual schedule. In our example it is configured to reload on every third tuesday of the month (this is to match up with our release schedule).
[Unit]
Description=Refresh images and update containers
[Timer]
OnCalendar=Tue *-*-15..21 03:00:00
RandomizedDelaySec=0
[Install]
WantedBy=timers.target
As always make sure the file as the correcet ownership and permissions.
sudo chown root:root /etc/systemd/system/docker-compose-reload.timer
sudo chmod 644 /etc/systemd/system/docker-compose-reload.timer
Reload SystemD to make it aware of the new service and timer
sudo systemctl daemon-reload
The next step is to enable the timer (just the timer and NOT the service)
sudo systemctl enable /etc/systemd/system/docker-compose-reload.timer
Automatic Cleanup (optional,recommended)
We are using the timer fucntionality of SystemD to schedule automatic cleanup of the images. If you want to implement it this way or do it manually is entirely up to you, this is not a requirement in any way (but we do recommend the automatic way since it tries to prevent Docker from using up all the disk space).
We will begin with creating a service-file for the timer so open it up in an editor.
sudo nano /etc/systemd/system/docker-auto-cleanup.service
The contents of this requires no adaptation and can be used as is since it’s directly running docker commands. The only thing one might want to change is the retention period, currently 720h, but we recommend sticking with the default in the beginning.
[Unit]
Description=Cleanup Docker directories
[Service]
Type=oneshot
ExecStart=/usr/bin/docker system prune -af --filter "until=720h"
Make sure it has the correct owner and permissions
sudo chown root:root /etc/systemd/system/docker-auto-cleanup.service
sudo chmod 644 /etc/systemd/system/docker-auto-cleanup.service
Now it is time to create the actual timer that’s responsible for running our newly created docker-auto-cleanup.service. To create a timer for docker-auto-cleanup.service we will create docker-auto-cleanup.timer.
sudo nano /etc/systemd/system/docker-auto-cleanup.timer
You can use the provided timer example as it is if you want to. If there is something you might want to change it is the actual schedule. In our example it is configured to reload on every third monday of the month (this is to match up with our release schedule). If you’re using a custom update schedule you might want to change this schedule also.
[Unit]
Description=Automatic Docker cleanup
[Timer]
OnCalendar=Mon *-*-14..20 03:00:00
RandomizedDelaySec=0
[Install]
WantedBy=timers.target
As always make sure the file as the correcet ownership and permissions.
sudo chown root:root /etc/systemd/system/docker-auto-cleanup.timer
sudo chmod 644 /etc/systemd/system/docker-auto-cleanup.timer
Reload SystemD to make it aware of the new service and timer
sudo systemctl daemon-reload
The next step is to enable the timer (just the timer and NOT the service)
sudo systemctl enable /etc/systemd/system/docker-auto-cleanup.timer
Docker Compose file
It is now time to create the Docker Compose file that will launch all the containers that are required. Our first step is to create a folder we will store docker-compose.yml and the related env-files for settings.
sudo mkdir -p /etc/docker-compose/settings
sudo chown -R root:root /etc/docker-compose
sudo chmod -R 700 /etc/docker-compose
Tip
Since we’ve now changed the permissions of /etc/docker-compose to only allow root access just using sudo can be a bit awkward. While it is possible to run all the commands listed below using sudo tab-completion won’t work and it’s not possble to list the contents of the directory. To switch to running as root user instead run this:
sudo su
Start by creating docker-compose.yml
sudo nano /etc/docker-compose/docker-compose.yml
You can use this template to create the docker-compose.yml file but you will need to change <your-moodle-image> and <your-moodle-cron-image> to the information you’ve received from us.
version: '3'
services:
moodle:
image: <your-moodle-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
cron:
image: <your-moodle-cron-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
networks:
autotech:
Caution
You will still not be able to access Moodle with this docker-compose.yml. No ports are exposed yet. See the section Ports, Proxy & Varnish
Caution
If you’ve used another layout for organizing your data you need to change the paths under volumes for both the moodle and cron service.
Ports, Proxy & Varnish
The following section is a bit fuzzt around the edges when it comes to hard recommendations, mostly because it’s very much up to what fits in your environment. It is possible to run things a multiple different ways with very small changes to the configuration.
If you want to run without SSL or if you want to use a non-docker reverse proxy (e.g. reverse proxy installed on host, external load balancer or similar) the steps are almost the same. What needs to be done is to expose the port for the Moodle container.
In the example below you can see how it’s done. The first value is the host port (8080) and the second value is the container port (80). The container port will always be 80 but you can change the host port to suite your needs. This might be an example for running the reverse proxy directly on the host.
version: '3'
services:
moodle:
image: <your-moodle-image>
restart: unless-stopped
ports:
- "8080:80"
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
cron:
image: <your-moodle-cron-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
networks:
autotech:
Reverse Proxy
If you want to deploy a all-in-one solution where everything is handled by Docker and docker-compose.yml we provide a preconfigured reverse proxy that can be used. It is based on nginx and prepared for SSL termination (mainly certificates from Let’s Encrypt but can be used with customer provided certificates also). When using a docker-compose.yml based solution there is no need to expose the port for the Moodle container. This is an example using our reverse proxy and Certbot for Let’s Encrypt certificates (using Cloudflare DNS validation).
version: '3'
services:
reverseproxy:
image: registry.autotech.se/tools/generic-moodle-reverseproxy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- ENVIRONMENT=production
- HOSTNAME=lms.example.com
- MOODLE_HOST=moodle
volumes:
- /data/ssl/conf:/etc/letsencrypt
- /data/ssl/data:/var/www/certbot
networks:
- autotech
depends_on:
- moodle
moodle:
image: <your-moodle-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
cron:
image: <your-moodle-cron-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
certbot:
image: certbot/dns-cloudflare
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini ; sleep 12h & wait $${!}; done;'"
restart: unless-stopped
volumes:
- /data/ssl/conf:/etc/letsencrypt
- /data/ssl/data:/var/www/certbot
- ./settings/cloudflare.ini:/cloudflare.ini:ro
# docker-compose run --entrypoint certbot --rm certbot certonly --non-interactive --agree-tos --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini -m <admin email> --cert-name sparkfore.com -d <your domain name>
networks:
autotech:
Note
During the initial creation of the certificate we give the certificate the name sparkfore.com, this name is what the reverse proxy image is looking for. Mounting a non-Let’s Encrypt certificate and giving it the same name will also work.
Varnish
We also have a customzied Varnish image that can be used for HTTP caching. It is pre-configured for Moodle and basically plug-n-play. As long as the service for Moodle is named moodle all that is needed is to add the Varnish service and change the MOODLE_HOST variable for the reverse proxy. Here is a complete example using our reverse proxy, varnish image and certbot for Let’s Encrypt certificates.
version: '3'
services:
reverseproxy:
image: registry.autotech.se/tools/generic-moodle-reverseproxy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- ENVIRONMENT=production
- HOSTNAME=lms.example.com
- MOODLE_HOST=varnish:7777
volumes:
- /data/ssl/conf:/etc/letsencrypt
- /data/ssl/data:/var/www/certbot
networks:
- autotech
depends_on:
- moodle
- varnish
varnish:
image: registry.autotech.se/tools/moodle-varnish:latest
restart: unless-stopped
environment:
- VARNISH_SIZE=512M
- VARNISH_HTTP_PORT=7777
tmpfs:
- /var/lib/varnish/varnishd:exec
networks:
- autotech
depends_on:
- moodle
moodle:
image: <your-moodle-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
cron:
image: <your-moodle-cron-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
networks:
- autotech
certbot:
image: certbot/dns-cloudflare
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini ; sleep 12h & wait $${!}; done;'"
restart: unless-stopped
volumes:
- /data/ssl/conf:/etc/letsencrypt
- /data/ssl/data:/var/www/certbot
- ./settings/cloudflare.ini:/cloudflare.ini:ro
# docker-compose run --entrypoint certbot --rm certbot certonly --non-interactive --agree-tos --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini -m <admin email> --cert-name sparkfore.com -d <your domain name>
networks:
autotech:
Settings
Our prefered method is to use multiple env-files grouped by context (as seen in docker-compose.yml above). The reasoning behind this is that almost all values must be shared between the moodle and cron container and this prevents us from having to write every setting twice.
The format of the settings file can be found in detail here: https://docs.docker.com/compose/environment-variables/env-file/ but the basics are as this:
VAR=value
app-moodle.env
This file contains settings for configuring the behavior of Moodle (except the database connection).
Required (always)
These variables always needs be defined for Moodle to work correctly (or at all).
- MOODLE_APP_URL:
The full base URL for the LMS, including protocol, but no trailing slash. For example:
https://lms.example.comNote: Moodle actually saves this value in the database when making some actions. In practice this means that if you change this value afterwards the saved values will still point to the old URL. Our recommendation is to use the correct URL from the begining.
Warning: Moodle behaves a bit odd when running on a non-standard port (not 80 or 443). So if you want to test Moodle locally you might try to run it by mapping the host port 8080 to the container port 80. This will however put you in an eternal redirect loop. To fix this loop you need to set the APP_URL to http://localhost:8080 and NGINX_PORT to 8080. This will cause the container port to be 8080 so you would then need to map host port 8080 to container port 8080.
- MOODLE_SSL_PROXY:
As described in the On-premise System Architecture chapter the Moodle container itself only responds to HTTP on port 80, no builtin SSL termination. HTTPS must be handled by a reverse proxy or load balancer. When running Moodle behind a reverse proxy or load balancer that terminates SSL it must be made aware of this. Setting this variable to 1 will make Moodle aware of the SSL termination. For now the default is 0 and therefore this variable must be defined and set to 1 in most scenarios.
Valid options:
0(default)1
Required (install)
These variables are only required during the installation of Moodle, meaning most often just the first start. During the installation Moodle creates a Super Administrator account using this information. When Moodle has been installed it’s possible to remove this information if it’s desired. Part of the reasoning behind only giving root access to this folder is to protect information of this kind.
- MOODLE_ADMIN_EMAIL:
The email to use for the default Super Administrator account
- MOODLE_ADMIN_USER:
The username to use for the default Super Administrator account
- MOODLE_ADMIN_PASS:
The password to use for the default Super Administrator account
Optional
The variables in this section are generally not defined by default but they have functionality that, in some cases, might be useful.
- MOODLE_NO_EMAILS:
If you define this variable and set it to 1 Moodle will not send any emails at all. This feature is almost exclusively used in troubleshooting scenarios.
Valid options:
0(default)1
database-moodle.env
This file is for specifying the database connection options. While these options still are for Moodle we have separated them to a separate file for easier overview.
Common Settings (required)
- MOODLE_DB_TYPE:
What type of database is Moodle trying to connect to?
Valid options:
pgsql(default)mariadbmysqlsqlsrv
- MOODLE_DB_USER:
Username for connecting to the database
- MOODLE_DB_PASS:
Password for connecting to the database
- MOODLE_DB_NAME:
Name of the database that is to be used
- MOODLE_DB_HOST:
Hostname, eg ‘localhost’ or ‘db.isp.com’, or IP
- MOODLE_DB_PORT:
Port used for connecting to the database server. Defaults to
5432
Common Settings (optional)
- MOODLE_DB_PREFIX:
This is the prefix to use for all table names. Defaults to
mdl_
PostgreSQL Specific (optional)
- MOODLE_DB_FETCHBUFFERSIZE:
Set the limit on the number of rows that are fetched into
memory when doing a large recordset query (e.g. search
indexing). Set to0to turn it off. You need to set to0if you
are using pg_bouncer in ‘transaction’ mode (it is fine in ‘session’
mode). Default is100000.- MOODLE_DB_PGBOUNCER:
When set to
trueit enables the “dbhandlesoptions” setting to
true.Valid options:
truefalse(default)
- MOODLE_DB_SSL:
Set this to
trueto enable support for connect to the database
over SSL.Valid options:
truefalse(default)
- MOODLE_DB_SSLMODE:
Set what SSL mode PostgreSQL will be using. Verified to be
working withrequireValid options:
allowpreferrequireverify-caverify-full
MS SQL Server Specific (optional)
These options are only valid from Moodle 4.2 an upwards.
Microsoft ODBC 18 by default tries to encrypt the database connection and these options are to help connect to databases that doesn’t encrypt the connection.
- MOODLE_DB_SQLSRV_ENCRYPT:
Should the connection be encrypted?
Valid options:
truefalse
- MOODLE_DB_SQLSRV_TRUSTSERVERCERTIFICATE:
Should the SQL Server certificate be trusted
(e.g. self-signed certificates)?Valid options:
truefalse
Custom Root Certificates
Sometimes, mainly when connecting to Active Directory/LDAP for synchronizing users, there’s a need to add custom root certificates. Our images support this and all you really need to do is to make sure that the certificate is in PEM-format and then mount it into the container on the correct location. The path you need to mount the certificate to is /usr/local/share/ca-certificates/ (in both moodle and cron) and when the container starts it will then add the mounted certificate(s) to its CA-store. Here is an example of a docker-compose.yml with a custom root certificate mounted.
version: '3'
services:
moodle:
image: <your-moodle-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
- ./settings/shared-email.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
- ./custom-root-ca.pem:/usr/local/share/ca-certificates/custom-root-ca.crt
networks:
- autotech
cron:
image: <your-moodle-cron-image>
restart: unless-stopped
env_file:
- ./settings/database-moodle.env
- ./settings/app-moodle.env
- ./settings/shared-email.env
#- ./settings/shared-debug.env
volumes:
- /data/moodledata:/moodledata/
- ./custom-root-ca.pem:/usr/local/share/ca-certificates/custom-root-ca.crt
networks:
- autotech
networks:
autotech:
Performance Tunings
Depending on the type of load the installation is under it might be required to tweak the settings of PHP or PHP-FPM. Therefore various settings for PHP and PHP-FPM are exposed as environmen variables. The following is a list of the available variables and their default values.
- FPM_PM_MAX_SPARE_SERVICE:
3- FPM_PM_MAX_REQUESTS:
0- FPM_PM_MAX_CHILDREN:
5- FPM_PM_START_SERVICE:
2- FPM_PM:
dynamic- FPM_PM_MIN_SPARE_SERVICE:
1- FPM_PM_PROCESS_IDLE_TIMEOUT:
10s- PHP_OPCACHE_ENABLE_FILE_OVERRIDE:
0- PHP_MEMORY_LIMIT:
128M- PHP_MAX_FILE_UPLOADS:
20- PHP_OPCACHE_SAVE_COMMENTS:
1- PHP_OPCACHE_MAX_ACCELERATED_FILES:
8000- PHP_OPCACHE_REVALIDATE_FREQ:
60- PHP_OPCACHE_MEMORY_CONSUMPTION:
128- PHP_MAX_INPUT_TIME:
60- PHP_MAX_EXECUTION_TIME:
30
More Environtment Variables
The default value is listed along them.
- LANG:
C.UTF-8
- TZ:
Europe/Stockholm
- NGINX_HOST:
_
- NGINX_PORT:
80
- MOODLE_REVERSEPROXY:
false
- MOODLE_CLUSTERED:
false