How to Install Drupal with Docker on Ubuntu 22.04
Drupal is an open-source content management system (CMS) written in PHP. Many organizations worldwide use it to create blogs, government sites, corporate websites, and more. It comes with a growing set of features and modules to extend its functionality, allowing you to create any website you want.
This tutorial will teach you how to install Drupal using Docker on a Ubuntu 22.04 server. Drupal works with PHP 8.2 and MySQL. It also supports PostgreSQL since version 9 but there are some bugs. Therefore, we will stick to using MySQL for our tutorial. We will use Docker Compose to integrate Drupal with Nginx and Certbot tool to server a site Drupal website on secure HTTPS protocol.
Prerequisites
- A server running Ubuntu 22.04 with a minimum of 1GB of RAM for smaller communities. To host larger communities, you should get a server with a minimum of 2GB of RAM or more.
- A non-root user with sudo privileges.
- A fully qualified domain name (FQDN) pointing to your server. For our purposes, we will use
example.com
as the domain name. - Make sure everything is updated.
- $ sudo apt update
- Install basic utility packages. Some of them may already be installed.
- $ sudo apt install wget curl nano software-properties-common dirmngr apt-transport-https gnupg gnupg2 ca-certificates lsb-release ubuntu-keyring unzip -y
Step 1 — Configure Firewall
The first step is to configure the firewall. Ubuntu comes with ufw (Uncomplicated Firewall) by default.
Check if the firewall is running.
$ sudo ufw status
You should get the following output.
Status: inactive
Allow SSH port so the firewall doesn’t break the current connection on enabling it.
$ sudo ufw allow OpenSSH
Allow HTTP and HTTPS ports as well.
$ sudo ufw allow http
$ sudo ufw allow https
Enable the Firewall
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Check the status of the firewall again.
$ sudo ufw status
You should see a similar output.
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
80/tcp ALLOW Anywhere
443 ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
Step 2 — Install Docker and Docker Compose
Ubuntu 22.04 ships with an older version of Docker. To install the latest version, first, import the Docker GPG key.
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Create a Docker repository file.
$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install the latest version of Docker.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify that it is running.
$ sudo systemctl status docker
? docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023-01-14 10:41:35 UTC; 2min 1s ago
TriggeredBy: ? docker.socket
Docs: https://docs.docker.com
Main PID: 2054 (dockerd)
Tasks: 52
Memory: 22.5M
CPU: 248ms
CGroup: /system.slice/docker.service
?? 2054 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
By default, Docker requires root privileges. If you want to avoid using sudo
every time you run the docker
command, add your username to the docker
group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su - $USER
Confirm that your user is added to the Docker group.
$ groups
navjot wheel docker
Step 3 — Create Docker Compose File for Drupal
Create the directory for Drupal.
$ mkdir ~/drupal
Switch to the directory.
$ cd ~/drupal
Create and open the docker-compose.yml
file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
env_file: .env
volumes:
- db-data:/var/lib/mysql
networks:
- internal
drupal:
image: drupal:10-fpm-alpine
container_name: drupal
depends_on:
- mysql
restart: unless-stopped
networks:
- internal
- external
volumes:
- drupal-data:/var/www/html
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
- drupal
restart: unless-stopped
ports:
- 80:80
volumes:
- drupal-data:/var/www/html
- ./nginx-conf:/etc/nginx/conf.d
- certbot-etc:/etc/letsencrypt
networks:
- external
certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- drupal-data:/var/www/html
command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --staging -d example.com -d www.example.com
networks:
external:
driver: bridge
internal:
driver: bridge
volumes:
drupal-data:
db-data:
certbot-etc:
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through each service defined in the above file.
MySQL Docker Service
Here we are pulling the latest mysql:8.0 image from the Docker hub. We are using the 8.x version instead of using the latest tag. This way we can stick to the stable and tested version of MySQL which works with Drupal. We have set a name for the container, which can be used with Docker commands to stop, start and view logs. The container will continue running unless stopped manually. We have defined a .env
file that we will populate with MySQL credentials. We have also mounted a named volume db-data
to the /var/lib/mysql
directory on the container. The MySQL service will use an internal network to connect with drupal.
Drupal Service
We are using the Drupal 10 Alpine image. Alpine docker images are smaller in size. This image also contains PHP-FPM to handle PHP processing. This will work alongside Nginx to serve the site. The depends_on
option tells Drupal to connect with the MySQL service. It also ensures that the Drupal container will always start after the MySQL container. Drupal uses the internal network to connect with MySQL and the external network to expose itself to other containers. We have also created a named volume for Drupal to point to /var/www/html
directory in the container.
Nginx service
We are using an Alpine image for Nginx. It exposes port 80 to the host. We use two named volumes, one for Drupal’s public directory and the other for storing Let’s Encrypt SSL certificates. The third volume is bind mount to the Nginx configuration directory on the host which we will define later. Nginx also connects to an external Docker network for the Drupal site to work.
Certbot service
And at last, we pull the Certbot image to install SSL certificates. It shares its volumes with the Nginx service for the certificates and webroot definition. We have also included a command that will run when the container is created. Here the command uses the --staging
flag to get a test server for the first time. We need nginx to validate the certificates but Nginx won't start if the certificates are missing. This is why we will create a staging certificate, use that to start Nginx, and then create the real certificates.
Step 4 — Create Nginx Configuration
Create the directory for Nginx configuration.
$ mkdir nginx-conf
Create and open the file for Nginx.
$ nano nginx-conf/drupal.conf
Paste the following code into it.
server jpeg
Save the file by pressing Ctrl + X and entering Y when prompted.
In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client’s request for certificates, PHP processing, and static asset requests. For now, the Nginx will listen only on port 80 to allow Certbot to request for the staging certificate by placing a temporary file in the /var/www/html/.well-known/acme-challenge
directory to validate the DNS. This allows us to use Certbot with the webroot plugin.
Step 5 — Generate SSL certificates
To generate SSL certificates, we will start our containers. The correct staging certificates will be available at the /etc/letsencrypt/live
folder in the Nginx container.
$ docker compose up -d
Check the status of the services.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot "certbot certonly --…" certbot 6 minutes ago Exited (1) 5 minutes ago
drupal drupal:10-fpm-alpine "docker-php-entrypoi…" drupal 6 minutes ago Up 6 minutes 9000/tcp
mysql mysql:8.0 "docker-entrypoint.s…" mysql 6 minutes ago Up 6 minutes 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine "/docker-entrypoint.…" webserver 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
The Certbot container exits successfully after generating the certificate. Check the location of the certificates in the Nginx container.
$ docker compose exec webserver ls -la /etc/letsencrypt/live
You will get the following output.
total 16
drwx------ 3 root root 4096 Jan 17 09:15 .
drwxr-xr-x 9 root root 4096 Jan 17 09:15 ..
-rw-r--r-- 1 root root 740 Jan 17 09:15 README
drwxr-xr-x 2 root root 4096 Jan 17 09:15 drupal.example.com
This confirms that everything is successful. The next step is to generate the actual certificates.
Open the docker-compose.yml
file for editing.
$ nano docker-compose.yml
Replace the --staging
flag in the Certbot service section and replace it with the --force-renewal
flag. This tells Certbot to request new certificates for your domain. The renewal flag is used because that will be used to renew the certificates from here on.
certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- drupal-data:/var/www/html
command: certonly --webroot --webroot-path=/var/www/html --email name@example.com --agree-tos --no-eff-email --staple-ocsp --force-renewal -d drupal.example.com
Save the file by pressing Ctrl + X and entering Y when prompted.
Run the docker compose up
command again to recreate the Certbot container. The --no-deps
flag tells Certbot to skip starting the webserver
container since it is already running.
$ docker compose up --force-recreate --no-deps certbot
You will get the following output.
[+] Running 1/0
? Container certbot Recreated 0.1s
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for drupal.example.com
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/drupal.example.com/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/drupal.example.com/privkey.pem
certbot | This certificate expires on 2023-04-17.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot exited with code 0
Step 6 — Configure Nginx for SSL
Now that our certificates are live, we need to configure Nginx to serve them and redirect HTTP requests to HTTPS.
Stop the Nginx server.
$ docker stop webserver
Create a new Nginx file for SSL configuration and open it for editing.
$ nano nginx-conf/drupal-ssl.conf
Paste the following code in it.
server
listen 80;
listen [::]:80;
server_name drupal.example.com;
location ~ /.well-known/acme-challenge
allow all;
root /var/www/html;
location /
rewrite ^ https://$host$request_uri? permanent;
server jpg
Save the file by pressing Ctrl + X and entering Y when prompted.
The HTTP block specifies the location for Certbot’s webroot plugin and redirects any HTTP request to HTTPS.
The next step is to make sure the Nginx container listens to port 443. Open the docker-compose.yml
file for editing.
$ nano docker-compose.yml
In the Nginx section of the file, make changes to expose 443 and enable SSL as shown below.
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
- drupal
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- drupal-data:/var/www/html
- ./nginx-conf:/etc/nginx/conf.d
- certbot-etc:/etc/letsencrypt
- /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem
networks:
- external
Save the file by pressing Ctrl + X and entering Y when prompted.
Now that we have enabled and added SSL configuration for Nginx, you can delete the older HTTP configuration file.
$ rm nginx-conf/drupal.conf
Before restarting Nginx, we need to generate a Diffie-Hellman group certificate which we have already configured above.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Recreate the Nginx container.
$ docker compose up -d --force-recreate --no-deps webserver
Check the status of the containers.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot "certbot certonly --…" certbot 3 hours ago Exited (0) 3 hours ago
drupal drupal:10-fpm-alpine "docker-php-entrypoi…" drupal 3 hours ago Up 3 hours 9000/tcp
mysql mysql:8.0 "docker-entrypoint.s…" mysql 3 hours ago Up 3 hours 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine "/docker-entrypoint.…" webserver 15 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
Step 7 — Start Drupal Web Installer
It is time to start the Drupal web installer. Open the URL https://drupal.example.com
in your browser and you will get the following screen.
$(lsb_release -cs) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install the latest version of Docker.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify that it is running.
$ sudo systemctl status docker
? docker.service — Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023–01–14 10:41:35 UTC; 2min 1s ago
TriggeredBy: ? docker.socket
Docs: https://docs.docker.com
Main PID: 2054 (dockerd)
Tasks: 52
Memory: 22.5M
CPU: 248ms
CGroup: /system.slice/docker.service
?? 2054 /usr/bin/dockerd -H fd:// — containerd=/run/containerd/containerd.sock
By default, Docker requires root privileges. If you want to avoid using sudo every time you run the docker command, add your username to the docker group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su — $USER
Confirm that your user is added to the Docker group.
$ groups
navjot wheel docker
Step 3 — Create Docker Compose File for Drupal
Create the directory for Drupal.
$ mkdir ~/drupal
Switch to the directory.
$ cd ~/drupal
Create and open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
env_file: .env
volumes:
— db-data:/var/lib/mysql
networks:
— internal
drupal:
image: drupal:10-fpm-alpine
container_name: drupal
depends_on:
— mysql
restart: unless-stopped
networks:
— internal
— external
volumes:
— drupal-data:/var/www/html
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
networks:
— external
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email sammy@your_domain — agree-tos — no-eff-email — staging -d example.com -d www.example.com
networks:
external:
driver: bridge
internal:
driver: bridge
volumes:
drupal-data:
db-data:
certbot-etc:
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through each service defined in the above file.
MySQL Docker Service
Here we are pulling the latest mysql:8.0 image from the Docker hub. We are using the 8.x version instead of using the latest tag. This way we can stick to the stable and tested version of MySQL which works with Drupal. We have set a name for the container, which can be used with Docker commands to stop, start and view logs. The container will continue running unless stopped manually. We have defined a .env file that we will populate with MySQL credentials. We have also mounted a named volume db-data to the /var/lib/mysql directory on the container. The MySQL service will use an internal network to connect with drupal.
Drupal Service
We are using the Drupal 10 Alpine image. Alpine docker images are smaller in size. This image also contains PHP-FPM to handle PHP processing. This will work alongside Nginx to serve the site. The depends_on option tells Drupal to connect with the MySQL service. It also ensures that the Drupal container will always start after the MySQL container. Drupal uses the internal network to connect with MySQL and the external network to expose itself to other containers. We have also created a named volume for Drupal to point to /var/www/html directory in the container.
Nginx service
We are using an Alpine image for Nginx. It exposes port 80 to the host. We use two named volumes, one for Drupal’s public directory and the other for storing Let’s Encrypt SSL certificates. The third volume is bind mount to the Nginx configuration directory on the host which we will define later. Nginx also connects to an external Docker network for the Drupal site to work.
Certbot service
And at last, we pull the Certbot image to install SSL certificates. It shares its volumes with the Nginx service for the certificates and webroot definition. We have also included a command that will run when the container is created. Here the command uses the — staging flag to get a test server for the first time. We need nginx to validate the certificates but Nginx won’t start if the certificates are missing. This is why we will create a staging certificate, use that to start Nginx, and then create the real certificates.
Step 4 — Create Nginx Configuration
Create the directory for Nginx configuration.
$ mkdir nginx-conf
Create and open the file for Nginx.
$ nano nginx-conf/drupal.conf
Paste the following code into it.
server ico
Save the file by pressing Ctrl + X and entering Y when prompted.
In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client’s request for certificates, PHP processing, and static asset requests. For now, the Nginx will listen only on port 80 to allow Certbot to request for the staging certificate by placing a temporary file in the /var/www/html/.well-known/acme-challenge directory to validate the DNS. This allows us to use Certbot with the webroot plugin.
Step 5 — Generate SSL certificates
To generate SSL certificates, we will start our containers. The correct staging certificates will be available at the /etc/letsencrypt/live folder in the Nginx container.
$ docker compose up -d
Check the status of the services.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 6 minutes ago Exited (1) 5 minutes ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 6 minutes ago Up 6 minutes 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 6 minutes ago Up 6 minutes 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
The Certbot container exits successfully after generating the certificate. Check the location of the certificates in the Nginx container.
$ docker compose exec webserver ls -la /etc/letsencrypt/live
You will get the following output.
total 16
drwx — — — 3 root root 4096 Jan 17 09:15 .
drwxr-xr-x 9 root root 4096 Jan 17 09:15 ..
-rw-r — r — 1 root root 740 Jan 17 09:15 README
drwxr-xr-x 2 root root 4096 Jan 17 09:15 drupal.example.com
This confirms that everything is successful. The next step is to generate the actual certificates.
Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Replace the — staging flag in the Certbot service section and replace it with the — force-renewal flag. This tells Certbot to request new certificates for your domain. The renewal flag is used because that will be used to renew the certificates from here on.
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email name@example.com — agree-tos — no-eff-email — staple-ocsp — force-renewal -d drupal.example.com
Save the file by pressing Ctrl + X and entering Y when prompted.
Run the docker compose up command again to recreate the Certbot container. The — no-deps flag tells Certbot to skip starting the webserver container since it is already running.
$ docker compose up — force-recreate — no-deps certbot
You will get the following output.
[+] Running 1/0
? Container certbot Recreated 0.1s
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for drupal.example.com
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/drupal.example.com/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/drupal.example.com/privkey.pem
certbot | This certificate expires on 2023–04–17.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | — The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot exited with code 0
Step 6 — Configure Nginx for SSL
Now that our certificates are live, we need to configure Nginx to serve them and redirect HTTP requests to HTTPS.
Stop the Nginx server.
$ docker stop webserver
Create a new Nginx file for SSL configuration and open it for editing.
$ nano nginx-conf/drupal-ssl.conf
Paste the following code in it.
server
listen 80;
listen [::]:80;
server_name drupal.example.com;
location ~ /.well-known/acme-challenge
allow all;
root /var/www/html;
location /
rewrite ^ https://$host$request_uri? permanent;
server jpeg
Save the file by pressing Ctrl + X and entering Y when prompted.
The HTTP block specifies the location for Certbot’s webroot plugin and redirects any HTTP request to HTTPS.
The next step is to make sure the Nginx container listens to port 443. Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
In the Nginx section of the file, make changes to expose 443 and enable SSL as shown below.
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
— 443:443
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
— /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem
networks:
— external
Save the file by pressing Ctrl + X and entering Y when prompted.
Now that we have enabled and added SSL configuration for Nginx, you can delete the older HTTP configuration file.
$ rm nginx-conf/drupal.conf
Before restarting Nginx, we need to generate a Diffie-Hellman group certificate which we have already configured above.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Recreate the Nginx container.
$ docker compose up -d — force-recreate — no-deps webserver
Check the status of the containers.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 3 hours ago Exited (0) 3 hours ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 3 hours ago Up 3 hours 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 3 hours ago Up 3 hours 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 15 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
Step 7 — Start Drupal Web Installer
It is time to start the Drupal web installer. Open the URL https://drupal.example.com in your browser and you will get the following screen.
Drupal Installer Home
Click the Save and continue button to proceed to the Installation profile page.
Drupal Installation Profile
We will stick to the Standard profile. Click the Save and continue button to proceed to the database configuration page.
Drupal Database Configuration
Fill in the database credentials we used in the environment file, expand the Advanced options section, and enter mysql as the database host. This matches the name of the MySQL service in our Docker compose file with which Drupal will need to connect.
Click the Save and continue button to continue. Drupal will start installing default modules and themes.
Drupal Module and Theme Installer
Next, you will be taken to the Drupal configuration page. Fill in the site name, email, username, password, and regional settings. Click the Save and continue button once you are finished.
Drupal Site Configuration
Finally, you will be taken to the Drupal dashboard. You can start using Drupal for making your website.
Drupal Dashboard
Step 8 — Configure Drupal
This step is optional but helps in improving the performance of Drupal. The first step is to set the MySQL transaction isolation level. The default transaction isolation level for MySQL, MariaDB, and equivalent databases is “REPEATABLE READ”. This setting with Drupal can result in deadlocks on tables, which will result in the site becoming very slow or not responding at all. The recommended transaction isolation level for Drupal sites is ‘READ COMMITTED’.
Log in to the MySQL container SSH shell.
$ docker exec -it mysql bash
Open the MySQL shell using the root user.
bash-4.4# mysql -u root -p
Enter password:
Run the following command to change the transaction level globally.
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Exit the MySQL shell and the container by typing exit twice.
The next step is to enter your domain as the trusted host to protect against HTTP HOST Header attacks. For this, we need to edit the /var/www/html/sites/default/settings.php file inside the Drupal container. Since we are using a named volume for Drupal files, the recommended way to make any changes is to copy the file from the container to the host, make the edit, and copy it back into the container. You can do this with any file you need to change inside the Drupal installation.
Copy the settings file from the container to the host.
$ docker cp drupal:/var/www/html/sites/default/settings.php settings.php
The file is in read-only mode. Give it writing permissions.
$ chmod+w settings.php
Open the file for editing.
$ nano settings.php
Find the following section in the file.
#$settings[‘trusted_host_patterns’] = [
# ‘^www\.example\.com$’,
#];
Uncomment it by removing the hash sign and adding your Drupal domain as shown below.
$settings[‘trusted_host_patterns’] = [
‘^drupal\.example\.com$’,
];
Save the file by pressing Ctrl + X and entering Y when prompted.
Remove the writing permissions again.
$ chmod -w settings.php
Copy the file back inside the container.
$ docker cp settings.php drupal:/var/www/html/sites/default
Step 9 — Backup Drupal
We will use the command line to back up the Drupal database. Switch to the Drupal directory.
$ cd ~/drupal
Create the directory for Backups.
$ mkdir backup-data
Use the following command to back up the Drupal database. You will be asked for your MySQL root password.
$ docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Enter password: root_password
The above command will create the SQL backup in the ~/drupal/backup-data directory.
Check the directory contents.
$ ls -al backup-data
total 6716
drwxrwxr-x 2 navjot navjot 4096 Jan 19 13:59 .
drwxrwxr-x 4 navjot navjot 4096 Jan 19 13:35 ..
-rw-rw-r — 1 navjot navjot 6868325 Jan 19 13:37 data_19–01–2023_13_36_58.sql
You can see the database backed up in the directory. You can restore this database using the phpMyAdmin tool or using the following command.
$ docker compose exec mysql sh -c “exec mysql -uroot -p” < backup-data/data_19–01–2023_13_36_58.sql
You can create a cron job to back up the database regularly.
Create the backup script in the /etc/cron.daily directory and open it for editing.
$ sudo nano /etc/cron.daily/drupalbackup.sh
Paste the following code in it.
#!/bin/bash
cd /home/navjot/drupal/
/usr/bin/docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ sudo chmod +x /etc/cron.daily/drupalbackup.sh
Now, your database will be backed up daily.
Step 10 — Upgrade Drupal
The first step in upgrading Drupal is to back up the Drupal database using the command from step 9.
Then, switch to the directory.
$ cd ~/drupal
Stop the Containers.
$ docker compose down
Pull the latest container images.
$ docker compose pull drupal:10-fpm-alpine
If you want to upgrade to the next major version, you will need to adjust the image name accordingly and go through Drupal’s release notes to check for any issues.
Make any changes you need in the docker-compose.yml if you want. The remaining images you can update by changing their definition in the Docker compose file.
Restart the Drupal containers. This will also pull the latest images for minor versions of other packages.
$ docker compose up -d
Conclusion
This concludes our tutorial on installing Drupal using Docker on a Ubuntu 22.04 server. If you have any questions, post them in the comments below.” width=”750" height=”417" />
Click the Save and continue button to proceed to the Installation profile page.
$(lsb_release -cs) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install the latest version of Docker.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify that it is running.
$ sudo systemctl status docker
? docker.service — Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023–01–14 10:41:35 UTC; 2min 1s ago
TriggeredBy: ? docker.socket
Docs: https://docs.docker.com
Main PID: 2054 (dockerd)
Tasks: 52
Memory: 22.5M
CPU: 248ms
CGroup: /system.slice/docker.service
?? 2054 /usr/bin/dockerd -H fd:// — containerd=/run/containerd/containerd.sock
By default, Docker requires root privileges. If you want to avoid using sudo every time you run the docker command, add your username to the docker group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su — $USER
Confirm that your user is added to the Docker group.
$ groups
navjot wheel docker
Step 3 — Create Docker Compose File for Drupal
Create the directory for Drupal.
$ mkdir ~/drupal
Switch to the directory.
$ cd ~/drupal
Create and open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
env_file: .env
volumes:
— db-data:/var/lib/mysql
networks:
— internal
drupal:
image: drupal:10-fpm-alpine
container_name: drupal
depends_on:
— mysql
restart: unless-stopped
networks:
— internal
— external
volumes:
— drupal-data:/var/www/html
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
networks:
— external
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email sammy@your_domain — agree-tos — no-eff-email — staging -d example.com -d www.example.com
networks:
external:
driver: bridge
internal:
driver: bridge
volumes:
drupal-data:
db-data:
certbot-etc:
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through each service defined in the above file.
MySQL Docker Service
Here we are pulling the latest mysql:8.0 image from the Docker hub. We are using the 8.x version instead of using the latest tag. This way we can stick to the stable and tested version of MySQL which works with Drupal. We have set a name for the container, which can be used with Docker commands to stop, start and view logs. The container will continue running unless stopped manually. We have defined a .env file that we will populate with MySQL credentials. We have also mounted a named volume db-data to the /var/lib/mysql directory on the container. The MySQL service will use an internal network to connect with drupal.
Drupal Service
We are using the Drupal 10 Alpine image. Alpine docker images are smaller in size. This image also contains PHP-FPM to handle PHP processing. This will work alongside Nginx to serve the site. The depends_on option tells Drupal to connect with the MySQL service. It also ensures that the Drupal container will always start after the MySQL container. Drupal uses the internal network to connect with MySQL and the external network to expose itself to other containers. We have also created a named volume for Drupal to point to /var/www/html directory in the container.
Nginx service
We are using an Alpine image for Nginx. It exposes port 80 to the host. We use two named volumes, one for Drupal’s public directory and the other for storing Let’s Encrypt SSL certificates. The third volume is bind mount to the Nginx configuration directory on the host which we will define later. Nginx also connects to an external Docker network for the Drupal site to work.
Certbot service
And at last, we pull the Certbot image to install SSL certificates. It shares its volumes with the Nginx service for the certificates and webroot definition. We have also included a command that will run when the container is created. Here the command uses the — staging flag to get a test server for the first time. We need nginx to validate the certificates but Nginx won’t start if the certificates are missing. This is why we will create a staging certificate, use that to start Nginx, and then create the real certificates.
Step 4 — Create Nginx Configuration
Create the directory for Nginx configuration.
$ mkdir nginx-conf
Create and open the file for Nginx.
$ nano nginx-conf/drupal.conf
Paste the following code into it.
server png)$
expires max;
log_not_found off;
Save the file by pressing Ctrl + X and entering Y when prompted.
In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client’s request for certificates, PHP processing, and static asset requests. For now, the Nginx will listen only on port 80 to allow Certbot to request for the staging certificate by placing a temporary file in the /var/www/html/.well-known/acme-challenge directory to validate the DNS. This allows us to use Certbot with the webroot plugin.
Step 5 — Generate SSL certificates
To generate SSL certificates, we will start our containers. The correct staging certificates will be available at the /etc/letsencrypt/live folder in the Nginx container.
$ docker compose up -d
Check the status of the services.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 6 minutes ago Exited (1) 5 minutes ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 6 minutes ago Up 6 minutes 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 6 minutes ago Up 6 minutes 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
The Certbot container exits successfully after generating the certificate. Check the location of the certificates in the Nginx container.
$ docker compose exec webserver ls -la /etc/letsencrypt/live
You will get the following output.
total 16
drwx — — — 3 root root 4096 Jan 17 09:15 .
drwxr-xr-x 9 root root 4096 Jan 17 09:15 ..
-rw-r — r — 1 root root 740 Jan 17 09:15 README
drwxr-xr-x 2 root root 4096 Jan 17 09:15 drupal.example.com
This confirms that everything is successful. The next step is to generate the actual certificates.
Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Replace the — staging flag in the Certbot service section and replace it with the — force-renewal flag. This tells Certbot to request new certificates for your domain. The renewal flag is used because that will be used to renew the certificates from here on.
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email name@example.com — agree-tos — no-eff-email — staple-ocsp — force-renewal -d drupal.example.com
Save the file by pressing Ctrl + X and entering Y when prompted.
Run the docker compose up command again to recreate the Certbot container. The — no-deps flag tells Certbot to skip starting the webserver container since it is already running.
$ docker compose up — force-recreate — no-deps certbot
You will get the following output.
[+] Running 1/0
? Container certbot Recreated 0.1s
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for drupal.example.com
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/drupal.example.com/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/drupal.example.com/privkey.pem
certbot | This certificate expires on 2023–04–17.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | — The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot exited with code 0
Step 6 — Configure Nginx for SSL
Now that our certificates are live, we need to configure Nginx to serve them and redirect HTTP requests to HTTPS.
Stop the Nginx server.
$ docker stop webserver
Create a new Nginx file for SSL configuration and open it for editing.
$ nano nginx-conf/drupal-ssl.conf
Paste the following code in it.
server
listen 80;
listen [::]:80;
server_name drupal.example.com;
location ~ /.well-known/acme-challenge
allow all;
root /var/www/html;
location /
rewrite ^ https://$host$request_uri? permanent;
server png)$
expires max;
log_not_found off;
Save the file by pressing Ctrl + X and entering Y when prompted.
The HTTP block specifies the location for Certbot’s webroot plugin and redirects any HTTP request to HTTPS.
The next step is to make sure the Nginx container listens to port 443. Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
In the Nginx section of the file, make changes to expose 443 and enable SSL as shown below.
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
— 443:443
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
— /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem
networks:
— external
Save the file by pressing Ctrl + X and entering Y when prompted.
Now that we have enabled and added SSL configuration for Nginx, you can delete the older HTTP configuration file.
$ rm nginx-conf/drupal.conf
Before restarting Nginx, we need to generate a Diffie-Hellman group certificate which we have already configured above.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Recreate the Nginx container.
$ docker compose up -d — force-recreate — no-deps webserver
Check the status of the containers.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 3 hours ago Exited (0) 3 hours ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 3 hours ago Up 3 hours 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 3 hours ago Up 3 hours 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 15 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
Step 7 — Start Drupal Web Installer
It is time to start the Drupal web installer. Open the URL https://drupal.example.com in your browser and you will get the following screen.
Drupal Installer Home
Click the Save and continue button to proceed to the Installation profile page.
Drupal Installation Profile
We will stick to the Standard profile. Click the Save and continue button to proceed to the database configuration page.
Drupal Database Configuration
Fill in the database credentials we used in the environment file, expand the Advanced options section, and enter mysql as the database host. This matches the name of the MySQL service in our Docker compose file with which Drupal will need to connect.
Click the Save and continue button to continue. Drupal will start installing default modules and themes.
Drupal Module and Theme Installer
Next, you will be taken to the Drupal configuration page. Fill in the site name, email, username, password, and regional settings. Click the Save and continue button once you are finished.
Drupal Site Configuration
Finally, you will be taken to the Drupal dashboard. You can start using Drupal for making your website.
Drupal Dashboard
Step 8 — Configure Drupal
This step is optional but helps in improving the performance of Drupal. The first step is to set the MySQL transaction isolation level. The default transaction isolation level for MySQL, MariaDB, and equivalent databases is “REPEATABLE READ”. This setting with Drupal can result in deadlocks on tables, which will result in the site becoming very slow or not responding at all. The recommended transaction isolation level for Drupal sites is ‘READ COMMITTED’.
Log in to the MySQL container SSH shell.
$ docker exec -it mysql bash
Open the MySQL shell using the root user.
bash-4.4# mysql -u root -p
Enter password:
Run the following command to change the transaction level globally.
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Exit the MySQL shell and the container by typing exit twice.
The next step is to enter your domain as the trusted host to protect against HTTP HOST Header attacks. For this, we need to edit the /var/www/html/sites/default/settings.php file inside the Drupal container. Since we are using a named volume for Drupal files, the recommended way to make any changes is to copy the file from the container to the host, make the edit, and copy it back into the container. You can do this with any file you need to change inside the Drupal installation.
Copy the settings file from the container to the host.
$ docker cp drupal:/var/www/html/sites/default/settings.php settings.php
The file is in read-only mode. Give it writing permissions.
$ chmod+w settings.php
Open the file for editing.
$ nano settings.php
Find the following section in the file.
#$settings[‘trusted_host_patterns’] = [
# ‘^www\.example\.com$’,
#];
Uncomment it by removing the hash sign and adding your Drupal domain as shown below.
$settings[‘trusted_host_patterns’] = [
‘^drupal\.example\.com$’,
];
Save the file by pressing Ctrl + X and entering Y when prompted.
Remove the writing permissions again.
$ chmod -w settings.php
Copy the file back inside the container.
$ docker cp settings.php drupal:/var/www/html/sites/default
Step 9 — Backup Drupal
We will use the command line to back up the Drupal database. Switch to the Drupal directory.
$ cd ~/drupal
Create the directory for Backups.
$ mkdir backup-data
Use the following command to back up the Drupal database. You will be asked for your MySQL root password.
$ docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Enter password: root_password
The above command will create the SQL backup in the ~/drupal/backup-data directory.
Check the directory contents.
$ ls -al backup-data
total 6716
drwxrwxr-x 2 navjot navjot 4096 Jan 19 13:59 .
drwxrwxr-x 4 navjot navjot 4096 Jan 19 13:35 ..
-rw-rw-r — 1 navjot navjot 6868325 Jan 19 13:37 data_19–01–2023_13_36_58.sql
You can see the database backed up in the directory. You can restore this database using the phpMyAdmin tool or using the following command.
$ docker compose exec mysql sh -c “exec mysql -uroot -p” < backup-data/data_19–01–2023_13_36_58.sql
You can create a cron job to back up the database regularly.
Create the backup script in the /etc/cron.daily directory and open it for editing.
$ sudo nano /etc/cron.daily/drupalbackup.sh
Paste the following code in it.
#!/bin/bash
cd /home/navjot/drupal/
/usr/bin/docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ sudo chmod +x /etc/cron.daily/drupalbackup.sh
Now, your database will be backed up daily.
Step 10 — Upgrade Drupal
The first step in upgrading Drupal is to back up the Drupal database using the command from step 9.
Then, switch to the directory.
$ cd ~/drupal
Stop the Containers.
$ docker compose down
Pull the latest container images.
$ docker compose pull drupal:10-fpm-alpine
If you want to upgrade to the next major version, you will need to adjust the image name accordingly and go through Drupal’s release notes to check for any issues.
Make any changes you need in the docker-compose.yml if you want. The remaining images you can update by changing their definition in the Docker compose file.
Restart the Drupal containers. This will also pull the latest images for minor versions of other packages.
$ docker compose up -d
Conclusion
This concludes our tutorial on installing Drupal using Docker on a Ubuntu 22.04 server. If you have any questions, post them in the comments below.” width=”750" height=”413" />
We will stick to the Standard profile. Click the Save and continue button to proceed to the database configuration page.
$(lsb_release -cs) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install the latest version of Docker.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify that it is running.
$ sudo systemctl status docker
? docker.service — Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023–01–14 10:41:35 UTC; 2min 1s ago
TriggeredBy: ? docker.socket
Docs: https://docs.docker.com
Main PID: 2054 (dockerd)
Tasks: 52
Memory: 22.5M
CPU: 248ms
CGroup: /system.slice/docker.service
?? 2054 /usr/bin/dockerd -H fd:// — containerd=/run/containerd/containerd.sock
By default, Docker requires root privileges. If you want to avoid using sudo every time you run the docker command, add your username to the docker group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su — $USER
Confirm that your user is added to the Docker group.
$ groups
navjot wheel docker
Step 3 — Create Docker Compose File for Drupal
Create the directory for Drupal.
$ mkdir ~/drupal
Switch to the directory.
$ cd ~/drupal
Create and open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
env_file: .env
volumes:
— db-data:/var/lib/mysql
networks:
— internal
drupal:
image: drupal:10-fpm-alpine
container_name: drupal
depends_on:
— mysql
restart: unless-stopped
networks:
— internal
— external
volumes:
— drupal-data:/var/www/html
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
networks:
— external
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email sammy@your_domain — agree-tos — no-eff-email — staging -d example.com -d www.example.com
networks:
external:
driver: bridge
internal:
driver: bridge
volumes:
drupal-data:
db-data:
certbot-etc:
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through each service defined in the above file.
MySQL Docker Service
Here we are pulling the latest mysql:8.0 image from the Docker hub. We are using the 8.x version instead of using the latest tag. This way we can stick to the stable and tested version of MySQL which works with Drupal. We have set a name for the container, which can be used with Docker commands to stop, start and view logs. The container will continue running unless stopped manually. We have defined a .env file that we will populate with MySQL credentials. We have also mounted a named volume db-data to the /var/lib/mysql directory on the container. The MySQL service will use an internal network to connect with drupal.
Drupal Service
We are using the Drupal 10 Alpine image. Alpine docker images are smaller in size. This image also contains PHP-FPM to handle PHP processing. This will work alongside Nginx to serve the site. The depends_on option tells Drupal to connect with the MySQL service. It also ensures that the Drupal container will always start after the MySQL container. Drupal uses the internal network to connect with MySQL and the external network to expose itself to other containers. We have also created a named volume for Drupal to point to /var/www/html directory in the container.
Nginx service
We are using an Alpine image for Nginx. It exposes port 80 to the host. We use two named volumes, one for Drupal’s public directory and the other for storing Let’s Encrypt SSL certificates. The third volume is bind mount to the Nginx configuration directory on the host which we will define later. Nginx also connects to an external Docker network for the Drupal site to work.
Certbot service
And at last, we pull the Certbot image to install SSL certificates. It shares its volumes with the Nginx service for the certificates and webroot definition. We have also included a command that will run when the container is created. Here the command uses the — staging flag to get a test server for the first time. We need nginx to validate the certificates but Nginx won’t start if the certificates are missing. This is why we will create a staging certificate, use that to start Nginx, and then create the real certificates.
Step 4 — Create Nginx Configuration
Create the directory for Nginx configuration.
$ mkdir nginx-conf
Create and open the file for Nginx.
$ nano nginx-conf/drupal.conf
Paste the following code into it.
server jpeg
Save the file by pressing Ctrl + X and entering Y when prompted.
In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client’s request for certificates, PHP processing, and static asset requests. For now, the Nginx will listen only on port 80 to allow Certbot to request for the staging certificate by placing a temporary file in the /var/www/html/.well-known/acme-challenge directory to validate the DNS. This allows us to use Certbot with the webroot plugin.
Step 5 — Generate SSL certificates
To generate SSL certificates, we will start our containers. The correct staging certificates will be available at the /etc/letsencrypt/live folder in the Nginx container.
$ docker compose up -d
Check the status of the services.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 6 minutes ago Exited (1) 5 minutes ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 6 minutes ago Up 6 minutes 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 6 minutes ago Up 6 minutes 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
The Certbot container exits successfully after generating the certificate. Check the location of the certificates in the Nginx container.
$ docker compose exec webserver ls -la /etc/letsencrypt/live
You will get the following output.
total 16
drwx — — — 3 root root 4096 Jan 17 09:15 .
drwxr-xr-x 9 root root 4096 Jan 17 09:15 ..
-rw-r — r — 1 root root 740 Jan 17 09:15 README
drwxr-xr-x 2 root root 4096 Jan 17 09:15 drupal.example.com
This confirms that everything is successful. The next step is to generate the actual certificates.
Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Replace the — staging flag in the Certbot service section and replace it with the — force-renewal flag. This tells Certbot to request new certificates for your domain. The renewal flag is used because that will be used to renew the certificates from here on.
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email name@example.com — agree-tos — no-eff-email — staple-ocsp — force-renewal -d drupal.example.com
Save the file by pressing Ctrl + X and entering Y when prompted.
Run the docker compose up command again to recreate the Certbot container. The — no-deps flag tells Certbot to skip starting the webserver container since it is already running.
$ docker compose up — force-recreate — no-deps certbot
You will get the following output.
[+] Running 1/0
? Container certbot Recreated 0.1s
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for drupal.example.com
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/drupal.example.com/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/drupal.example.com/privkey.pem
certbot | This certificate expires on 2023–04–17.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | — The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot exited with code 0
Step 6 — Configure Nginx for SSL
Now that our certificates are live, we need to configure Nginx to serve them and redirect HTTP requests to HTTPS.
Stop the Nginx server.
$ docker stop webserver
Create a new Nginx file for SSL configuration and open it for editing.
$ nano nginx-conf/drupal-ssl.conf
Paste the following code in it.
server
listen 80;
listen [::]:80;
server_name drupal.example.com;
location ~ /.well-known/acme-challenge
allow all;
root /var/www/html;
location /
rewrite ^ https://$host$request_uri? permanent;
server js
Save the file by pressing Ctrl + X and entering Y when prompted.
The HTTP block specifies the location for Certbot’s webroot plugin and redirects any HTTP request to HTTPS.
The next step is to make sure the Nginx container listens to port 443. Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
In the Nginx section of the file, make changes to expose 443 and enable SSL as shown below.
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
— 443:443
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
— /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem
networks:
— external
Save the file by pressing Ctrl + X and entering Y when prompted.
Now that we have enabled and added SSL configuration for Nginx, you can delete the older HTTP configuration file.
$ rm nginx-conf/drupal.conf
Before restarting Nginx, we need to generate a Diffie-Hellman group certificate which we have already configured above.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Recreate the Nginx container.
$ docker compose up -d — force-recreate — no-deps webserver
Check the status of the containers.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 3 hours ago Exited (0) 3 hours ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 3 hours ago Up 3 hours 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 3 hours ago Up 3 hours 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 15 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
Step 7 — Start Drupal Web Installer
It is time to start the Drupal web installer. Open the URL https://drupal.example.com in your browser and you will get the following screen.
Drupal Installer Home
Click the Save and continue button to proceed to the Installation profile page.
Drupal Installation Profile
We will stick to the Standard profile. Click the Save and continue button to proceed to the database configuration page.
Drupal Database Configuration
Fill in the database credentials we used in the environment file, expand the Advanced options section, and enter mysql as the database host. This matches the name of the MySQL service in our Docker compose file with which Drupal will need to connect.
Click the Save and continue button to continue. Drupal will start installing default modules and themes.
Drupal Module and Theme Installer
Next, you will be taken to the Drupal configuration page. Fill in the site name, email, username, password, and regional settings. Click the Save and continue button once you are finished.
Drupal Site Configuration
Finally, you will be taken to the Drupal dashboard. You can start using Drupal for making your website.
Drupal Dashboard
Step 8 — Configure Drupal
This step is optional but helps in improving the performance of Drupal. The first step is to set the MySQL transaction isolation level. The default transaction isolation level for MySQL, MariaDB, and equivalent databases is “REPEATABLE READ”. This setting with Drupal can result in deadlocks on tables, which will result in the site becoming very slow or not responding at all. The recommended transaction isolation level for Drupal sites is ‘READ COMMITTED’.
Log in to the MySQL container SSH shell.
$ docker exec -it mysql bash
Open the MySQL shell using the root user.
bash-4.4# mysql -u root -p
Enter password:
Run the following command to change the transaction level globally.
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Exit the MySQL shell and the container by typing exit twice.
The next step is to enter your domain as the trusted host to protect against HTTP HOST Header attacks. For this, we need to edit the /var/www/html/sites/default/settings.php file inside the Drupal container. Since we are using a named volume for Drupal files, the recommended way to make any changes is to copy the file from the container to the host, make the edit, and copy it back into the container. You can do this with any file you need to change inside the Drupal installation.
Copy the settings file from the container to the host.
$ docker cp drupal:/var/www/html/sites/default/settings.php settings.php
The file is in read-only mode. Give it writing permissions.
$ chmod+w settings.php
Open the file for editing.
$ nano settings.php
Find the following section in the file.
#$settings[‘trusted_host_patterns’] = [
# ‘^www\.example\.com$’,
#];
Uncomment it by removing the hash sign and adding your Drupal domain as shown below.
$settings[‘trusted_host_patterns’] = [
‘^drupal\.example\.com$’,
];
Save the file by pressing Ctrl + X and entering Y when prompted.
Remove the writing permissions again.
$ chmod -w settings.php
Copy the file back inside the container.
$ docker cp settings.php drupal:/var/www/html/sites/default
Step 9 — Backup Drupal
We will use the command line to back up the Drupal database. Switch to the Drupal directory.
$ cd ~/drupal
Create the directory for Backups.
$ mkdir backup-data
Use the following command to back up the Drupal database. You will be asked for your MySQL root password.
$ docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Enter password: root_password
The above command will create the SQL backup in the ~/drupal/backup-data directory.
Check the directory contents.
$ ls -al backup-data
total 6716
drwxrwxr-x 2 navjot navjot 4096 Jan 19 13:59 .
drwxrwxr-x 4 navjot navjot 4096 Jan 19 13:35 ..
-rw-rw-r — 1 navjot navjot 6868325 Jan 19 13:37 data_19–01–2023_13_36_58.sql
You can see the database backed up in the directory. You can restore this database using the phpMyAdmin tool or using the following command.
$ docker compose exec mysql sh -c “exec mysql -uroot -p” < backup-data/data_19–01–2023_13_36_58.sql
You can create a cron job to back up the database regularly.
Create the backup script in the /etc/cron.daily directory and open it for editing.
$ sudo nano /etc/cron.daily/drupalbackup.sh
Paste the following code in it.
#!/bin/bash
cd /home/navjot/drupal/
/usr/bin/docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ sudo chmod +x /etc/cron.daily/drupalbackup.sh
Now, your database will be backed up daily.
Step 10 — Upgrade Drupal
The first step in upgrading Drupal is to back up the Drupal database using the command from step 9.
Then, switch to the directory.
$ cd ~/drupal
Stop the Containers.
$ docker compose down
Pull the latest container images.
$ docker compose pull drupal:10-fpm-alpine
If you want to upgrade to the next major version, you will need to adjust the image name accordingly and go through Drupal’s release notes to check for any issues.
Make any changes you need in the docker-compose.yml if you want. The remaining images you can update by changing their definition in the Docker compose file.
Restart the Drupal containers. This will also pull the latest images for minor versions of other packages.
$ docker compose up -d
Conclusion
This concludes our tutorial on installing Drupal using Docker on a Ubuntu 22.04 server. If you have any questions, post them in the comments below.” width=”682" height=”750" />
Fill in the database credentials we used in the environment file, expand the Advanced options section, and enter mysql
as the database host. This matches the name of the MySQL service in our Docker compose file with which Drupal will need to connect.
Click the Save and continue button to continue. Drupal will start installing default modules and themes.
$(lsb_release -cs) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repository list.
$ sudo apt update
Install the latest version of Docker.
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify that it is running.
$ sudo systemctl status docker
? docker.service — Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023–01–14 10:41:35 UTC; 2min 1s ago
TriggeredBy: ? docker.socket
Docs: https://docs.docker.com
Main PID: 2054 (dockerd)
Tasks: 52
Memory: 22.5M
CPU: 248ms
CGroup: /system.slice/docker.service
?? 2054 /usr/bin/dockerd -H fd:// — containerd=/run/containerd/containerd.sock
By default, Docker requires root privileges. If you want to avoid using sudo every time you run the docker command, add your username to the docker group.
$ sudo usermod -aG docker $(whoami)
You will need to log out of the server and back in as the same user to enable this change or use the following command.
$ su — $USER
Confirm that your user is added to the Docker group.
$ groups
navjot wheel docker
Step 3 — Create Docker Compose File for Drupal
Create the directory for Drupal.
$ mkdir ~/drupal
Switch to the directory.
$ cd ~/drupal
Create and open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Paste the following code in it.
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
env_file: .env
volumes:
— db-data:/var/lib/mysql
networks:
— internal
drupal:
image: drupal:10-fpm-alpine
container_name: drupal
depends_on:
— mysql
restart: unless-stopped
networks:
— internal
— external
volumes:
— drupal-data:/var/www/html
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
networks:
— external
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email sammy@your_domain — agree-tos — no-eff-email — staging -d example.com -d www.example.com
networks:
external:
driver: bridge
internal:
driver: bridge
volumes:
drupal-data:
db-data:
certbot-etc:
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through each service defined in the above file.
MySQL Docker Service
Here we are pulling the latest mysql:8.0 image from the Docker hub. We are using the 8.x version instead of using the latest tag. This way we can stick to the stable and tested version of MySQL which works with Drupal. We have set a name for the container, which can be used with Docker commands to stop, start and view logs. The container will continue running unless stopped manually. We have defined a .env file that we will populate with MySQL credentials. We have also mounted a named volume db-data to the /var/lib/mysql directory on the container. The MySQL service will use an internal network to connect with drupal.
Drupal Service
We are using the Drupal 10 Alpine image. Alpine docker images are smaller in size. This image also contains PHP-FPM to handle PHP processing. This will work alongside Nginx to serve the site. The depends_on option tells Drupal to connect with the MySQL service. It also ensures that the Drupal container will always start after the MySQL container. Drupal uses the internal network to connect with MySQL and the external network to expose itself to other containers. We have also created a named volume for Drupal to point to /var/www/html directory in the container.
Nginx service
We are using an Alpine image for Nginx. It exposes port 80 to the host. We use two named volumes, one for Drupal’s public directory and the other for storing Let’s Encrypt SSL certificates. The third volume is bind mount to the Nginx configuration directory on the host which we will define later. Nginx also connects to an external Docker network for the Drupal site to work.
Certbot service
And at last, we pull the Certbot image to install SSL certificates. It shares its volumes with the Nginx service for the certificates and webroot definition. We have also included a command that will run when the container is created. Here the command uses the — staging flag to get a test server for the first time. We need nginx to validate the certificates but Nginx won’t start if the certificates are missing. This is why we will create a staging certificate, use that to start Nginx, and then create the real certificates.
Step 4 — Create Nginx Configuration
Create the directory for Nginx configuration.
$ mkdir nginx-conf
Create and open the file for Nginx.
$ nano nginx-conf/drupal.conf
Paste the following code into it.
server ico
Save the file by pressing Ctrl + X and entering Y when prompted.
In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client’s request for certificates, PHP processing, and static asset requests. For now, the Nginx will listen only on port 80 to allow Certbot to request for the staging certificate by placing a temporary file in the /var/www/html/.well-known/acme-challenge directory to validate the DNS. This allows us to use Certbot with the webroot plugin.
Step 5 — Generate SSL certificates
To generate SSL certificates, we will start our containers. The correct staging certificates will be available at the /etc/letsencrypt/live folder in the Nginx container.
$ docker compose up -d
Check the status of the services.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 6 minutes ago Exited (1) 5 minutes ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 6 minutes ago Up 6 minutes 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 6 minutes ago Up 6 minutes 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
The Certbot container exits successfully after generating the certificate. Check the location of the certificates in the Nginx container.
$ docker compose exec webserver ls -la /etc/letsencrypt/live
You will get the following output.
total 16
drwx — — — 3 root root 4096 Jan 17 09:15 .
drwxr-xr-x 9 root root 4096 Jan 17 09:15 ..
-rw-r — r — 1 root root 740 Jan 17 09:15 README
drwxr-xr-x 2 root root 4096 Jan 17 09:15 drupal.example.com
This confirms that everything is successful. The next step is to generate the actual certificates.
Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
Replace the — staging flag in the Certbot service section and replace it with the — force-renewal flag. This tells Certbot to request new certificates for your domain. The renewal flag is used because that will be used to renew the certificates from here on.
certbot:
depends_on:
— webserver
image: certbot/certbot
container_name: certbot
volumes:
— certbot-etc:/etc/letsencrypt
— drupal-data:/var/www/html
command: certonly — webroot — webroot-path=/var/www/html — email name@example.com — agree-tos — no-eff-email — staple-ocsp — force-renewal -d drupal.example.com
Save the file by pressing Ctrl + X and entering Y when prompted.
Run the docker compose up command again to recreate the Certbot container. The — no-deps flag tells Certbot to skip starting the webserver container since it is already running.
$ docker compose up — force-recreate — no-deps certbot
You will get the following output.
[+] Running 1/0
? Container certbot Recreated 0.1s
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for drupal.example.com
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/drupal.example.com/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/drupal.example.com/privkey.pem
certbot | This certificate expires on 2023–04–17.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | — The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
certbot exited with code 0
Step 6 — Configure Nginx for SSL
Now that our certificates are live, we need to configure Nginx to serve them and redirect HTTP requests to HTTPS.
Stop the Nginx server.
$ docker stop webserver
Create a new Nginx file for SSL configuration and open it for editing.
$ nano nginx-conf/drupal-ssl.conf
Paste the following code in it.
server
listen 80;
listen [::]:80;
server_name drupal.example.com;
location ~ /.well-known/acme-challenge
allow all;
root /var/www/html;
location /
rewrite ^ https://$host$request_uri? permanent;
server
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name drupal.example.com;
index index.php index.html index.htm;
root /var/www/html;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/drupal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/drupal.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/drupal.example.com/chain.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_ecdh_curve secp384r1;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header X-Frame-Options “SAMEORIGIN” always;
add_header X-XSS-Protection “1; mode=block” always;
add_header X-Content-Type-Options “nosniff” always;
add_header Referrer-Policy “no-referrer-when-downgrade” always;
add_header Content-Security-Policy “default-src * data: ‘unsafe-eval’ ‘unsafe-inline’” always;
location /
try_files $uri $uri/ /index.php$is_args$args;
rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;
location ~ \.php$
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass drupal:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
location ~ /\.ht
deny all;
location = /favicon.ico
log_not_found off; access_log off;
location = /robots.txt
log_not_found off; access_log off; allow all;
location ~* \.(css
Save the file by pressing Ctrl + X and entering Y when prompted.
The HTTP block specifies the location for Certbot’s webroot plugin and redirects any HTTP request to HTTPS.
The next step is to make sure the Nginx container listens to port 443. Open the docker-compose.yml file for editing.
$ nano docker-compose.yml
In the Nginx section of the file, make changes to expose 443 and enable SSL as shown below.
webserver:
image: nginx:1.22.1-alpine
container_name: webserver
depends_on:
— drupal
restart: unless-stopped
ports:
— 80:80
— 443:443
volumes:
— drupal-data:/var/www/html
— ./nginx-conf:/etc/nginx/conf.d
— certbot-etc:/etc/letsencrypt
— /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem
networks:
— external
Save the file by pressing Ctrl + X and entering Y when prompted.
Now that we have enabled and added SSL configuration for Nginx, you can delete the older HTTP configuration file.
$ rm nginx-conf/drupal.conf
Before restarting Nginx, we need to generate a Diffie-Hellman group certificate which we have already configured above.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Recreate the Nginx container.
$ docker compose up -d — force-recreate — no-deps webserver
Check the status of the containers.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
certbot certbot/certbot “certbot certonly — …” certbot 3 hours ago Exited (0) 3 hours ago
drupal drupal:10-fpm-alpine “docker-php-entrypoi…” drupal 3 hours ago Up 3 hours 9000/tcp
mysql mysql:8.0 “docker-entrypoint.s…” mysql 3 hours ago Up 3 hours 3306/tcp, 33060/tcp
webserver nginx:1.22.1-alpine “/docker-entrypoint.…” webserver 15 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
Step 7 — Start Drupal Web Installer
It is time to start the Drupal web installer. Open the URL https://drupal.example.com in your browser and you will get the following screen.
Drupal Installer Home
Click the Save and continue button to proceed to the Installation profile page.
Drupal Installation Profile
We will stick to the Standard profile. Click the Save and continue button to proceed to the database configuration page.
Drupal Database Configuration
Fill in the database credentials we used in the environment file, expand the Advanced options section, and enter mysql as the database host. This matches the name of the MySQL service in our Docker compose file with which Drupal will need to connect.
Click the Save and continue button to continue. Drupal will start installing default modules and themes.
Drupal Module and Theme Installer
Next, you will be taken to the Drupal configuration page. Fill in the site name, email, username, password, and regional settings. Click the Save and continue button once you are finished.
Drupal Site Configuration
Finally, you will be taken to the Drupal dashboard. You can start using Drupal for making your website.
Drupal Dashboard
Step 8 — Configure Drupal
This step is optional but helps in improving the performance of Drupal. The first step is to set the MySQL transaction isolation level. The default transaction isolation level for MySQL, MariaDB, and equivalent databases is “REPEATABLE READ”. This setting with Drupal can result in deadlocks on tables, which will result in the site becoming very slow or not responding at all. The recommended transaction isolation level for Drupal sites is ‘READ COMMITTED’.
Log in to the MySQL container SSH shell.
$ docker exec -it mysql bash
Open the MySQL shell using the root user.
bash-4.4# mysql -u root -p
Enter password:
Run the following command to change the transaction level globally.
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Exit the MySQL shell and the container by typing exit twice.
The next step is to enter your domain as the trusted host to protect against HTTP HOST Header attacks. For this, we need to edit the /var/www/html/sites/default/settings.php file inside the Drupal container. Since we are using a named volume for Drupal files, the recommended way to make any changes is to copy the file from the container to the host, make the edit, and copy it back into the container. You can do this with any file you need to change inside the Drupal installation.
Copy the settings file from the container to the host.
$ docker cp drupal:/var/www/html/sites/default/settings.php settings.php
The file is in read-only mode. Give it writing permissions.
$ chmod+w settings.php
Open the file for editing.
$ nano settings.php
Find the following section in the file.
#$settings[‘trusted_host_patterns’] = [
# ‘^www\.example\.com$’,
#];
Uncomment it by removing the hash sign and adding your Drupal domain as shown below.
$settings[‘trusted_host_patterns’] = [
‘^drupal\.example\.com$’,
];
Save the file by pressing Ctrl + X and entering Y when prompted.
Remove the writing permissions again.
$ chmod -w settings.php
Copy the file back inside the container.
$ docker cp settings.php drupal:/var/www/html/sites/default
Step 9 — Backup Drupal
We will use the command line to back up the Drupal database. Switch to the Drupal directory.
$ cd ~/drupal
Create the directory for Backups.
$ mkdir backup-data
Use the following command to back up the Drupal database. You will be asked for your MySQL root password.
$ docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Enter password: root_password
The above command will create the SQL backup in the ~/drupal/backup-data directory.
Check the directory contents.
$ ls -al backup-data
total 6716
drwxrwxr-x 2 navjot navjot 4096 Jan 19 13:59 .
drwxrwxr-x 4 navjot navjot 4096 Jan 19 13:35 ..
-rw-rw-r — 1 navjot navjot 6868325 Jan 19 13:37 data_19–01–2023_13_36_58.sql
You can see the database backed up in the directory. You can restore this database using the phpMyAdmin tool or using the following command.
$ docker compose exec mysql sh -c “exec mysql -uroot -p” < backup-data/data_19–01–2023_13_36_58.sql
You can create a cron job to back up the database regularly.
Create the backup script in the /etc/cron.daily directory and open it for editing.
$ sudo nano /etc/cron.daily/drupalbackup.sh
Paste the following code in it.
#!/bin/bash
cd /home/navjot/drupal/
/usr/bin/docker compose exec mysql sh -c “exec mysqldump drupal -uroot -p” | tee backup-data/data_`date +%d-%m-%Y”_”%H_%M_%S`.sql >/dev/null
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ sudo chmod +x /etc/cron.daily/drupalbackup.sh
Now, your database will be backed up daily.
Step 10 — Upgrade Drupal
The first step in upgrading Drupal is to back up the Drupal database using the command from step 9.
Then, switch to the directory.
$ cd ~/drupal
Stop the Containers.
$ docker compose down
Pull the latest container images.
$ docker compose pull drupal:10-fpm-alpine
If you want to upgrade to the next major version, you will need to adjust the image name accordingly and go through Drupal’s release notes to check for any issues.
Make any changes you need in the docker-compose.yml if you want. The remaining images you can update by changing their definition in the Docker compose file.
Restart the Drupal containers. This will also pull the latest images for minor versions of other packages.
$ docker compose up -d
Conclusion
This concludes our tutorial on installing Drupal using Docker on a Ubuntu 22.04 server. If you have any questions, post them in the comments below.” width=”750" height=”398" />
Next, you will be taken to the Drupal configuration page. Fill in the site name, email, username, password, and regional settings. Click the Save and continue button once you are finished.
Finally, you will be taken to the Drupal dashboard. You can start using Drupal for making your website.
Step 8 — Configure Drupal
This step is optional but helps in improving the performance of Drupal. The first step is to set the MySQL transaction isolation level. The default transaction isolation level for MySQL, MariaDB, and equivalent databases is “REPEATABLE READ”. This setting with Drupal can result in deadlocks on tables, which will result in the site becoming very slow or not responding at all. The recommended transaction isolation level for Drupal sites is ‘READ COMMITTED’.
Log in to the MySQL container SSH shell.
$ docker exec -it mysql bash
Open the MySQL shell using the root user.
bash-4.4# mysql -u root -p
Enter password:
Run the following command to change the transaction level globally.
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Exit the MySQL shell and the container by typing exit
twice.
The next step is to enter your domain as the trusted host to protect against HTTP HOST Header attacks. For this, we need to edit the /var/www/html/sites/default/settings.php
file inside the Drupal container. Since we are using a named volume for Drupal files, the recommended way to make any changes is to copy the file from the container to the host, make the edit, and copy it back into the container. You can do this with any file you need to change inside the Drupal installation.
Copy the settings file from the container to the host.
$ docker cp drupal:/var/www/html/sites/default/settings.php settings.php
The file is in read-only mode. Give it writing permissions.
$ chmod+w settings.php
Open the file for editing.
$ nano settings.php
Find the following section in the file.
#$settings['trusted_host_patterns'] = [
# '^www\.example\.com$',
#];
Uncomment it by removing the hash sign and adding your Drupal domain as shown below.
$settings['trusted_host_patterns'] = [
'^drupal\.example\.com$',
];
Save the file by pressing Ctrl + X and entering Y when prompted.
Remove the writing permissions again.
$ chmod -w settings.php
Copy the file back inside the container.
$ docker cp settings.php drupal:/var/www/html/sites/default
Step 9 — Backup Drupal
We will use the command line to back up the Drupal database. Switch to the Drupal directory.
$ cd ~/drupal
Create the directory for Backups.
$ mkdir backup-data
Use the following command to back up the Drupal database. You will be asked for your MySQL root password.
$ docker compose exec mysql sh -c "exec mysqldump drupal -uroot -p" | tee backup-data/data_`date +%d-%m-%Y"_"%H_%M_%S`.sql >/dev/null
Enter password: root_password
The above command will create the SQL backup in the ~/drupal/backup-data
directory.
Check the directory contents.
$ ls -al backup-data
total 6716
drwxrwxr-x 2 navjot navjot 4096 Jan 19 13:59 .
drwxrwxr-x 4 navjot navjot 4096 Jan 19 13:35 ..
-rw-rw-r-- 1 navjot navjot 6868325 Jan 19 13:37 data_19-01-2023_13_36_58.sql
You can see the database backed up in the directory. You can restore this database using the phpMyAdmin tool or using the following command.
$ docker compose exec mysql sh -c "exec mysql -uroot -p" < backup-data/data_19-01-2023_13_36_58.sql
You can create a cron job to back up the database regularly.
Create the backup script in the /etc/cron.daily
directory and open it for editing.
$ sudo nano /etc/cron.daily/drupalbackup.sh
Paste the following code in it.
#!/bin/bash
cd /home/navjot/drupal/
/usr/bin/docker compose exec mysql sh -c "exec mysqldump drupal -uroot -p" | tee backup-data/data_`date +%d-%m-%Y"_"%H_%M_%S`.sql >/dev/null
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ sudo chmod +x /etc/cron.daily/drupalbackup.sh
Now, your database will be backed up daily.
Step 10 — Upgrade Drupal
The first step in upgrading Drupal is to back up the Drupal database using the command from step 9.
Then, switch to the directory.
$ cd ~/drupal
Stop the Containers.
$ docker compose down
Pull the latest container images.
$ docker compose pull drupal:10-fpm-alpine
If you want to upgrade to the next major version, you will need to adjust the image name accordingly and go through Drupal’s release notes to check for any issues.
Make any changes you need in the docker-compose.yml
if you want. The remaining images you can update by changing their definition in the Docker compose file.
Restart the Drupal containers. This will also pull the latest images for minor versions of other packages.
$ docker compose up -d
Conclusion
This concludes our tutorial on installing Drupal using Docker on a Ubuntu 22.04 server. If you have any questions, post them in the comments below.
https://www.computingpost.com/how-to-install-drupal-with-docker-on-ubuntu-22-04/?feed_id=26175&_unique_id=63ced39069ae1