Drupal is a general used CMS framework all over the world, and Composer is one of the best PHP package manager. It’s very easy to deploy a drupal site with composer.


Table of Contents

  1. Prerequisites
    1. Nginx
    2. PostgreSQL 13
    3. PHP 7.4
    4. Composer
    5. Drupal
    6. Selinux
  2. Deploy Drupal
    1. Nginx Config
    2. PostgreSQL Config
    3. Drupal Config
    4. Drupal Update

Prerequisites

Drupal now (>=9.0) requires:

  • PHP >= 7.2
  • SQL
    • PostgreSQL
    • MySQL
  • Nginx

Nginx

1
2
$ sudo dnf module install -y nginx:1.20
$ sudo systemctl enable --now nginx

PostgreSQL 13

1
$ sudo dnf module install postgresql:13

PHP 7.4

1
2
3
$ sudo dnf module install php:7.4 -y
$ sudo sed -i -e "s|^user = apache|user = nginx|g" -e "s|^group = apache|group = nginx|g" /etc/php-fpm.d/www.conf
$ sudo systemctl enable --now php-fpm.service

Composer

1
2
3
4
5
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php composer-setup.php
$ php -r "unlink('composer-setup.php');"
$ sudo mv composer.phar /usr/bin/composer

Or just:

1
2
$ sudo wget https://getcomposer.org/download/latest-stable/composer.phar -O /usr/bin/composer
$ sudo chmod +x /usr/bin/composer

Drupal

1
2
3
4
5
6
#### Install drupal
$ cd /path/to/web/
$ composer create-project drupal/recommended-project my-project
$ cd my-project
#### install drush
$ composer require drush/drush

Selinux

Disable Selinux for convenience.

1
2
$ sudo setenforce 0
$ sudo sed -i "s|SELINUX=enforcing|SELINUX=disabled|g" /etc/selinux/config

Or

1
2
3
4
5
6
7
#### For nginx
$ sudo semanage permissive -a httpd_t
# or
$ sudo setsebool -P httpd_can_network_connect 1
#
$ chcon -R -t httpd_sys_content_t /path/to/drupal/site
#### For php-fpm

Deploy Drupal

Nginx Config

A typical nginx config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#### ssl
server_tokens off;
#
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1;mode=block";
add_header Content-Security-Policy "default-src *; style-src static.addtoany.com self 'unsafe-inline' 'unsafe-eval'; script-src static.addtoany.com self 'unsafe-inline' 'unsafe-eval'; img-src 'self' 'unsafe-inline'; script-src-elem 'static.addtoany.com' 'self' 'unsafe-inline' 'unsafe-eval';";
#add_header Content-Security-Policy "default-src *; style-src static.addtoany.com self unsafe-inline unsafe-eval; script-src static.addtoany.com self unsafe-inline unsafe-eval; img-src self unsafe-inline; script-src-elem static.addtoany.com self unsafe-inline unsafe-eval;";

# ssl config

ssl_certificate /etc/nginx/ssl/ssl.crt;
ssl_certificate_key /etc/nginx/ssl/ssl.key;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;

#perfect Forward Security
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security max-age=2592000;

ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;

fastcgi_max_temp_file_size 0;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;

fastcgi_read_timeout 600;
fastcgi_send_timeout 600;
map_hash_max_size 64;
map_hash_bucket_size 64;

server {
listen 80;
listen [::]:80;
listen 443 default_server ssl;
listen [::]:443 default_server ssl;
if ($scheme = http){
return 301 https://$server_name$request_uri;
}

#server_name my-site-ltd;
root /path/to/drupal/site;

location = /favicon.ico {
log_not_found off;
access_log off;
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

# Very rarely should these ever be accessed outside of your lan
location ~* \.(txt|log)$ {
allow 192.168.0.0/16;
deny all;
}

location ~ \..*/.*\.php$ {
return 403;
}

location ~ ^/sites/.*/private/ {
return 403;
}

# Block access to scripts in site files directory
location ~ ^/sites/[^/]+/files/.*\.php$ {
deny all;
}

# Allow "Well-Known URIs" as per RFC 5785
location ~* ^/.well-known/ {
allow all;
}

# Block access to "hidden" files and directories whose names begin with a
# period. This includes directories used by version control systems such
# as Subversion or Git to store control files.
# location ~ (^|/)\. {
# return 403;
# }

location / {
try_files $uri /index.php?$query_string; # For Drupal >= 7
}
rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;
location @rewrite {
#rewrite ^/(.*)$ /index.php?q=$1; # For Drupal <= 6
rewrite ^ /index.php; # For Drupal >= 7
}

# Don't allow direct access to PHP files in the vendor directory.
location ~ /vendor/.*\.php$ {
deny all;
return 404;
}

# Protect files and directories from prying eyes.
location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
deny all;
return 404;
}

# In Drupal 8, we must also match new paths where the '.php' appears in
# the middle, such as update.php/selection. The rule we use is strict,
# and only allows this pattern with the update.php front controller.
# This allows legacy path aliases in the form of
# blog/index.php/legacy-path to continue to route to Drupal nodes. If
# you do not have any paths like that, then you might prefer to use a
# laxer rule, such as:
# location ~ \.php(/|$) {
# The laxer rule will continue to work if Drupal uses this new URL
# pattern with front controllers other than update.php in a future
# release.
location ~ '\.php$|^/update.php' {
fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
# Ensure the php file exists. Mitigates CVE-2019-11043
try_files $fastcgi_script_name =404;
# Security note: If you're running a version of PHP older than the
# latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
# See http://serverfault.com/q/627903/94922 for details.
include fastcgi_params;
# Block httpoxy attacks. See https://httpoxy.org/.
fastcgi_param HTTP_PROXY "";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;
fastcgi_intercept_errors on;
# PHP 5 socket location.
#fastcgi_pass unix:/var/run/php5-fpm.sock;
# PHP 7 socket location.
fastcgi_pass 127.0.0.1:9000;
# fastcgi_pass unix:/var/run/php-fpm/www.sock;
}

# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
# try_files $uri @rewrite;
# expires max;
# log_not_found off;
# }

# Fighting with Styles? This little gem is amazing.
# location ~ ^/sites/.*/files/imagecache/ { # For Drupal <= 6
# location ~ ^/sites/.*/files/styles/ { # For Drupal >= 7
# try_files $uri @rewrite;
# }

# Handle private files through Drupal. Private file's path can come
# with a language prefix.
location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
try_files $uri /index.php?$query_string;
}
}

PostgreSQL Config

Run via docker

1
$ docker run -dit --name postgres --restart always -e POSTGRES_PASSWORD=mysecretpassword -e POSTGRES_USER=drupal -e POSTGRES_DB=drupal -e PGDATA=/var/lib/postgresql/data/ -e TZ=Asia/Shanghai -v /custom/mount:/var/lib/postgresql/data -p 5432:5432 postgres:13

Drupal Config

Drupal Update

  1. Put drupal into maintainence mode
  2. Update drupal via composer
1
2
3
4
5
6
7
8
9
10
11
$ cd /path/to/drupal/site
#### To check if any update for drupal/core or drupal/core-recommended
$ composer outdated "drupal/*"
#### Update drupal/core-recommended
$ composer show drupal/core-recommended
$ composer update drupal/core "drupal/core-*" --with-all-dependencies
#### Update drupal/core
$ composer update drupal/core --with-dependencies
#### Update database via drush
$ drush updatedb
$ drush cache:rebuild

To be continued…