Overview
Categories:
6 minute read
diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 6313b56c5..000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto eol=lf diff --git a/.github/workflows/event-dispatcher.yml b/.github/workflows/event-dispatcher.yml deleted file mode 100644 index 60409ba5a..000000000 --- a/.github/workflows/event-dispatcher.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: repository dispatch - -on: - repository_dispatch: - types: [wxt-tagged] - -jobs: - deploy: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive # Fetch Hugo themes (true OR recursive) - fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - - - name: Display the payload - run: echo ${{ github.event.client_payload.sha }} - - - name: Setup Hugo - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: '0.114.0' - extended: true - - - name: Setup Node - uses: actions/setup-node@v1 - with: - node-version: '16.x' - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - run: npm install - - - name: Build - run: hugo --environment production --minify - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/main' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 259342309..000000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: github pages - -on: - push: - branches: - - main # Set a branch to deploy - pull_request: - -jobs: - deploy: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - fetch-depth: 0 - - - name: Setup Hugo - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: '0.114.0' - extended: true - - - name: Setup Node - uses: actions/setup-node@v1 - with: - node-version: '16.x' - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - run: npm install - - - name: Build - run: hugo --environment production --minify - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d0c572893..000000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -/public -/resources -.DS_Store -.hugo_build.lock -_vendor diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 9be43191b..000000000 --- a/.markdownlint.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MD001": false, - "MD003": false, - "MD013": false, - "MD024": false, - "MD025": false, - "MD029": false, - "MD033": false, - "MD036": false -} diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index b009dfb9d..000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/* diff --git a/404.html b/404.html new file mode 100644 index 000000000..c7008521c --- /dev/null +++ b/404.html @@ -0,0 +1,6 @@ +
Oops! This page doesn't exist. Try going back to the home page.
6 minute read
3 minute read
2 minute read
less than a minute
5 minute read
less than a minute
2 minute read
less than a minute
2 minute read
3 minute read
2 minute read
3 minute read
less than a minute
less than a minute
7 minute read
less than a minute
2 minute read
2 minute read
3 minute read
2 minute read
4 minute read
less than a minute
2 minute read
The Drupal WxT distribution is - a web content management system which assists in building and - maintaining multilingual web sites that are accessible, usable, and interoperable.
-This distribution complies with the mandatory requirement to implement the Content - and Information Architecture (C&IA) Specification as well as consulting the reference - implementation and - design patterns provided by the Canada.ca design - system.
-This is accomplished through our integration and use of the components provided by the - Web Experience Toolkit which undergoes routine - usability testing as well as provides conformance to the Web Content Accessibility Guideline (WCAG 2.0) and - complies to the standards on Web - Accessibility, - Web Usability, - and Web - Interoperability.
- -Discussion and help from your fellow users.
-{{% /blocks/feature %}} -{{% blocks/feature icon="fab fa-github" title="Contributions wanted" %}} - -Want to collaborate with us? New users are always welcomed!
-{{% /blocks/feature %}} -{{% blocks/feature icon="fab fa-drupal" title="Submit an issue" %}} - -Give and recieve support from the Drupal.org community.
-{{% /blocks/feature %}} - -{{% /blocks/section %}} - -{{% blocks/section color="white" type="row" %}} -Component | -Features | -Machine Name | -Type | -
---|---|---|---|
WxT | -
-
|
- wxt | -Distribution | -
WxT Bootstrap | -
-
|
- wxt_bootstrap | -Standalone | -
WxT Library | -
-
|
- wxt_library | -Standalone | -
WxT Admin | -
-
|
- wxt_admin | -Distribution | -
WxT Core | -
-
|
- wxt_core | -Distribution | -
WxT Extend | -
-
|
- wxt_extend | -Distribution | -
WxT Translation | -
-
|
- wxt_translation | -Distribution | -
This is the multi-page printable view of this section. +Click here to print.
This user guide is for project teams who are using the Drupal WxT distribution.
Userguide for all of the general information related to the maintenance and operation of Drupal WxT.
The Drupal WxT distribution is designed for organizations that must meet accessibility and bilingualism standards. It attempts to integrate with the design patterns found in the WET-BOEW and Canada.ca design system, including the mandatory Content and Information Architecture (C&IA) Specification for the Government of Canada.
To make working with Drupal WxT easier, there are potentially three ways you can approach it.
The Drupal WxT distribution method stands out as a preferred choice for web developers and organizations seeking a robust web development solution.
Unlike a standalone installation, the distribution provides a comprehensive package of features and workflows that have been vetted and tested by the Drupal WxT community based on real world use cases.
This means users can leverage a well-established framework with proven capabilities, saving time and effort in development while ensuring stability and reliability.
By opting for the distribution method, teams gain access to shared resources, ongoing support, and a community-driven ecosystem, hopefully helping to build accessible, and bilingual web experiences with confidence.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT |
| wxt | Distribution |
WxT Bootstrap |
| wxt_bootstrap | Standalone |
WxT Library |
| wxt_library | Standalone |
WxT Admin |
| wxt_admin | Distribution |
WxT Core |
| wxt_core | Distribution |
WxT Extend |
| wxt_extend | Distribution |
WxT Translation |
| wxt_translation | Distribution |
A standalone installation allows you to install and configure the standalone components type discussed in the previous section separately without relying on a pre-packaged distribution (composer project).
A composer project will often include multiple modules whether both custom and contributed along with the various configuration and dependencies they will rely on.
Drupal WxT offers a standalone installation as an alternative for those users who don’t want the full weight of a distribution and prefer more control over their setup while still conforming to the Government of Canada C&IA Specification.
Instead users can opt to create their own distribution (composer project) and install only the specific modules and themes required for their needs.
At a minimum and to comply with the WET-BOEW and Canada.ca design system you only need use 2 components.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT Bootstrap (Theme) |
| wxt_bootstrap | Standalone |
WxT Library (Module) |
| wxt_library | Standalone |
For the WET-BOEW Framework Assets it is mandatory that you follow the expected naming convention and that these files be placed within the /libraries
folder.
For you convenience all of these components are already part of a composer repository that can be added very easily to your new or existing composer project.
{
+ ...
+ "require": {
+ ...
+ "drupal/wxt_bootstrap": "^8.0",
+ "drupal/wxt_library": "^8.0",
+ },
+ ...
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://drupalwxt.github.io/composer-extdeps/"
+ }
+ ],
+ ...
+}
+
Note: It is still recommended to use the distribution method, as the standalone option receives limited support and you will lose out on some of the functionality / plugins that help to more fully integrate with the WET-BOEW and Canada.ca design system.
If you prefer full control over your codebase and want to reduce external dependencies, you can use Drupal WxT as a reference implementation.
This means that, as long as you provide proper attribution, you have the freedom to copy or fork any part of the codebase and incorporate it into your own project.
The main drawback of this approach is that you won’t receive community support and also won’t have the same tight integration of features with the WET-BOEW and Canada.ca design system.
However you can selectively choose exactly what you need for your project, potentially saving some time and reducing additional external dependencies.
Our advice at the end of the day is you must consider what is best for your department or organization in the long term.
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
As Drupal WxT is a Drupal distribution, the official guide for Drupal system requirements will apply.
Run the following commands (choosing your version) and replace site-name with the directory of your choice this is where WxT will be installed.
# Requires PHP 8.2 (Drupal 10 LTS)
+composer self-update
+composer create-project drupalwxt/site-wxt:10.4.x-dev <site-name> --no-interaction
+
+# Requires PHP 8.3 (Drupal 11 - alpha release)
+composer self-update
+composer create-project drupalwxt/site-wxt:11.1.x-dev <site-name> --no-interaction
+
Note: Normally you would pass a stable tag to the above command rather then just pulling from the development branch.
If you don’t want to use Composer, you can install WxT the traditional way by downloading a tarball from WxT’s GitHub releases page.
Note: That the tarball generated by the Drupal.org packager does not include the required Composer dependencies and should not be used without following the specialized instructions.
For the (optional) container based local development workflow please consult our documentation site:
a) The Drupal Root is in <site-name>/html
b) You can install Drupal WxT through the browser as any other drupal installation or use drush site-install
to install the WxT installation profile:
drush si wxt \
+ --sites-subdir=default \
+ --db-url=mysql://root:root@db:3306/wxt \
+ --account-name=admin \
+ --account-pass=Drupal@2024 \
+ --site-mail=admin@example.com \
+ --site-name="Drupal Install Profile (WxT)" \
+ wxt_extension_configure_form.select_all='TRUE' \
+ install_configure_form.update_status_module='array(FALSE,FALSE)' \
+ --yes
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
c) You can download up-to-date translations using:
drush locale-check
+drush locale-update
+
d) If you work for the Government of Canada you will want to enable the canada.ca
theme:
drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
Note: You can navigate to the
admin/config/wxt/wxt_library
settings page.
e) The Drupal WxT site should now be sucessfully installed and you can loging via the /user
page.
Note: Please always go to the
admin/report/status
page and confirm there are no warnings and / or errors.
The standalone install is provided as an additional method for those who do not wish to have the full weight of a distribution and its required dependencies. You will need to add at the minimum the below listed modules and themes (including Bootstrap base theme) as well as the WxT jQuery Framework assets installed into the /libraries
folder with the proper naming scheme.
Note: We highly recommend that you use the distribution method as limited support is provided for the standalone method.
The following is an example of how to use the Migrate API module to import common design patterns for Canada.ca aligning to the C&IA specifications:
# Set the WxT theme to GCWeb
+drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
+# Import design patterns for Canada.ca
+drush migrate:import --group wxt --tag 'Core'
+drush migrate:import --group gcweb --tag 'Core'
+drush migrate:import --group gcweb --tag 'Menu'
+
+drush cr
+
Note: There is a corresponding group
wxt_translation
andgcweb_translation
for importing the corresponding french content.
Drupal WxT relies on Drupal’s configuration system for configuring default features and functionality. A consequence of this is, once you have installed Drupal WxT, that we cannot modify the sites configuration without having an impact on your site. Drupal WxT will, however, offer to make changes to your configuration as part of the update process.
If you’ve installed WxT using our Composer-based project template, all you need to do is following the given steps below.
These are the typical steps you should following when updating Drupal WxT:
a) Read the release notes for the release to which you are updating along with any releases in between.
b) To update your WxT codebase you would replace [VERSION]
with the release version you wish to use.
composer self update
+composer require drupalwxt/wxt:[VERSION]
+composer update
+
Note: We highly recommend that you are using the v2.x.x line of Composer.
c) Run any database updates:
drush cache:rebuild
+drush updatedb
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run database updates.
d) Run any WxT configuration updates:
drush cache:rebuild
+drush update:wxt
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run WxT updates.
If you are using configuration management to move your configuration between development, staging, and production environments, you should follow the standard Drupal process.
a) Export the new configuration:
drush cache:rebuild
+drush config:export
+
b) Commit the code and configuration changes to your source code repository and push them to your environment.
c) Import any configuration changes:
drush cache:rebuild
+drush config:import
+
The following table is a list of all the releases that are housed under the Drupal WxT organization on GitHub:
Release | Created Date | Description |
---|---|---|
5.4.1 | 2025-03-14 |
Upgrade path:
Note(s): N/A |
5.4.0 | 2024-12-20 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.3.0 | 2024-11-04 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.2.3 | 2024-07-02 |
Upgrade path:
Note(s): The Group module has been updated to the 2.2.x branch as an intermediate step required to get to the recommended 3.2.x branch. |
5.2.2 | 2024-04-15 |
Upgrade path:
Note(s): The Group module has been downgraded to the 8.x-1.6 release + alongside the flexible permissions module which has been added. This is needed for an immediate upgrade path for groups and the next release will have groups using again the 2.2.x branch. |
The following table is a list of all the repositories that are housed under the Drupal WxT organization on GitHub:
Name | Website | Description | Size |
---|---|---|---|
composer-extdeps | drupalwxt/composer-extdeps | Composer repository for external dependencies on Drupal WxT | Size: 83 Bytes |
docker-scaffold | drupalwxt/docker-scaffold | Docker Scaffold for Drupal WxT | Size: 219 Bytes |
drupalwxt.github.io | drupalwxt/drupalwxt.github.io | GitHub Pages for Drupal WxT. | Size: 15010 Bytes |
helm-drupal | drupalwxt/helm-drupal | Helm Chart for deploying an enterprise-grade Drupal environment. | Size: 67695 Bytes |
site-wxt | drupalwxt/site-wxt | An example composer project for the Drupal WxT distribution used for integration testing. | Size: 4101 Bytes |
terraform-kubernetes-drupalwxt | drupalwxt/terraform-kubernetes-drupalwxt | Terraform module for Drupal WxT | Size: 35 Bytes |
themes-cdn | drupalwxt/themes-cdn | Content Delivery Network (CDN) files for the theme repositories of the Web Experience Toolkit (WET) | Size: 12445 Bytes |
wxt | drupalwxt/wxt | Drupal variant of the Web Experience Toolkit (WxT). | Size: 3482 Bytes |
wxt-project | drupalwxt/wxt-project | Composer project template for Drupal 9 sites built with the WxT distribution. | Size: 76 Bytes |
wxt_bootstrap | drupalwxt/wxt_bootstrap | Bootstrap derived sub-theme aligned for use with the Web Experience Toolkit jQuery Framework. | Size: 1163 Bytes |
wxt_library | drupalwxt/wxt_library | Web Experience Toolkit Framework integration for Drupal. | Size: 118 Bytes |
The following are links to some useful resources:
The core distribution will always strive to be:
Beyond the above the distribution will provide extensible features that can be opted into through the wxt_ext suite of modules:
modulename.wxt_extension.yml
file so can be enabled as optional extension during profile installationIn addition, Drupal WxT will offer out of tree (external) modules that implement specific features:
Note: The governance around the core distribution will always be much stricter then the governance around adding a
wxt_ext
or an out of tree module.
This section provides information for developers who wish to help collaborate and improve Drupal WxT.
The goal of Drupal WxT since the 4.1.x line is to make the installation profile very minimal by default but providing additional extensions that can be enabled as desired.
What WxT offers is some light enhancements to Drupal Core, mainly around security and performance, and integration with the Web Experience Toolkit. By default, the distribution offers minimal functionality to allow full customizations by users. However a great deal of optional extensions are available that can provide additional functionality generally beneficial to Government departments.
Note: In the future we are looking into providing a list of community modules that are build to work with the distribution but are “out of tree”.
All of the optional modules are located in the wxt_ext
folder named after WxT Extend and can be enabled during the initial site installation by passing the following flag via the drush cli:
wxt_extension_configure_form.select_all='TRUE'
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
In order to provide a list of the optional enabled extensions during the installation that can be checked, all that any module has to do is provide a modulename.wxt_extension.yml
file in their root and they will be picked as installable during the profile install and also respond to the additional drush flag discussed above.
For more information on some of the history leading to this design:
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
The following command is all you need to get started:
composer create-project drupalwxt/wxt-project:5.1.1 <site-name>
+
Note: For development you may also specify a branch using
drupalwxt/wxt-project:5.1.x-dev
.
You can see a working example of a fully generated Composer Project Template over at:
Where the following is the command that was used for the initial generation:
composer create-project drupalwxt/wxt-project:5.1.1 site-wxt
+
Note: Remember to keep the
composer.json
andcomposer.lock
files that exist abovedocroot
in source control as they are controlling your dependencies.
List of common commands are as follows:
Task | Composer |
---|---|
Installing a contrib project (latest version) | composer require drupal/PROJECT |
Installing a contrib project (specific version) | composer require drupal/PROJECT:1.0.0-beta5 |
Updating all projects including Drupal Core | composer update |
Updating a single contrib project | composer update drupal/PROJECT_NAME |
Updating Drupal Core | composer update drupal/core |
Note: Composer is a dependency manager and helps us keep track of what code and at what version our application relies on so that it always get installed the right way on every copy of that application.
A specific version can be specified from the cli:
composer require drupal/<modulename>:<version>
+
However please note if you specify a branch, such as 1.x you must add -dev
to the end of the version:
composer require drupal/token:1.x-dev
+
Taking a look at the .gitignore
file, you will discover that certain directories, including all those directories containing contributed projects, are excluded from source control which is by design.
Note: Unlike Drush in a Composer derived project you should never commit your install dependencies to source control.
Composer will create composer.lock
file, which is a list of dependencies that were installed, and in which versions.
Note: In general you should always commit your
composer.lock
file to source control so that others via a quickcomposer install
can have everything installed along with the correct versions specified in thecomposer.lock
file.
Please don’t add drupal/core
to your project’s composer.json since WxT manages Drupal Core for you along with the series of patches on top of it.
For example:
drupalwxt/wxt:~5.2.0
will require Drupal Core 10.2.xdrupalwxt/wxt:~5.1.0
will require Drupal Core 10.1.xWhen you need to update Drupal Core as an example from 10.1.x to 10.2.x, all you would do is change your requirement for drupalwxt/wxt
in your composer.json
file:
composer require --no-update drupalwxt/wxt:~5.2.0
+composer update
+
WxT version | Drupal Core version | Drush version | PHP version |
---|---|---|---|
5.2.x | 10.2.x | >=12.4 | 8.2 |
5.1.x | 10.1.x | >=12.1 | 8.1 |
Drupal WxT thanks to the work done by the Acquia Team is able to use advanced +configuration management strategies.
At the moment this remains an opt-in process and you will have to add the
+following modules to your composer.json
before you add the code snippet
+below to your settings.php
file.
Once enabled all default configuration will be stored in /sites/default/files/config/default/
+and then depending on your environment additionally configuration splits can
+be leveraged depending on your SDLC
.
/**
+ * Configuration Split for Configuration Management
+ *
+ * WxT is following the best practices given by Acquia for configuration
+ * management. The "default" configuration directory should be shared between
+ * all multi-sites, and each multisite will override this selectively using
+ * configuration splits.
+ *
+ * To disable this functionality simply set the following parameters:
+ * $wxt_override_config_dirs = FALSE;
+ * $settings['config_sync_directory'] = $dir . "/config/$site_dir";
+ *
+ * See https://github.com/acquia/blt/blob/12.x/settings/config.settings.php
+ * for more information.
+ */
+
+use Drupal\wxt\Robo\Common\EnvironmentDetector;
+
+if (!isset($wxt_override_config_dirs)) {
+ $wxt_override_config_dirs = TRUE;
+}
+if ($wxt_override_config_dirs) {
+ $config_directories['sync'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+ $settings['config_sync_directory'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+}
+$split_filename_prefix = 'config_split.config_split';
+if (isset($config_directories['sync'])) {
+ $split_filepath_prefix = $config_directories['sync'] . '/' . $split_filename_prefix;
+}
+else {
+ $split_filepath_prefix = $settings['config_sync_directory'] . '/' . $split_filename_prefix;
+}
+
+/**
+ * Set environment splits.
+ */
+$split_envs = [
+ 'local',
+ 'dev',
+ 'test',
+ 'qa',
+ 'prod',
+ 'ci',
+];
+foreach ($split_envs as $split_env) {
+ $config["$split_filename_prefix.$split_env"]['status'] = FALSE;
+}
+if (!isset($split)) {
+ $split = 'none';
+ if (EnvironmentDetector::isLocalEnv()) {
+ $split = 'local';
+ }
+ if (EnvironmentDetector::isCiEnv()) {
+ $split = 'ci';
+ }
+ if (EnvironmentDetector::isDevEnv()) {
+ $split = 'dev';
+ }
+ elseif (EnvironmentDetector::isTestEnv()) {
+ $split = 'test';
+ }
+ elseif (EnvironmentDetector::isQaEnv()) {
+ $split = 'qa';
+ }
+ elseif (EnvironmentDetector::isProdEnv()) {
+ $split = 'prod';
+ }
+}
+if ($split != 'none') {
+ $config["$split_filename_prefix.$split"]['status'] = TRUE;
+}
+
+/**
+ * Set multisite split.
+ */
+// $config["$split_filename_prefix.SITENAME"]['status'] = TRUE;
+
Below are some recommended settings that improve the performance of Drupal WxT sites.
To properly configure PostgreSQL with Drupal you should ensure the following configuration is used.
Note: Some customizations might be necessary depending on your individual requirements.
postgresqlConfiguration:
+ listenAddresses: "'*'"
+ maxConnections: "200"
+ sharedBuffers: 512MB
+ workMem: 2048MB
+ effectiveCacheSize: 512MB
+ effectiveIoConcurrency: "100"
+ maintenanceWorkMem: 32MB
+ minWalSize: 512MB
+ maxWalSize: 512MB
+ walBuffers: 8048kB
+ byteaOutput: "'escape'"
+ hugePages: "off"
+ walLevel: "replica"
+ maxWalSenders: "0"
+ synchronousCommit: "on"
+ walKeepSegments: "130"
+ checkpointTimeout: "'15 min'"
+ checkpointCompletionTarget: "0.9"
+ walCompression: "on"
+ walWriterDelay: 200ms
+ walWriterFlushAfter: 1MB
+ bgwriterDelay: 200ms
+ bgwriterLruMaxpages: "100"
+ bgwriterLruMultiplier: "2.0"
+ bgwriterFlushAfter: "0"
+ maxWorkerProcesses: "8"
+ maxParallelWorkersPerGather: "4"
+ maxParallelWorkers: "4"
+
Note: The above is written in yaml syntax which will work for both Docker Compose and Kubernetes Helm Charts. For the
postgresql.conf
file itself without using these tools simply find the_
counterpart.
There is a known PostgreSQL performance issue that exists in Drupal and is related to leveraging queries with ILIKE
.
This issue is particularly noticeable in relation to the path_alias table.
There are patches being worked on to handle this in Drupal core but a very quick fix can be implemented leveraging pg_trgm.
There is a great blog article listed below which goes over this issue in more detail.
The instructions are a bit outdated so the updated syntax to enter in psql is given below:
CREATE EXTENSION pg_trgm;
+CREATE INDEX path_alias__alias_trgm_gist_idx ON path_alias USING gist (alias gist_trgm_ops);
+CREATE INDEX path_alias__path_trgm_gist_idx ON path_alias USING gist (path gist_trgm_ops);
+ANALYZE path_alias;
+
To properly configure Redis with Drupal you should ensure the following configuration is added to your settings.php
file.
Note: Some customizations might be necessary depending on your individual requirements.
if (extension_loaded('redis')) {
+ // Set Redis as the default backend for any cache bin not otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+ $settings['redis.connection']['interface'] = 'PhpRedis';
+ $settings['redis.connection']['scheme'] = 'http';
+ $settings['redis.connection']['host'] = 'localhost';
+ $settings['redis.connection']['port'] = '6379';
+ $settings['redis.connection']['password'] = getenv('REDIS_PASSWORD') ?: '';
+ $settings['redis.connection']['persistent'] = FALSE;
+
+ // Allow the services to work before the Redis module itself is enabled.
+ $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
+ $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
+
+ // Manually add the classloader path, this is required for the container cache bin definition below
+ // and allows to use it without the redis module being enabled.
+ $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
+
+ $settings['bootstrap_container_definition'] = [
+ 'parameters' => [],
+ 'services' => [
+ 'redis.factory' => [
+ 'class' => 'Drupal\redis\ClientFactory',
+ ],
+ 'cache.backend.redis' => [
+ 'class' => 'Drupal\redis\Cache\CacheBackendFactory',
+ 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
+ ],
+ 'cache.container' => [
+ 'class' => '\Drupal\redis\Cache\PhpRedis',
+ 'factory' => ['@cache.backend.redis', 'get'],
+ 'arguments' => ['container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
+ 'arguments' => ['@redis.factory'],
+ ],
+ 'serialization.phpserialize' => [
+ 'class' => 'Drupal\Component\Serialization\PhpSerialize',
+ ],
+ ],
+ ];
+
+ /** Optional prefix for cache entries */
+ $settings['cache_prefix'] = 'drupal_';
+
+ // Always set the fast backend for bootstrap, discover and config, otherwise
+ // this gets lost when redis is enabled.
+ $settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
+
+ // Use for all bins otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+
+ // Use for all queues unless otherwise specified for a specific queue.
+ $settings['queue_default'] = 'queue.redis';
+
+ // Or if you want to use reliable queue implementation.
+ // $settings['queue_default'] = 'queue.redis_reliable';
+
+ // Use this to only use Redis for a specific queue.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis';
+
+ // Use this to use reliable queue implementation.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable';
+}
+
To properly configure Varnish with Drupal you should ensure the following configuration is your default.vcl
file.
Note: Some customizations might be necessary depending on your individual requirements.
vcl 4.0;
+
+import std;
+import directors;
+
+backend nginx {
+ .host = "hostname-nginx";
+ .host_header = "hostname-nginx";
+ .port = "80";
+}
+
+sub vcl_init {
+ new backends = directors.round_robin();
+ backends.add_backend(nginx);
+}
+
+sub vcl_recv {
+ set req.http.X-Forwarded-Host = req.http.Host;
+ if (!req.http.X-Forwarded-Proto) {
+ set req.http.X-Forwarded-Proto = "http";
+ }
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Always cache certain file types
+ # Remove cookies that Drupal doesn't care about
+ if (req.url ~ "(?i)\.(asc|dat|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
+ unset req.http.Cookie;
+ } else if (req.http.Cookie) {
+ set req.http.Cookie = ";" + req.http.Cookie;
+ set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
+ set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
+ if (req.http.Cookie == "") {
+ unset req.http.Cookie;
+ } else {
+ return (pass);
+ }
+ }
+ # If POST, PUT or DELETE, then don't cache
+ if (req.method == "POST" || req.method == "PUT" || req.method == "DELETE") {
+ return (pass);
+ }
+ # Happens before we check if we have this in cache already.
+ #
+ # Typically you clean up the request here, removing cookies you don't need,
+ # rewriting the request, etc.
+ return (hash);
+ #return (pass);
+}
+
+sub vcl_backend_fetch {
+ # NEW
+ set bereq.http.Host = "hostname-nginx";
+
+ # Don't add 127.0.0.1 to X-Forwarded-For
+ set bereq.http.X-Forwarded-For = regsub(bereq.http.X-Forwarded-For, "(, )?127\.0\.0\.1$", "");
+}
+
+sub vcl_backend_response {
+ if (beresp.http.Location) {
+ set beresp.http.Location = regsub(
+ beresp.http.Location,
+ "^https?://[^/]+/",
+ bereq.http.X-Forwarded-Proto + "://" + bereq.http.X-Forwarded-Host + "/"
+ );
+ }
+ # Only cache select response codes
+ if (beresp.status == 200 || beresp.status == 203 || beresp.status == 204 || beresp.status == 206 || beresp.status == 300 || beresp.status == 301 || beresp.status == 404 || beresp.status == 405 || beresp.status == 410 || beresp.status == 414 || beresp.status == 501) {
+ # Cache for 5 minutes
+ set beresp.ttl = 5m;
+ set beresp.grace = 12h;
+ set beresp.keep = 24h;
+ } else {
+ set beresp.ttl = 0s;
+ }
+}
+
+sub vcl_deliver {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Comment these for easier Drupal cache tag debugging in development.
+ unset resp.http.Cache-Tags;
+ unset resp.http.X-Drupal-Cache-Contexts;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net data:; font-src 'self' *.example.ca https://fonts.gstatic.com";
+
+ # Add CORS Headers
+ # if (req.http.Origin ~ "(?i)\.example\.ca$") {
+ # if (req.url ~ "\.(ttd|woff|woff2)(\?.*)?$") {
+ # set resp.http.Access-Control-Allow-Origin = "*";
+ # set resp.http.Access-Control-Allow-Methods = "GET";
+ # }
+ # }
+
+ # Add X-Frame-Options
+ if (req.url ~ "^/livechat" || req.url ~ "^/(en/|fr/)?entity-browser/") {
+ set resp.http.X-Frame-Options = "SAMEORIGIN";
+ } else {
+ set resp.http.X-Frame-Options = "DENY";
+ }
+
+ set resp.http.X-Content-Type-Options = "nosniff";
+ set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # Happens when we have all the pieces we need, and are about to send the
+ # response to the client.
+ #
+ # You can do accounting or modifying the final object here.
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+ # Handle errors
+ if ( (resp.status >= 500 && resp.status <= 599)
+ || resp.status == 400
+ || resp.status == 401
+ || resp.status == 403
+ || resp.status == 404) {
+ return (synth(resp.status));
+ }
+}
+
+sub vcl_synth {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca data:;";
+ # set resp.http.X-Content-Type-Options = "nosniff";
+ # set resp.http.X-Frame-Options = "DENY";
+ # set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # if (resp.status >= 500 && resp.status <= 599) {
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+ # return (deliver);
+ # } elseif (resp.status == 400) { # 400 - Bad Request
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/400.html"));
+ # return (deliver);
+ # } elseif (resp.status == 401) { # 401 - Unauthorized
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/401.html"));
+ # return (deliver);
+ # } elseif (resp.status == 403) { # 403 - Forbidden
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/403.html"));
+ # return (deliver);
+ # } elseif (resp.status == 404) { # 404 - Not Found
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/404.html"));
+ # return (deliver);
+ # } else
+ if (resp.status == 700) { # Respond to healthcheck
+ set resp.status = 200;
+ set resp.http.Content-Type = "text/plain";
+ synthetic ( {"OK"} );
+ return (deliver);
+ }
+}
+
+##
+# ERROR HANDLING
+##
+# sub vcl_backend_error {
+# set beresp.http.Content-Type = "text/html; charset=utf-8";
+# synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+# return (deliver);
+# }
+
WxT releases are numbered using a form of semantic versioning. More information can be found in our Versioning page.
MAJOR.FEATURE.SPRINT
In general, when preparing a release: increment the FEATURE when Drupal Core has a major release (ie. 9.5.x to 10.0.x) otherwise simply increment the SPRINT number.
Create an issue in the Drupal WxT project on GitHub for release tracking, title it Release x.x.x
(where x.x.x is the incremented version number).
This issue should contain the following checklist as well as any other related steps or information regarding preparing the release.
See the [full release documentation](https://drupalwxt.github.io/docs/development/release-process/) for more detail.
+
+- [ ] All related projects (wxt_library and wxt_bootstrap) tagged and released on GitHub.com and Drupal.org
+- [ ] Version number selected
+- [ ] CHANGELOG.md updated
+- [ ] composer.json updated
+- [ ] Run version.sh for hook_updates and wxt contrib
+- [ ] CI build passes
+- [ ] Releases tagged and pushed to GitHub.com and Drupal.org
+- [ ] WxT released on Drupal.org (https://drupalwxt.github.io/docs/development/release-process/#release)
+- [ ] Add changelog information to published tag once CI is done
+
Ensure the changelog contains an entry for the release and is updated as issues and changes are resolved (in the next steps or when committing code / changes).
composer.json
filecomposer.json
fileAll projects must be released on drupal.org (and github).
Note: Changes to
composer.json
file (specifically dev dependencies and repositories) should be mentioned in the CHANGELOG.
Drupal.org does not currently support semantic versioning. Instead, the version number on drupal.org is 10.x-X.YZZ
, where:
X = MAJOR
Y = FEATURE
ZZ = SPRINT
(two digits - add leading zero for < 10)If the wxt dependent modules are updated, we need to reflect this in wxt composer.json
and the CHANGELOG.
git clone https://github.com/drupalwxt/wxt.git
composer.json
)git tag MAJOR.FEATURE.SPRINT
git push $GITHUB_REMOTE MAJOR.FEATURE.SPRINT
release notes
The builds on Drupal.org are incomplete as they don’t fully support Composer yet which is why we host a tarball on GitHub for those not using Composer.
<strong>CHANGELOG</strong>
+
+See the <a href="https://github.com/drupalwxt/wxt/blob/5.2.x/CHANGELOG.md">changelog.md</a> file.
+
Largely when doing any theme related work with Drupal WxT this almost always should be done in a sub-theme.
For more on creating sub-themes please consult the official documentation:
To assist with sub-theme creation WxT Bootstrap provides an example starterkit that should be of benefit.
Note: Sub-themes are just like any other theme except they inherit the parent theme’s resources.
a) Replace every instance of THEMENAME
with your chosen machine name often of the pattern <prefix>_bootstrap
.
b) Enable your new sub-theme preferably via drush:
drush en `<prefix>_bootstrap`
+drush cc css-js
+
c) Point to your new sub theme for WxT Library to properly load assets under Themes Visibility on the /admin/config/wxt/wxt_library
page.
If the theme you are extending has custom block templates these won’t be immediately inherited because a sub-theme creates copies of all the blocks in the parent theme and renames them with the sub-theme’s name as a prefix. Twig block templates are derived from the block’s name, so this breaks the link between these templates and their block.
Fixing this problem currently requires a hook in the THEMENAME.theme
file and should have the following contents:
/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function THEMENAME_theme_suggestions_block_alter(&$suggestions, $variables) {
+ // Load theme suggestions for blocks from parent theme.
+ // https://www.drupal.org/project/wxt/issues/3310485#comment-14715969
+ for ($i = 0; $i < count($suggestions); $i++) {
+ if (str_contains($suggestions[$i], 'THEMENAME_')) {
+ $new_suggestions = [
+ str_replace('THEMENAME_', '', $suggestions[$i]),
+ str_replace('THEMENAME_', 'wxt_bootstrap_', $suggestions[$i]),
+ ];
+ array_splice($suggestions, $i, 0, $new_suggestions);
+ $i += 2;
+ }
+ }
+}
+
The following provides an example of how you can configure your sub theme to be installed as the default on a module install:
/**
+ * Implements hook_modules_installed().
+ */
+function MODULENAME_modules_installed($modules) {
+ if (in_array('wxt', $modules)) {
+ \Drupal::configFactory()
+ ->getEditable('system.theme')
+ ->set('default', 'THEMENAME')
+ ->set('admin', 'claro')
+ ->save(TRUE);
+ }
+ }
+}
+
The following provides an example of how you can configure wxt_library
to use your sub theme by creating a config/install/wxt_library.settings.yml
file with the following contents:
url:
+ visibility: 0
+ pages:
+ - 'admin*'
+ - 'imagebrowser*'
+ - 'img_assist*'
+ - 'imce*'
+ - 'node/add/*'
+ - 'node/*/edit'
+ - 'print/*'
+ - 'printpdf/*'
+ - 'system/ajax'
+ - 'system/ajax/*'
+theme:
+ visibility: 1
+ themes:
+ THEMENAME: THEMENAME
+ wxt_bootstrap: wxt_bootstrap
+minimized:
+ options: 1
+files:
+ types:
+ css: css
+ js: js
+wxt:
+ theme: theme-gcweb
+
The Drupal WxT distribution is following semantic versioning.
WxT typically makes a sprint release every four to six weeks. We will also use sprint releases to package new minor releases of Drupal Core with WxT as they become available.
In addition, we will also increment the major version number of WxT about once every four to six months.
Support for semantic versioning for extensions (modules, themes, etc) is still ongoing.
The three parts of our versioning system are MAJOR.FEATURE.SPRINT.
Given the following tag: 10.x-2.00:
10 | Major version of Drupal Core |
x | |
5 | Major version of WxT |
0 | Feature release of WxT. Also increments with minor core releases. |
0 | Sprint release between feature releases |
Note: Due to the constraints of drupal.org, there is no separator between the FEATURE and SPRINT digits.
This section documents best practices on how to deploy Drupal WxT to your chosen environment.
For the (optional) container based development workflow this is roughly the steps that are followed.
Clone the docker-scaffold repository:
git clone https://github.com/drupalwxt/docker-scaffold.git docker
+
Note: The
docker
folder should be added to your.gitignore
file.
The following are the steps you should follow for a Linux based environment.
Create the necessary symlinks:
ln -s docker/docker-compose.base.yml docker-compose.base.yml
+ln -s docker/docker-compose.ci.yml docker-compose.ci.yml
+ln -sf docker/docker-compose.yml docker-compose.yml
+
Create and adjust the following Makefile:
include .env
+NAME := $(or $(BASE_IMAGE),$(BASE_IMAGE),drupalwxt/site-wxt)
+VERSION := $(or $(VERSION),$(VERSION),'latest')
+PLATFORM := $(shell uname -s)
+$(eval GIT_USERNAME := $(if $(GIT_USERNAME),$(GIT_USERNAME),gitlab-ci-token))
+$(eval GIT_PASSWORD := $(if $(GIT_PASSWORD),$(GIT_PASSWORD),$(CI_JOB_TOKEN)))
+DOCKER_REPO := https://github.com/drupalwxt/docker-scaffold.git
+GET_DOCKER := $(shell [ -d docker ] || git clone $(DOCKER_REPO) docker)
+include docker/Makefile
+
Build and setup your environment with default content:
# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.yml build --no-cache
+docker compose -f docker-compose.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you have Docker for Desktop
and a new enough OSX environment (Monterey or higher) then the steps are the exact same as those for the Linux environment given above.
All that is required in advance is to enable VirtioFS
accelerated directory sharing which you can see in the attached picture below.
Docker for Desktop VirtioFS
+
Image: Drupal / CC-BY-CA
For older environments you may still use mutagen which is discussed below.
While this is fixed with the new virtualization framework discussed above.
For older environments mutagen will have to be used instead and as such requires a few additional steps.
# Mutagen Setup
+export VOLUME=site-wxt-mutagen-cache
+docker volume create $VOLUME
+docker container create --name $VOLUME -v $VOLUME:/volumes/$VOLUME mutagenio/sidecar:0.13.0-beta3
+docker start $VOLUME
+mutagen sync create --name $VOLUME --sync-mode=two-way-resolved --default-file-mode-beta 0666 --default-directory-mode-beta 0777 $(pwd) docker://$VOLUME/volumes/$VOLUME
+
+# Create symlinks
+ln -s docker/docker-compose.mutagen.yml docker-compose.mutagen.yml
+
+# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.mutagen.yml build --no-cache
+docker compose -f docker-compose.mutagen.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you wish to have a pristine docker environment you may execute the following commands.
docker rm $(docker ps -a -q) --force
+docker rmi $(docker images -q) --force
+docker volume prune -f
+
For those still using Mutagen you may also need to execute the following command:
mutagen sync terminate <sync_xxxxx>
+
This document represents a high-level technical overview of how the Helm Chart for Drupal WxT was built and how we envision Drupal itself should be architected in the cloud to support any of the Government of Canada procured cloud service providers (AWS
, Azure
, and GCP
). It should be noted that this Helm chart would also work in an on-premise environment with the appropriate Kubernetes infrastructure.
A key mandate when creating this architecture was to follow the Open Source Directive as given by the Treasury Board Secretariat (C.2.3.8) which states that you should try to use open standards and open source software first. Additionally, where possible all functionality should be exposed as restful services and leverage microservices via a containerized approach (C2.3.10).
We are leveraging a microservices design pattern utilizing immutable and scanned images through containerization running on Kubernetes with a platform that has been built and open sourced by Statistics Canada. While the platform will be discussed briefly to provide context the bulk of the document discusses how Drupal is installed and configured on top of it.
Kubernetes orchestrates the computing, networking, and storage infrastructure on behalf of user workloads. It assigns workloads and resources to a series of nearly identically-configured virtual machines.
Kukbernetes supports workloads running anywhere, from IoT devices, to private cloud and all the way to public cloud. This is possible due to Kubernetes’ pluggable architecture, which defines interfaces that are then implemented for the different environments. Kubernetes provides an Infrastructure as Code environment defined through declarative configuration. Because Kubernetes abstracts away the implementation of the computing environment, application dependencies such as storage, networking, etc., applications do not have to concern themselves with these differences.
Kubernetes is backed by a huge (10,000+) and vibrant growing community, consisting of end users, business, vendors and large cloud providers.
This architecture brings many benefits to the Government of Canada:
Kubernetes is supported across all cloud service providers (fully managed and self managed), preventing vendor lock-in. Managed offerings are available from Google, IBM, Azure, Digital Ocean, Amazon, Oracle and more. The choice whether to roll your own, using a managed service (AKS, EKS, GKE) or a Platform as a Service (OpenShift, Pivotal) is up to the organization to decide based on their requirements and risks. Our preference is to stay as close as possible to the open source version of Kubernetes as well as tooling in order to remain compatible with the different Kubernetes offerings (raw, managed, platform, etc.).
Kubernetes is being actively investigated and/or used by many departments across the Government of Canada. Departments are starting to collaborate more and work together towards a common, well-vetted solution and this is why we have have Open Sourced our platform on the GC Accelerators hoping to foster this collaboration and form a community of practice.
Provided below is the Terraform (Infrastructure as Code) necessarily to install the Azure Kubernetes Service Infrastructure as well as configure with optional platform components (RBAC, Service Mesh, Policies, etc).
A managed Drupal Platform as a Service is a strong candidate to take advantage of what a Kubernetes platform offers. The design enables a quick onboarding of new workloads through the repeatable deployment methodology provided by Kubernetes.
Recommendation: Kubernetes
Kubernetes is the basis of the Drupal platform and was further discussed above.
The whole Drupal application stack can be easily installed in a distributed fashion in minutes using our Helm chart, The chart facilitates a managed service workflow (rolling updates, cronjobs, health checks, auto-scaling, etc.) without user intervention.
Recommendation: Istio
The ingress controller is responsible for accepting external HTTPS connections and routing them to backend applications based on configuration defined in Kubernetes Ingress objects. Routing can be done by domain and/or path.
Recommendation: Varnish
Varnish is a highly customizable reverse proxy cache. This will aid in supporting a large number of concurrent visitors as the final rendered pages can be served from cache. Varnish is only required on the public environment and is not used in the content staging environment.
Nginx can technically address some of the cache requirements needed, however the open source version does not support purging selective pages. We need to clear caches based on content being updated / saved which Varnish supports along with the Expire Drupal module quite readily
Recommendation: Nginx
Nginx is an open source web server that can also be used a reverse proxy, HTTP cache, and load balancer. Due to its root in performance optimization under scale, Nginx often outperforms similarly popular web servers and is built to offer low memory usage, and high concurrency.
Recommendation: PHP-FPM
Drupal runs in the PHP runtime environment. PHP-FPM is the process manager organized as a master process managing pools of individual worker processes. Its architecture shares design similarities with event-driven web servers such as Nginx and allows for PHP scripts to use as much of the server's available resources as necessary without additional overhead that comes from running them inside of web server processes.
The PHP-FPM master process dynamically creates and terminates worker processes (within configurable limits) as traffic to PHP scripts increases and decreases. Processing scripts in this way allows for much higher processing performance, improved security, and better stability. The primary performance benefits from using PHP-FPM are more efficient PHP handling and ability to use opcode caching.
Recommendation: Redis
Redis is an advanced key-value cache and store.
It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps, etc.
Redis is particularly useful when using cloud managed databases to limit the overall database load and to make performance more consistent.
Recommendation: MySQL or PostgreSQL
Drupal maintains its state in a database and while supports several types only MySQL or PostgreSQL should be considered. Personally, we highly recommend PostgreSQL based on the experience we had building / launching quite a few Drupal sites in the cloud with it. However both run quite well with minimal operational concerns. Additionally the Helm Chart supports connection pooling using either ProxySQL and / or PGBouncer depending on the database used.
Note: Our recommendation would be to use a managed database offering from the cloud providers for a production environment. Coupled with a managed file service, this removes all stateful components from the cluster enabling the best application experience possible.
Drupal stores generated CSS/JS assets and uploaded content (images, videos, etc.) in a file storage. As the architecture is designed to be distributed, this present some design considerations for us.
Fully managed file shares in the cloud that are accessible via Server Message Block (SMB) or NFS protocol. Support is provided for dynamically creating and using a persistent volume with Azure Files in the Azure Kubernetes Service.
For more information on Azure Files, please see Azure Files and AKS.
Note: This is currently our recommended choice as it results in a simpler installation in Azure then relying on an S3 compatible object store discussed below. Similar storage solutions exist with the other cloud providers.
This page provides an overview for the process of creating a monolith container to deploy to Azure App Service (appsvc). It assumes you already have your project setup to work with the docker-scaffold repository. For initial project setup using docker-scaffold, see the beginning of the container based development workflow here - Local Docker setup
# Make our base docker image
+make build
+
+# Build the appsvc image
+docker compose -f docker-compose.appsvc.yml up -d
+
Note: After making changes to the project, you will need to remove your base image and build it again. This will ensure all changed files are copied into the base image as needed.
docker rmi $(docker images -q) --force
+
Now that you have build your appsvc image, you need to tag and push it to the ACR in order to deploy to it App Service.
docker login MY-CONTAINER-REGISTRY.azurecr.io
+docker tag site-XYZ-appsvc:latest MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+docker push MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+
Once this is done, you should be able to see your new image in the ACR.
In order to automate the build process using Azure DevOps, you can create a pipeline file in the root of your Drupal repo - Example pipeline file
This pipeline script will build the appsvc image and push it to your container registry. Make sure you have cretaed the required Service Connection in Azure DevOps (Git repository, ACR).
This is the multi-page printable view of this section. +Click here to print.
This section provides information for developers who wish to help collaborate and improve Drupal WxT.
The goal of Drupal WxT since the 4.1.x line is to make the installation profile very minimal by default but providing additional extensions that can be enabled as desired.
What WxT offers is some light enhancements to Drupal Core, mainly around security and performance, and integration with the Web Experience Toolkit. By default, the distribution offers minimal functionality to allow full customizations by users. However a great deal of optional extensions are available that can provide additional functionality generally beneficial to Government departments.
Note: In the future we are looking into providing a list of community modules that are build to work with the distribution but are “out of tree”.
All of the optional modules are located in the wxt_ext
folder named after WxT Extend and can be enabled during the initial site installation by passing the following flag via the drush cli:
wxt_extension_configure_form.select_all='TRUE'
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
In order to provide a list of the optional enabled extensions during the installation that can be checked, all that any module has to do is provide a modulename.wxt_extension.yml
file in their root and they will be picked as installable during the profile install and also respond to the additional drush flag discussed above.
For more information on some of the history leading to this design:
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
The following command is all you need to get started:
composer create-project drupalwxt/wxt-project:5.1.1 <site-name>
+
Note: For development you may also specify a branch using
drupalwxt/wxt-project:5.1.x-dev
.
You can see a working example of a fully generated Composer Project Template over at:
Where the following is the command that was used for the initial generation:
composer create-project drupalwxt/wxt-project:5.1.1 site-wxt
+
Note: Remember to keep the
composer.json
andcomposer.lock
files that exist abovedocroot
in source control as they are controlling your dependencies.
List of common commands are as follows:
Task | Composer |
---|---|
Installing a contrib project (latest version) | composer require drupal/PROJECT |
Installing a contrib project (specific version) | composer require drupal/PROJECT:1.0.0-beta5 |
Updating all projects including Drupal Core | composer update |
Updating a single contrib project | composer update drupal/PROJECT_NAME |
Updating Drupal Core | composer update drupal/core |
Note: Composer is a dependency manager and helps us keep track of what code and at what version our application relies on so that it always get installed the right way on every copy of that application.
A specific version can be specified from the cli:
composer require drupal/<modulename>:<version>
+
However please note if you specify a branch, such as 1.x you must add -dev
to the end of the version:
composer require drupal/token:1.x-dev
+
Taking a look at the .gitignore
file, you will discover that certain directories, including all those directories containing contributed projects, are excluded from source control which is by design.
Note: Unlike Drush in a Composer derived project you should never commit your install dependencies to source control.
Composer will create composer.lock
file, which is a list of dependencies that were installed, and in which versions.
Note: In general you should always commit your
composer.lock
file to source control so that others via a quickcomposer install
can have everything installed along with the correct versions specified in thecomposer.lock
file.
Please don’t add drupal/core
to your project’s composer.json since WxT manages Drupal Core for you along with the series of patches on top of it.
For example:
drupalwxt/wxt:~5.2.0
will require Drupal Core 10.2.xdrupalwxt/wxt:~5.1.0
will require Drupal Core 10.1.xWhen you need to update Drupal Core as an example from 10.1.x to 10.2.x, all you would do is change your requirement for drupalwxt/wxt
in your composer.json
file:
composer require --no-update drupalwxt/wxt:~5.2.0
+composer update
+
WxT version | Drupal Core version | Drush version | PHP version |
---|---|---|---|
5.2.x | 10.2.x | >=12.4 | 8.2 |
5.1.x | 10.1.x | >=12.1 | 8.1 |
Drupal WxT thanks to the work done by the Acquia Team is able to use advanced +configuration management strategies.
At the moment this remains an opt-in process and you will have to add the
+following modules to your composer.json
before you add the code snippet
+below to your settings.php
file.
Once enabled all default configuration will be stored in /sites/default/files/config/default/
+and then depending on your environment additionally configuration splits can
+be leveraged depending on your SDLC
.
/**
+ * Configuration Split for Configuration Management
+ *
+ * WxT is following the best practices given by Acquia for configuration
+ * management. The "default" configuration directory should be shared between
+ * all multi-sites, and each multisite will override this selectively using
+ * configuration splits.
+ *
+ * To disable this functionality simply set the following parameters:
+ * $wxt_override_config_dirs = FALSE;
+ * $settings['config_sync_directory'] = $dir . "/config/$site_dir";
+ *
+ * See https://github.com/acquia/blt/blob/12.x/settings/config.settings.php
+ * for more information.
+ */
+
+use Drupal\wxt\Robo\Common\EnvironmentDetector;
+
+if (!isset($wxt_override_config_dirs)) {
+ $wxt_override_config_dirs = TRUE;
+}
+if ($wxt_override_config_dirs) {
+ $config_directories['sync'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+ $settings['config_sync_directory'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+}
+$split_filename_prefix = 'config_split.config_split';
+if (isset($config_directories['sync'])) {
+ $split_filepath_prefix = $config_directories['sync'] . '/' . $split_filename_prefix;
+}
+else {
+ $split_filepath_prefix = $settings['config_sync_directory'] . '/' . $split_filename_prefix;
+}
+
+/**
+ * Set environment splits.
+ */
+$split_envs = [
+ 'local',
+ 'dev',
+ 'test',
+ 'qa',
+ 'prod',
+ 'ci',
+];
+foreach ($split_envs as $split_env) {
+ $config["$split_filename_prefix.$split_env"]['status'] = FALSE;
+}
+if (!isset($split)) {
+ $split = 'none';
+ if (EnvironmentDetector::isLocalEnv()) {
+ $split = 'local';
+ }
+ if (EnvironmentDetector::isCiEnv()) {
+ $split = 'ci';
+ }
+ if (EnvironmentDetector::isDevEnv()) {
+ $split = 'dev';
+ }
+ elseif (EnvironmentDetector::isTestEnv()) {
+ $split = 'test';
+ }
+ elseif (EnvironmentDetector::isQaEnv()) {
+ $split = 'qa';
+ }
+ elseif (EnvironmentDetector::isProdEnv()) {
+ $split = 'prod';
+ }
+}
+if ($split != 'none') {
+ $config["$split_filename_prefix.$split"]['status'] = TRUE;
+}
+
+/**
+ * Set multisite split.
+ */
+// $config["$split_filename_prefix.SITENAME"]['status'] = TRUE;
+
Below are some recommended settings that improve the performance of Drupal WxT sites.
To properly configure PostgreSQL with Drupal you should ensure the following configuration is used.
Note: Some customizations might be necessary depending on your individual requirements.
postgresqlConfiguration:
+ listenAddresses: "'*'"
+ maxConnections: "200"
+ sharedBuffers: 512MB
+ workMem: 2048MB
+ effectiveCacheSize: 512MB
+ effectiveIoConcurrency: "100"
+ maintenanceWorkMem: 32MB
+ minWalSize: 512MB
+ maxWalSize: 512MB
+ walBuffers: 8048kB
+ byteaOutput: "'escape'"
+ hugePages: "off"
+ walLevel: "replica"
+ maxWalSenders: "0"
+ synchronousCommit: "on"
+ walKeepSegments: "130"
+ checkpointTimeout: "'15 min'"
+ checkpointCompletionTarget: "0.9"
+ walCompression: "on"
+ walWriterDelay: 200ms
+ walWriterFlushAfter: 1MB
+ bgwriterDelay: 200ms
+ bgwriterLruMaxpages: "100"
+ bgwriterLruMultiplier: "2.0"
+ bgwriterFlushAfter: "0"
+ maxWorkerProcesses: "8"
+ maxParallelWorkersPerGather: "4"
+ maxParallelWorkers: "4"
+
Note: The above is written in yaml syntax which will work for both Docker Compose and Kubernetes Helm Charts. For the
postgresql.conf
file itself without using these tools simply find the_
counterpart.
There is a known PostgreSQL performance issue that exists in Drupal and is related to leveraging queries with ILIKE
.
This issue is particularly noticeable in relation to the path_alias table.
There are patches being worked on to handle this in Drupal core but a very quick fix can be implemented leveraging pg_trgm.
There is a great blog article listed below which goes over this issue in more detail.
The instructions are a bit outdated so the updated syntax to enter in psql is given below:
CREATE EXTENSION pg_trgm;
+CREATE INDEX path_alias__alias_trgm_gist_idx ON path_alias USING gist (alias gist_trgm_ops);
+CREATE INDEX path_alias__path_trgm_gist_idx ON path_alias USING gist (path gist_trgm_ops);
+ANALYZE path_alias;
+
To properly configure Redis with Drupal you should ensure the following configuration is added to your settings.php
file.
Note: Some customizations might be necessary depending on your individual requirements.
if (extension_loaded('redis')) {
+ // Set Redis as the default backend for any cache bin not otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+ $settings['redis.connection']['interface'] = 'PhpRedis';
+ $settings['redis.connection']['scheme'] = 'http';
+ $settings['redis.connection']['host'] = 'localhost';
+ $settings['redis.connection']['port'] = '6379';
+ $settings['redis.connection']['password'] = getenv('REDIS_PASSWORD') ?: '';
+ $settings['redis.connection']['persistent'] = FALSE;
+
+ // Allow the services to work before the Redis module itself is enabled.
+ $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
+ $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
+
+ // Manually add the classloader path, this is required for the container cache bin definition below
+ // and allows to use it without the redis module being enabled.
+ $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
+
+ $settings['bootstrap_container_definition'] = [
+ 'parameters' => [],
+ 'services' => [
+ 'redis.factory' => [
+ 'class' => 'Drupal\redis\ClientFactory',
+ ],
+ 'cache.backend.redis' => [
+ 'class' => 'Drupal\redis\Cache\CacheBackendFactory',
+ 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
+ ],
+ 'cache.container' => [
+ 'class' => '\Drupal\redis\Cache\PhpRedis',
+ 'factory' => ['@cache.backend.redis', 'get'],
+ 'arguments' => ['container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
+ 'arguments' => ['@redis.factory'],
+ ],
+ 'serialization.phpserialize' => [
+ 'class' => 'Drupal\Component\Serialization\PhpSerialize',
+ ],
+ ],
+ ];
+
+ /** Optional prefix for cache entries */
+ $settings['cache_prefix'] = 'drupal_';
+
+ // Always set the fast backend for bootstrap, discover and config, otherwise
+ // this gets lost when redis is enabled.
+ $settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
+
+ // Use for all bins otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+
+ // Use for all queues unless otherwise specified for a specific queue.
+ $settings['queue_default'] = 'queue.redis';
+
+ // Or if you want to use reliable queue implementation.
+ // $settings['queue_default'] = 'queue.redis_reliable';
+
+ // Use this to only use Redis for a specific queue.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis';
+
+ // Use this to use reliable queue implementation.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable';
+}
+
To properly configure Varnish with Drupal you should ensure the following configuration is your default.vcl
file.
Note: Some customizations might be necessary depending on your individual requirements.
vcl 4.0;
+
+import std;
+import directors;
+
+backend nginx {
+ .host = "hostname-nginx";
+ .host_header = "hostname-nginx";
+ .port = "80";
+}
+
+sub vcl_init {
+ new backends = directors.round_robin();
+ backends.add_backend(nginx);
+}
+
+sub vcl_recv {
+ set req.http.X-Forwarded-Host = req.http.Host;
+ if (!req.http.X-Forwarded-Proto) {
+ set req.http.X-Forwarded-Proto = "http";
+ }
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Always cache certain file types
+ # Remove cookies that Drupal doesn't care about
+ if (req.url ~ "(?i)\.(asc|dat|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
+ unset req.http.Cookie;
+ } else if (req.http.Cookie) {
+ set req.http.Cookie = ";" + req.http.Cookie;
+ set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
+ set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
+ if (req.http.Cookie == "") {
+ unset req.http.Cookie;
+ } else {
+ return (pass);
+ }
+ }
+ # If POST, PUT or DELETE, then don't cache
+ if (req.method == "POST" || req.method == "PUT" || req.method == "DELETE") {
+ return (pass);
+ }
+ # Happens before we check if we have this in cache already.
+ #
+ # Typically you clean up the request here, removing cookies you don't need,
+ # rewriting the request, etc.
+ return (hash);
+ #return (pass);
+}
+
+sub vcl_backend_fetch {
+ # NEW
+ set bereq.http.Host = "hostname-nginx";
+
+ # Don't add 127.0.0.1 to X-Forwarded-For
+ set bereq.http.X-Forwarded-For = regsub(bereq.http.X-Forwarded-For, "(, )?127\.0\.0\.1$", "");
+}
+
+sub vcl_backend_response {
+ if (beresp.http.Location) {
+ set beresp.http.Location = regsub(
+ beresp.http.Location,
+ "^https?://[^/]+/",
+ bereq.http.X-Forwarded-Proto + "://" + bereq.http.X-Forwarded-Host + "/"
+ );
+ }
+ # Only cache select response codes
+ if (beresp.status == 200 || beresp.status == 203 || beresp.status == 204 || beresp.status == 206 || beresp.status == 300 || beresp.status == 301 || beresp.status == 404 || beresp.status == 405 || beresp.status == 410 || beresp.status == 414 || beresp.status == 501) {
+ # Cache for 5 minutes
+ set beresp.ttl = 5m;
+ set beresp.grace = 12h;
+ set beresp.keep = 24h;
+ } else {
+ set beresp.ttl = 0s;
+ }
+}
+
+sub vcl_deliver {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Comment these for easier Drupal cache tag debugging in development.
+ unset resp.http.Cache-Tags;
+ unset resp.http.X-Drupal-Cache-Contexts;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net data:; font-src 'self' *.example.ca https://fonts.gstatic.com";
+
+ # Add CORS Headers
+ # if (req.http.Origin ~ "(?i)\.example\.ca$") {
+ # if (req.url ~ "\.(ttd|woff|woff2)(\?.*)?$") {
+ # set resp.http.Access-Control-Allow-Origin = "*";
+ # set resp.http.Access-Control-Allow-Methods = "GET";
+ # }
+ # }
+
+ # Add X-Frame-Options
+ if (req.url ~ "^/livechat" || req.url ~ "^/(en/|fr/)?entity-browser/") {
+ set resp.http.X-Frame-Options = "SAMEORIGIN";
+ } else {
+ set resp.http.X-Frame-Options = "DENY";
+ }
+
+ set resp.http.X-Content-Type-Options = "nosniff";
+ set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # Happens when we have all the pieces we need, and are about to send the
+ # response to the client.
+ #
+ # You can do accounting or modifying the final object here.
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+ # Handle errors
+ if ( (resp.status >= 500 && resp.status <= 599)
+ || resp.status == 400
+ || resp.status == 401
+ || resp.status == 403
+ || resp.status == 404) {
+ return (synth(resp.status));
+ }
+}
+
+sub vcl_synth {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca data:;";
+ # set resp.http.X-Content-Type-Options = "nosniff";
+ # set resp.http.X-Frame-Options = "DENY";
+ # set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # if (resp.status >= 500 && resp.status <= 599) {
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+ # return (deliver);
+ # } elseif (resp.status == 400) { # 400 - Bad Request
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/400.html"));
+ # return (deliver);
+ # } elseif (resp.status == 401) { # 401 - Unauthorized
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/401.html"));
+ # return (deliver);
+ # } elseif (resp.status == 403) { # 403 - Forbidden
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/403.html"));
+ # return (deliver);
+ # } elseif (resp.status == 404) { # 404 - Not Found
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/404.html"));
+ # return (deliver);
+ # } else
+ if (resp.status == 700) { # Respond to healthcheck
+ set resp.status = 200;
+ set resp.http.Content-Type = "text/plain";
+ synthetic ( {"OK"} );
+ return (deliver);
+ }
+}
+
+##
+# ERROR HANDLING
+##
+# sub vcl_backend_error {
+# set beresp.http.Content-Type = "text/html; charset=utf-8";
+# synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+# return (deliver);
+# }
+
WxT releases are numbered using a form of semantic versioning. More information can be found in our Versioning page.
MAJOR.FEATURE.SPRINT
In general, when preparing a release: increment the FEATURE when Drupal Core has a major release (ie. 9.5.x to 10.0.x) otherwise simply increment the SPRINT number.
Create an issue in the Drupal WxT project on GitHub for release tracking, title it Release x.x.x
(where x.x.x is the incremented version number).
This issue should contain the following checklist as well as any other related steps or information regarding preparing the release.
See the [full release documentation](https://drupalwxt.github.io/docs/development/release-process/) for more detail.
+
+- [ ] All related projects (wxt_library and wxt_bootstrap) tagged and released on GitHub.com and Drupal.org
+- [ ] Version number selected
+- [ ] CHANGELOG.md updated
+- [ ] composer.json updated
+- [ ] Run version.sh for hook_updates and wxt contrib
+- [ ] CI build passes
+- [ ] Releases tagged and pushed to GitHub.com and Drupal.org
+- [ ] WxT released on Drupal.org (https://drupalwxt.github.io/docs/development/release-process/#release)
+- [ ] Add changelog information to published tag once CI is done
+
Ensure the changelog contains an entry for the release and is updated as issues and changes are resolved (in the next steps or when committing code / changes).
composer.json
filecomposer.json
fileAll projects must be released on drupal.org (and github).
Note: Changes to
composer.json
file (specifically dev dependencies and repositories) should be mentioned in the CHANGELOG.
Drupal.org does not currently support semantic versioning. Instead, the version number on drupal.org is 10.x-X.YZZ
, where:
X = MAJOR
Y = FEATURE
ZZ = SPRINT
(two digits - add leading zero for < 10)If the wxt dependent modules are updated, we need to reflect this in wxt composer.json
and the CHANGELOG.
git clone https://github.com/drupalwxt/wxt.git
composer.json
)git tag MAJOR.FEATURE.SPRINT
git push $GITHUB_REMOTE MAJOR.FEATURE.SPRINT
release notes
The builds on Drupal.org are incomplete as they don’t fully support Composer yet which is why we host a tarball on GitHub for those not using Composer.
<strong>CHANGELOG</strong>
+
+See the <a href="https://github.com/drupalwxt/wxt/blob/5.2.x/CHANGELOG.md">changelog.md</a> file.
+
Largely when doing any theme related work with Drupal WxT this almost always should be done in a sub-theme.
For more on creating sub-themes please consult the official documentation:
To assist with sub-theme creation WxT Bootstrap provides an example starterkit that should be of benefit.
Note: Sub-themes are just like any other theme except they inherit the parent theme’s resources.
a) Replace every instance of THEMENAME
with your chosen machine name often of the pattern <prefix>_bootstrap
.
b) Enable your new sub-theme preferably via drush:
drush en `<prefix>_bootstrap`
+drush cc css-js
+
c) Point to your new sub theme for WxT Library to properly load assets under Themes Visibility on the /admin/config/wxt/wxt_library
page.
If the theme you are extending has custom block templates these won’t be immediately inherited because a sub-theme creates copies of all the blocks in the parent theme and renames them with the sub-theme’s name as a prefix. Twig block templates are derived from the block’s name, so this breaks the link between these templates and their block.
Fixing this problem currently requires a hook in the THEMENAME.theme
file and should have the following contents:
/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function THEMENAME_theme_suggestions_block_alter(&$suggestions, $variables) {
+ // Load theme suggestions for blocks from parent theme.
+ // https://www.drupal.org/project/wxt/issues/3310485#comment-14715969
+ for ($i = 0; $i < count($suggestions); $i++) {
+ if (str_contains($suggestions[$i], 'THEMENAME_')) {
+ $new_suggestions = [
+ str_replace('THEMENAME_', '', $suggestions[$i]),
+ str_replace('THEMENAME_', 'wxt_bootstrap_', $suggestions[$i]),
+ ];
+ array_splice($suggestions, $i, 0, $new_suggestions);
+ $i += 2;
+ }
+ }
+}
+
The following provides an example of how you can configure your sub theme to be installed as the default on a module install:
/**
+ * Implements hook_modules_installed().
+ */
+function MODULENAME_modules_installed($modules) {
+ if (in_array('wxt', $modules)) {
+ \Drupal::configFactory()
+ ->getEditable('system.theme')
+ ->set('default', 'THEMENAME')
+ ->set('admin', 'claro')
+ ->save(TRUE);
+ }
+ }
+}
+
The following provides an example of how you can configure wxt_library
to use your sub theme by creating a config/install/wxt_library.settings.yml
file with the following contents:
url:
+ visibility: 0
+ pages:
+ - 'admin*'
+ - 'imagebrowser*'
+ - 'img_assist*'
+ - 'imce*'
+ - 'node/add/*'
+ - 'node/*/edit'
+ - 'print/*'
+ - 'printpdf/*'
+ - 'system/ajax'
+ - 'system/ajax/*'
+theme:
+ visibility: 1
+ themes:
+ THEMENAME: THEMENAME
+ wxt_bootstrap: wxt_bootstrap
+minimized:
+ options: 1
+files:
+ types:
+ css: css
+ js: js
+wxt:
+ theme: theme-gcweb
+
The Drupal WxT distribution is following semantic versioning.
WxT typically makes a sprint release every four to six weeks. We will also use sprint releases to package new minor releases of Drupal Core with WxT as they become available.
In addition, we will also increment the major version number of WxT about once every four to six months.
Support for semantic versioning for extensions (modules, themes, etc) is still ongoing.
The three parts of our versioning system are MAJOR.FEATURE.SPRINT.
Given the following tag: 10.x-2.00:
10 | Major version of Drupal Core |
x | |
5 | Major version of WxT |
0 | Feature release of WxT. Also increments with minor core releases. |
0 | Sprint release between feature releases |
Note: Due to the constraints of drupal.org, there is no separator between the FEATURE and SPRINT digits.
This is the multi-page printable view of this section. +Click here to print.
The goal of Drupal WxT since the 4.1.x line is to make the installation profile very minimal by default but providing additional extensions that can be enabled as desired.
What WxT offers is some light enhancements to Drupal Core, mainly around security and performance, and integration with the Web Experience Toolkit. By default, the distribution offers minimal functionality to allow full customizations by users. However a great deal of optional extensions are available that can provide additional functionality generally beneficial to Government departments.
Note: In the future we are looking into providing a list of community modules that are build to work with the distribution but are “out of tree”.
All of the optional modules are located in the wxt_ext
folder named after WxT Extend and can be enabled during the initial site installation by passing the following flag via the drush cli:
wxt_extension_configure_form.select_all='TRUE'
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
In order to provide a list of the optional enabled extensions during the installation that can be checked, all that any module has to do is provide a modulename.wxt_extension.yml
file in their root and they will be picked as installable during the profile install and also respond to the additional drush flag discussed above.
For more information on some of the history leading to this design:
2 minute read
The goal of Drupal WxT since the 4.1.x line is to make the installation profile very minimal by default but providing additional extensions that can be enabled as desired.
What WxT offers is some light enhancements to Drupal Core, mainly around security and performance, and integration with the Web Experience Toolkit. By default, the distribution offers minimal functionality to allow full customizations by users. However a great deal of optional extensions are available that can provide additional functionality generally beneficial to Government departments.
Note: In the future we are looking into providing a list of community modules that are build to work with the distribution but are “out of tree”.
All of the optional modules are located in the wxt_ext
folder named after WxT Extend and can be enabled during the initial site installation by passing the following flag via the drush cli:
wxt_extension_configure_form.select_all='TRUE'
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
In order to provide a list of the optional enabled extensions during the installation that can be checked, all that any module has to do is provide a modulename.wxt_extension.yml
file in their root and they will be picked as installable during the profile install and also respond to the additional drush flag discussed above.
For more information on some of the history leading to this design:
This is the multi-page printable view of this section. +Click here to print.
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
The following command is all you need to get started:
composer create-project drupalwxt/wxt-project:5.1.1 <site-name>
+
Note: For development you may also specify a branch using
drupalwxt/wxt-project:5.1.x-dev
.
You can see a working example of a fully generated Composer Project Template over at:
Where the following is the command that was used for the initial generation:
composer create-project drupalwxt/wxt-project:5.1.1 site-wxt
+
Note: Remember to keep the
composer.json
andcomposer.lock
files that exist abovedocroot
in source control as they are controlling your dependencies.
List of common commands are as follows:
Task | Composer |
---|---|
Installing a contrib project (latest version) | composer require drupal/PROJECT |
Installing a contrib project (specific version) | composer require drupal/PROJECT:1.0.0-beta5 |
Updating all projects including Drupal Core | composer update |
Updating a single contrib project | composer update drupal/PROJECT_NAME |
Updating Drupal Core | composer update drupal/core |
Note: Composer is a dependency manager and helps us keep track of what code and at what version our application relies on so that it always get installed the right way on every copy of that application.
A specific version can be specified from the cli:
composer require drupal/<modulename>:<version>
+
However please note if you specify a branch, such as 1.x you must add -dev
to the end of the version:
composer require drupal/token:1.x-dev
+
Taking a look at the .gitignore
file, you will discover that certain directories, including all those directories containing contributed projects, are excluded from source control which is by design.
Note: Unlike Drush in a Composer derived project you should never commit your install dependencies to source control.
Composer will create composer.lock
file, which is a list of dependencies that were installed, and in which versions.
Note: In general you should always commit your
composer.lock
file to source control so that others via a quickcomposer install
can have everything installed along with the correct versions specified in thecomposer.lock
file.
Please don’t add drupal/core
to your project’s composer.json since WxT manages Drupal Core for you along with the series of patches on top of it.
For example:
drupalwxt/wxt:~5.2.0
will require Drupal Core 10.2.xdrupalwxt/wxt:~5.1.0
will require Drupal Core 10.1.xWhen you need to update Drupal Core as an example from 10.1.x to 10.2.x, all you would do is change your requirement for drupalwxt/wxt
in your composer.json
file:
composer require --no-update drupalwxt/wxt:~5.2.0
+composer update
+
WxT version | Drupal Core version | Drush version | PHP version |
---|---|---|---|
5.2.x | 10.2.x | >=12.4 | 8.2 |
5.1.x | 10.1.x | >=12.1 | 8.1 |
3 minute read
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
The following command is all you need to get started:
composer create-project drupalwxt/wxt-project:5.1.1 <site-name>
+
Note: For development you may also specify a branch using
drupalwxt/wxt-project:5.1.x-dev
.
You can see a working example of a fully generated Composer Project Template over at:
Where the following is the command that was used for the initial generation:
composer create-project drupalwxt/wxt-project:5.1.1 site-wxt
+
Note: Remember to keep the
composer.json
andcomposer.lock
files that exist abovedocroot
in source control as they are controlling your dependencies.
List of common commands are as follows:
Task | Composer |
---|---|
Installing a contrib project (latest version) | composer require drupal/PROJECT |
Installing a contrib project (specific version) | composer require drupal/PROJECT:1.0.0-beta5 |
Updating all projects including Drupal Core | composer update |
Updating a single contrib project | composer update drupal/PROJECT_NAME |
Updating Drupal Core | composer update drupal/core |
Note: Composer is a dependency manager and helps us keep track of what code and at what version our application relies on so that it always get installed the right way on every copy of that application.
A specific version can be specified from the cli:
composer require drupal/<modulename>:<version>
+
However please note if you specify a branch, such as 1.x you must add -dev
to the end of the version:
composer require drupal/token:1.x-dev
+
Taking a look at the .gitignore
file, you will discover that certain directories, including all those directories containing contributed projects, are excluded from source control which is by design.
Note: Unlike Drush in a Composer derived project you should never commit your install dependencies to source control.
Composer will create composer.lock
file, which is a list of dependencies that were installed, and in which versions.
Note: In general you should always commit your
composer.lock
file to source control so that others via a quickcomposer install
can have everything installed along with the correct versions specified in thecomposer.lock
file.
Please don’t add drupal/core
to your project’s composer.json since WxT manages Drupal Core for you along with the series of patches on top of it.
For example:
drupalwxt/wxt:~5.2.0
will require Drupal Core 10.2.xdrupalwxt/wxt:~5.1.0
will require Drupal Core 10.1.xWhen you need to update Drupal Core as an example from 10.1.x to 10.2.x, all you would do is change your requirement for drupalwxt/wxt
in your composer.json
file:
composer require --no-update drupalwxt/wxt:~5.2.0
+composer update
+
WxT version | Drupal Core version | Drush version | PHP version |
---|---|---|---|
5.2.x | 10.2.x | >=12.4 | 8.2 |
5.1.x | 10.1.x | >=12.1 | 8.1 |
This is the multi-page printable view of this section. +Click here to print.
Drupal WxT thanks to the work done by the Acquia Team is able to use advanced +configuration management strategies.
At the moment this remains an opt-in process and you will have to add the
+following modules to your composer.json
before you add the code snippet
+below to your settings.php
file.
Once enabled all default configuration will be stored in /sites/default/files/config/default/
+and then depending on your environment additionally configuration splits can
+be leveraged depending on your SDLC
.
/**
+ * Configuration Split for Configuration Management
+ *
+ * WxT is following the best practices given by Acquia for configuration
+ * management. The "default" configuration directory should be shared between
+ * all multi-sites, and each multisite will override this selectively using
+ * configuration splits.
+ *
+ * To disable this functionality simply set the following parameters:
+ * $wxt_override_config_dirs = FALSE;
+ * $settings['config_sync_directory'] = $dir . "/config/$site_dir";
+ *
+ * See https://github.com/acquia/blt/blob/12.x/settings/config.settings.php
+ * for more information.
+ */
+
+use Drupal\wxt\Robo\Common\EnvironmentDetector;
+
+if (!isset($wxt_override_config_dirs)) {
+ $wxt_override_config_dirs = TRUE;
+}
+if ($wxt_override_config_dirs) {
+ $config_directories['sync'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+ $settings['config_sync_directory'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+}
+$split_filename_prefix = 'config_split.config_split';
+if (isset($config_directories['sync'])) {
+ $split_filepath_prefix = $config_directories['sync'] . '/' . $split_filename_prefix;
+}
+else {
+ $split_filepath_prefix = $settings['config_sync_directory'] . '/' . $split_filename_prefix;
+}
+
+/**
+ * Set environment splits.
+ */
+$split_envs = [
+ 'local',
+ 'dev',
+ 'test',
+ 'qa',
+ 'prod',
+ 'ci',
+];
+foreach ($split_envs as $split_env) {
+ $config["$split_filename_prefix.$split_env"]['status'] = FALSE;
+}
+if (!isset($split)) {
+ $split = 'none';
+ if (EnvironmentDetector::isLocalEnv()) {
+ $split = 'local';
+ }
+ if (EnvironmentDetector::isCiEnv()) {
+ $split = 'ci';
+ }
+ if (EnvironmentDetector::isDevEnv()) {
+ $split = 'dev';
+ }
+ elseif (EnvironmentDetector::isTestEnv()) {
+ $split = 'test';
+ }
+ elseif (EnvironmentDetector::isQaEnv()) {
+ $split = 'qa';
+ }
+ elseif (EnvironmentDetector::isProdEnv()) {
+ $split = 'prod';
+ }
+}
+if ($split != 'none') {
+ $config["$split_filename_prefix.$split"]['status'] = TRUE;
+}
+
+/**
+ * Set multisite split.
+ */
+// $config["$split_filename_prefix.SITENAME"]['status'] = TRUE;
+
2 minute read
Drupal WxT thanks to the work done by the Acquia Team is able to use advanced +configuration management strategies.
At the moment this remains an opt-in process and you will have to add the
+following modules to your composer.json
before you add the code snippet
+below to your settings.php
file.
Once enabled all default configuration will be stored in /sites/default/files/config/default/
+and then depending on your environment additionally configuration splits can
+be leveraged depending on your SDLC
.
/**
+ * Configuration Split for Configuration Management
+ *
+ * WxT is following the best practices given by Acquia for configuration
+ * management. The "default" configuration directory should be shared between
+ * all multi-sites, and each multisite will override this selectively using
+ * configuration splits.
+ *
+ * To disable this functionality simply set the following parameters:
+ * $wxt_override_config_dirs = FALSE;
+ * $settings['config_sync_directory'] = $dir . "/config/$site_dir";
+ *
+ * See https://github.com/acquia/blt/blob/12.x/settings/config.settings.php
+ * for more information.
+ */
+
+use Drupal\wxt\Robo\Common\EnvironmentDetector;
+
+if (!isset($wxt_override_config_dirs)) {
+ $wxt_override_config_dirs = TRUE;
+}
+if ($wxt_override_config_dirs) {
+ $config_directories['sync'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+ $settings['config_sync_directory'] = $repo_root . "/var/www/html/sites/default/files/config/default";
+}
+$split_filename_prefix = 'config_split.config_split';
+if (isset($config_directories['sync'])) {
+ $split_filepath_prefix = $config_directories['sync'] . '/' . $split_filename_prefix;
+}
+else {
+ $split_filepath_prefix = $settings['config_sync_directory'] . '/' . $split_filename_prefix;
+}
+
+/**
+ * Set environment splits.
+ */
+$split_envs = [
+ 'local',
+ 'dev',
+ 'test',
+ 'qa',
+ 'prod',
+ 'ci',
+];
+foreach ($split_envs as $split_env) {
+ $config["$split_filename_prefix.$split_env"]['status'] = FALSE;
+}
+if (!isset($split)) {
+ $split = 'none';
+ if (EnvironmentDetector::isLocalEnv()) {
+ $split = 'local';
+ }
+ if (EnvironmentDetector::isCiEnv()) {
+ $split = 'ci';
+ }
+ if (EnvironmentDetector::isDevEnv()) {
+ $split = 'dev';
+ }
+ elseif (EnvironmentDetector::isTestEnv()) {
+ $split = 'test';
+ }
+ elseif (EnvironmentDetector::isQaEnv()) {
+ $split = 'qa';
+ }
+ elseif (EnvironmentDetector::isProdEnv()) {
+ $split = 'prod';
+ }
+}
+if ($split != 'none') {
+ $config["$split_filename_prefix.$split"]['status'] = TRUE;
+}
+
+/**
+ * Set multisite split.
+ */
+// $config["$split_filename_prefix.SITENAME"]['status'] = TRUE;
+
less than a minute
This section provides information for developers who wish to help collaborate and improve Drupal WxT.
This is the multi-page printable view of this section. +Click here to print.
Below are some recommended settings that improve the performance of Drupal WxT sites.
To properly configure PostgreSQL with Drupal you should ensure the following configuration is used.
Note: Some customizations might be necessary depending on your individual requirements.
postgresqlConfiguration:
+ listenAddresses: "'*'"
+ maxConnections: "200"
+ sharedBuffers: 512MB
+ workMem: 2048MB
+ effectiveCacheSize: 512MB
+ effectiveIoConcurrency: "100"
+ maintenanceWorkMem: 32MB
+ minWalSize: 512MB
+ maxWalSize: 512MB
+ walBuffers: 8048kB
+ byteaOutput: "'escape'"
+ hugePages: "off"
+ walLevel: "replica"
+ maxWalSenders: "0"
+ synchronousCommit: "on"
+ walKeepSegments: "130"
+ checkpointTimeout: "'15 min'"
+ checkpointCompletionTarget: "0.9"
+ walCompression: "on"
+ walWriterDelay: 200ms
+ walWriterFlushAfter: 1MB
+ bgwriterDelay: 200ms
+ bgwriterLruMaxpages: "100"
+ bgwriterLruMultiplier: "2.0"
+ bgwriterFlushAfter: "0"
+ maxWorkerProcesses: "8"
+ maxParallelWorkersPerGather: "4"
+ maxParallelWorkers: "4"
+
Note: The above is written in yaml syntax which will work for both Docker Compose and Kubernetes Helm Charts. For the
postgresql.conf
file itself without using these tools simply find the_
counterpart.
There is a known PostgreSQL performance issue that exists in Drupal and is related to leveraging queries with ILIKE
.
This issue is particularly noticeable in relation to the path_alias table.
There are patches being worked on to handle this in Drupal core but a very quick fix can be implemented leveraging pg_trgm.
There is a great blog article listed below which goes over this issue in more detail.
The instructions are a bit outdated so the updated syntax to enter in psql is given below:
CREATE EXTENSION pg_trgm;
+CREATE INDEX path_alias__alias_trgm_gist_idx ON path_alias USING gist (alias gist_trgm_ops);
+CREATE INDEX path_alias__path_trgm_gist_idx ON path_alias USING gist (path gist_trgm_ops);
+ANALYZE path_alias;
+
To properly configure Redis with Drupal you should ensure the following configuration is added to your settings.php
file.
Note: Some customizations might be necessary depending on your individual requirements.
if (extension_loaded('redis')) {
+ // Set Redis as the default backend for any cache bin not otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+ $settings['redis.connection']['interface'] = 'PhpRedis';
+ $settings['redis.connection']['scheme'] = 'http';
+ $settings['redis.connection']['host'] = 'localhost';
+ $settings['redis.connection']['port'] = '6379';
+ $settings['redis.connection']['password'] = getenv('REDIS_PASSWORD') ?: '';
+ $settings['redis.connection']['persistent'] = FALSE;
+
+ // Allow the services to work before the Redis module itself is enabled.
+ $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
+ $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
+
+ // Manually add the classloader path, this is required for the container cache bin definition below
+ // and allows to use it without the redis module being enabled.
+ $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
+
+ $settings['bootstrap_container_definition'] = [
+ 'parameters' => [],
+ 'services' => [
+ 'redis.factory' => [
+ 'class' => 'Drupal\redis\ClientFactory',
+ ],
+ 'cache.backend.redis' => [
+ 'class' => 'Drupal\redis\Cache\CacheBackendFactory',
+ 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
+ ],
+ 'cache.container' => [
+ 'class' => '\Drupal\redis\Cache\PhpRedis',
+ 'factory' => ['@cache.backend.redis', 'get'],
+ 'arguments' => ['container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
+ 'arguments' => ['@redis.factory'],
+ ],
+ 'serialization.phpserialize' => [
+ 'class' => 'Drupal\Component\Serialization\PhpSerialize',
+ ],
+ ],
+ ];
+
+ /** Optional prefix for cache entries */
+ $settings['cache_prefix'] = 'drupal_';
+
+ // Always set the fast backend for bootstrap, discover and config, otherwise
+ // this gets lost when redis is enabled.
+ $settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
+
+ // Use for all bins otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+
+ // Use for all queues unless otherwise specified for a specific queue.
+ $settings['queue_default'] = 'queue.redis';
+
+ // Or if you want to use reliable queue implementation.
+ // $settings['queue_default'] = 'queue.redis_reliable';
+
+ // Use this to only use Redis for a specific queue.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis';
+
+ // Use this to use reliable queue implementation.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable';
+}
+
To properly configure Varnish with Drupal you should ensure the following configuration is your default.vcl
file.
Note: Some customizations might be necessary depending on your individual requirements.
vcl 4.0;
+
+import std;
+import directors;
+
+backend nginx {
+ .host = "hostname-nginx";
+ .host_header = "hostname-nginx";
+ .port = "80";
+}
+
+sub vcl_init {
+ new backends = directors.round_robin();
+ backends.add_backend(nginx);
+}
+
+sub vcl_recv {
+ set req.http.X-Forwarded-Host = req.http.Host;
+ if (!req.http.X-Forwarded-Proto) {
+ set req.http.X-Forwarded-Proto = "http";
+ }
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Always cache certain file types
+ # Remove cookies that Drupal doesn't care about
+ if (req.url ~ "(?i)\.(asc|dat|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
+ unset req.http.Cookie;
+ } else if (req.http.Cookie) {
+ set req.http.Cookie = ";" + req.http.Cookie;
+ set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
+ set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
+ if (req.http.Cookie == "") {
+ unset req.http.Cookie;
+ } else {
+ return (pass);
+ }
+ }
+ # If POST, PUT or DELETE, then don't cache
+ if (req.method == "POST" || req.method == "PUT" || req.method == "DELETE") {
+ return (pass);
+ }
+ # Happens before we check if we have this in cache already.
+ #
+ # Typically you clean up the request here, removing cookies you don't need,
+ # rewriting the request, etc.
+ return (hash);
+ #return (pass);
+}
+
+sub vcl_backend_fetch {
+ # NEW
+ set bereq.http.Host = "hostname-nginx";
+
+ # Don't add 127.0.0.1 to X-Forwarded-For
+ set bereq.http.X-Forwarded-For = regsub(bereq.http.X-Forwarded-For, "(, )?127\.0\.0\.1$", "");
+}
+
+sub vcl_backend_response {
+ if (beresp.http.Location) {
+ set beresp.http.Location = regsub(
+ beresp.http.Location,
+ "^https?://[^/]+/",
+ bereq.http.X-Forwarded-Proto + "://" + bereq.http.X-Forwarded-Host + "/"
+ );
+ }
+ # Only cache select response codes
+ if (beresp.status == 200 || beresp.status == 203 || beresp.status == 204 || beresp.status == 206 || beresp.status == 300 || beresp.status == 301 || beresp.status == 404 || beresp.status == 405 || beresp.status == 410 || beresp.status == 414 || beresp.status == 501) {
+ # Cache for 5 minutes
+ set beresp.ttl = 5m;
+ set beresp.grace = 12h;
+ set beresp.keep = 24h;
+ } else {
+ set beresp.ttl = 0s;
+ }
+}
+
+sub vcl_deliver {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Comment these for easier Drupal cache tag debugging in development.
+ unset resp.http.Cache-Tags;
+ unset resp.http.X-Drupal-Cache-Contexts;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net data:; font-src 'self' *.example.ca https://fonts.gstatic.com";
+
+ # Add CORS Headers
+ # if (req.http.Origin ~ "(?i)\.example\.ca$") {
+ # if (req.url ~ "\.(ttd|woff|woff2)(\?.*)?$") {
+ # set resp.http.Access-Control-Allow-Origin = "*";
+ # set resp.http.Access-Control-Allow-Methods = "GET";
+ # }
+ # }
+
+ # Add X-Frame-Options
+ if (req.url ~ "^/livechat" || req.url ~ "^/(en/|fr/)?entity-browser/") {
+ set resp.http.X-Frame-Options = "SAMEORIGIN";
+ } else {
+ set resp.http.X-Frame-Options = "DENY";
+ }
+
+ set resp.http.X-Content-Type-Options = "nosniff";
+ set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # Happens when we have all the pieces we need, and are about to send the
+ # response to the client.
+ #
+ # You can do accounting or modifying the final object here.
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+ # Handle errors
+ if ( (resp.status >= 500 && resp.status <= 599)
+ || resp.status == 400
+ || resp.status == 401
+ || resp.status == 403
+ || resp.status == 404) {
+ return (synth(resp.status));
+ }
+}
+
+sub vcl_synth {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca data:;";
+ # set resp.http.X-Content-Type-Options = "nosniff";
+ # set resp.http.X-Frame-Options = "DENY";
+ # set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # if (resp.status >= 500 && resp.status <= 599) {
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+ # return (deliver);
+ # } elseif (resp.status == 400) { # 400 - Bad Request
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/400.html"));
+ # return (deliver);
+ # } elseif (resp.status == 401) { # 401 - Unauthorized
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/401.html"));
+ # return (deliver);
+ # } elseif (resp.status == 403) { # 403 - Forbidden
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/403.html"));
+ # return (deliver);
+ # } elseif (resp.status == 404) { # 404 - Not Found
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/404.html"));
+ # return (deliver);
+ # } else
+ if (resp.status == 700) { # Respond to healthcheck
+ set resp.status = 200;
+ set resp.http.Content-Type = "text/plain";
+ synthetic ( {"OK"} );
+ return (deliver);
+ }
+}
+
+##
+# ERROR HANDLING
+##
+# sub vcl_backend_error {
+# set beresp.http.Content-Type = "text/html; charset=utf-8";
+# synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+# return (deliver);
+# }
+
less than a minute
Below are some recommended settings that improve the performance of Drupal WxT sites.
This is the multi-page printable view of this section. +Click here to print.
To properly configure PostgreSQL with Drupal you should ensure the following configuration is used.
Note: Some customizations might be necessary depending on your individual requirements.
postgresqlConfiguration:
+ listenAddresses: "'*'"
+ maxConnections: "200"
+ sharedBuffers: 512MB
+ workMem: 2048MB
+ effectiveCacheSize: 512MB
+ effectiveIoConcurrency: "100"
+ maintenanceWorkMem: 32MB
+ minWalSize: 512MB
+ maxWalSize: 512MB
+ walBuffers: 8048kB
+ byteaOutput: "'escape'"
+ hugePages: "off"
+ walLevel: "replica"
+ maxWalSenders: "0"
+ synchronousCommit: "on"
+ walKeepSegments: "130"
+ checkpointTimeout: "'15 min'"
+ checkpointCompletionTarget: "0.9"
+ walCompression: "on"
+ walWriterDelay: 200ms
+ walWriterFlushAfter: 1MB
+ bgwriterDelay: 200ms
+ bgwriterLruMaxpages: "100"
+ bgwriterLruMultiplier: "2.0"
+ bgwriterFlushAfter: "0"
+ maxWorkerProcesses: "8"
+ maxParallelWorkersPerGather: "4"
+ maxParallelWorkers: "4"
+
Note: The above is written in yaml syntax which will work for both Docker Compose and Kubernetes Helm Charts. For the
postgresql.conf
file itself without using these tools simply find the_
counterpart.
There is a known PostgreSQL performance issue that exists in Drupal and is related to leveraging queries with ILIKE
.
This issue is particularly noticeable in relation to the path_alias table.
There are patches being worked on to handle this in Drupal core but a very quick fix can be implemented leveraging pg_trgm.
There is a great blog article listed below which goes over this issue in more detail.
The instructions are a bit outdated so the updated syntax to enter in psql is given below:
CREATE EXTENSION pg_trgm;
+CREATE INDEX path_alias__alias_trgm_gist_idx ON path_alias USING gist (alias gist_trgm_ops);
+CREATE INDEX path_alias__path_trgm_gist_idx ON path_alias USING gist (path gist_trgm_ops);
+ANALYZE path_alias;
+
2 minute read
To properly configure PostgreSQL with Drupal you should ensure the following configuration is used.
Note: Some customizations might be necessary depending on your individual requirements.
postgresqlConfiguration:
+ listenAddresses: "'*'"
+ maxConnections: "200"
+ sharedBuffers: 512MB
+ workMem: 2048MB
+ effectiveCacheSize: 512MB
+ effectiveIoConcurrency: "100"
+ maintenanceWorkMem: 32MB
+ minWalSize: 512MB
+ maxWalSize: 512MB
+ walBuffers: 8048kB
+ byteaOutput: "'escape'"
+ hugePages: "off"
+ walLevel: "replica"
+ maxWalSenders: "0"
+ synchronousCommit: "on"
+ walKeepSegments: "130"
+ checkpointTimeout: "'15 min'"
+ checkpointCompletionTarget: "0.9"
+ walCompression: "on"
+ walWriterDelay: 200ms
+ walWriterFlushAfter: 1MB
+ bgwriterDelay: 200ms
+ bgwriterLruMaxpages: "100"
+ bgwriterLruMultiplier: "2.0"
+ bgwriterFlushAfter: "0"
+ maxWorkerProcesses: "8"
+ maxParallelWorkersPerGather: "4"
+ maxParallelWorkers: "4"
+
Note: The above is written in yaml syntax which will work for both Docker Compose and Kubernetes Helm Charts. For the
postgresql.conf
file itself without using these tools simply find the_
counterpart.
There is a known PostgreSQL performance issue that exists in Drupal and is related to leveraging queries with ILIKE
.
This issue is particularly noticeable in relation to the path_alias table.
There are patches being worked on to handle this in Drupal core but a very quick fix can be implemented leveraging pg_trgm.
There is a great blog article listed below which goes over this issue in more detail.
The instructions are a bit outdated so the updated syntax to enter in psql is given below:
CREATE EXTENSION pg_trgm;
+CREATE INDEX path_alias__alias_trgm_gist_idx ON path_alias USING gist (alias gist_trgm_ops);
+CREATE INDEX path_alias__path_trgm_gist_idx ON path_alias USING gist (path gist_trgm_ops);
+ANALYZE path_alias;
+
This is the multi-page printable view of this section. +Click here to print.
To properly configure Redis with Drupal you should ensure the following configuration is added to your settings.php
file.
Note: Some customizations might be necessary depending on your individual requirements.
if (extension_loaded('redis')) {
+ // Set Redis as the default backend for any cache bin not otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+ $settings['redis.connection']['interface'] = 'PhpRedis';
+ $settings['redis.connection']['scheme'] = 'http';
+ $settings['redis.connection']['host'] = 'localhost';
+ $settings['redis.connection']['port'] = '6379';
+ $settings['redis.connection']['password'] = getenv('REDIS_PASSWORD') ?: '';
+ $settings['redis.connection']['persistent'] = FALSE;
+
+ // Allow the services to work before the Redis module itself is enabled.
+ $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
+ $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
+
+ // Manually add the classloader path, this is required for the container cache bin definition below
+ // and allows to use it without the redis module being enabled.
+ $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
+
+ $settings['bootstrap_container_definition'] = [
+ 'parameters' => [],
+ 'services' => [
+ 'redis.factory' => [
+ 'class' => 'Drupal\redis\ClientFactory',
+ ],
+ 'cache.backend.redis' => [
+ 'class' => 'Drupal\redis\Cache\CacheBackendFactory',
+ 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
+ ],
+ 'cache.container' => [
+ 'class' => '\Drupal\redis\Cache\PhpRedis',
+ 'factory' => ['@cache.backend.redis', 'get'],
+ 'arguments' => ['container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
+ 'arguments' => ['@redis.factory'],
+ ],
+ 'serialization.phpserialize' => [
+ 'class' => 'Drupal\Component\Serialization\PhpSerialize',
+ ],
+ ],
+ ];
+
+ /** Optional prefix for cache entries */
+ $settings['cache_prefix'] = 'drupal_';
+
+ // Always set the fast backend for bootstrap, discover and config, otherwise
+ // this gets lost when redis is enabled.
+ $settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
+
+ // Use for all bins otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+
+ // Use for all queues unless otherwise specified for a specific queue.
+ $settings['queue_default'] = 'queue.redis';
+
+ // Or if you want to use reliable queue implementation.
+ // $settings['queue_default'] = 'queue.redis_reliable';
+
+ // Use this to only use Redis for a specific queue.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis';
+
+ // Use this to use reliable queue implementation.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable';
+}
+
2 minute read
To properly configure Redis with Drupal you should ensure the following configuration is added to your settings.php
file.
Note: Some customizations might be necessary depending on your individual requirements.
if (extension_loaded('redis')) {
+ // Set Redis as the default backend for any cache bin not otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+ $settings['redis.connection']['interface'] = 'PhpRedis';
+ $settings['redis.connection']['scheme'] = 'http';
+ $settings['redis.connection']['host'] = 'localhost';
+ $settings['redis.connection']['port'] = '6379';
+ $settings['redis.connection']['password'] = getenv('REDIS_PASSWORD') ?: '';
+ $settings['redis.connection']['persistent'] = FALSE;
+
+ // Allow the services to work before the Redis module itself is enabled.
+ $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
+ $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
+
+ // Manually add the classloader path, this is required for the container cache bin definition below
+ // and allows to use it without the redis module being enabled.
+ $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
+
+ $settings['bootstrap_container_definition'] = [
+ 'parameters' => [],
+ 'services' => [
+ 'redis.factory' => [
+ 'class' => 'Drupal\redis\ClientFactory',
+ ],
+ 'cache.backend.redis' => [
+ 'class' => 'Drupal\redis\Cache\CacheBackendFactory',
+ 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
+ ],
+ 'cache.container' => [
+ 'class' => '\Drupal\redis\Cache\PhpRedis',
+ 'factory' => ['@cache.backend.redis', 'get'],
+ 'arguments' => ['container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
+ 'arguments' => ['@redis.factory'],
+ ],
+ 'serialization.phpserialize' => [
+ 'class' => 'Drupal\Component\Serialization\PhpSerialize',
+ ],
+ ],
+ ];
+
+ /** Optional prefix for cache entries */
+ $settings['cache_prefix'] = 'drupal_';
+
+ // Always set the fast backend for bootstrap, discover and config, otherwise
+ // this gets lost when redis is enabled.
+ $settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
+ $settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
+
+ // Use for all bins otherwise specified.
+ $settings['cache']['default'] = 'cache.backend.redis';
+
+ // Use for all queues unless otherwise specified for a specific queue.
+ $settings['queue_default'] = 'queue.redis';
+
+ // Or if you want to use reliable queue implementation.
+ // $settings['queue_default'] = 'queue.redis_reliable';
+
+ // Use this to only use Redis for a specific queue.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis';
+
+ // Use this to use reliable queue implementation.
+ // $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable';
+}
+
This is the multi-page printable view of this section. +Click here to print.
To properly configure Varnish with Drupal you should ensure the following configuration is your default.vcl
file.
Note: Some customizations might be necessary depending on your individual requirements.
vcl 4.0;
+
+import std;
+import directors;
+
+backend nginx {
+ .host = "hostname-nginx";
+ .host_header = "hostname-nginx";
+ .port = "80";
+}
+
+sub vcl_init {
+ new backends = directors.round_robin();
+ backends.add_backend(nginx);
+}
+
+sub vcl_recv {
+ set req.http.X-Forwarded-Host = req.http.Host;
+ if (!req.http.X-Forwarded-Proto) {
+ set req.http.X-Forwarded-Proto = "http";
+ }
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Always cache certain file types
+ # Remove cookies that Drupal doesn't care about
+ if (req.url ~ "(?i)\.(asc|dat|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
+ unset req.http.Cookie;
+ } else if (req.http.Cookie) {
+ set req.http.Cookie = ";" + req.http.Cookie;
+ set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
+ set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
+ if (req.http.Cookie == "") {
+ unset req.http.Cookie;
+ } else {
+ return (pass);
+ }
+ }
+ # If POST, PUT or DELETE, then don't cache
+ if (req.method == "POST" || req.method == "PUT" || req.method == "DELETE") {
+ return (pass);
+ }
+ # Happens before we check if we have this in cache already.
+ #
+ # Typically you clean up the request here, removing cookies you don't need,
+ # rewriting the request, etc.
+ return (hash);
+ #return (pass);
+}
+
+sub vcl_backend_fetch {
+ # NEW
+ set bereq.http.Host = "hostname-nginx";
+
+ # Don't add 127.0.0.1 to X-Forwarded-For
+ set bereq.http.X-Forwarded-For = regsub(bereq.http.X-Forwarded-For, "(, )?127\.0\.0\.1$", "");
+}
+
+sub vcl_backend_response {
+ if (beresp.http.Location) {
+ set beresp.http.Location = regsub(
+ beresp.http.Location,
+ "^https?://[^/]+/",
+ bereq.http.X-Forwarded-Proto + "://" + bereq.http.X-Forwarded-Host + "/"
+ );
+ }
+ # Only cache select response codes
+ if (beresp.status == 200 || beresp.status == 203 || beresp.status == 204 || beresp.status == 206 || beresp.status == 300 || beresp.status == 301 || beresp.status == 404 || beresp.status == 405 || beresp.status == 410 || beresp.status == 414 || beresp.status == 501) {
+ # Cache for 5 minutes
+ set beresp.ttl = 5m;
+ set beresp.grace = 12h;
+ set beresp.keep = 24h;
+ } else {
+ set beresp.ttl = 0s;
+ }
+}
+
+sub vcl_deliver {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Comment these for easier Drupal cache tag debugging in development.
+ unset resp.http.Cache-Tags;
+ unset resp.http.X-Drupal-Cache-Contexts;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net data:; font-src 'self' *.example.ca https://fonts.gstatic.com";
+
+ # Add CORS Headers
+ # if (req.http.Origin ~ "(?i)\.example\.ca$") {
+ # if (req.url ~ "\.(ttd|woff|woff2)(\?.*)?$") {
+ # set resp.http.Access-Control-Allow-Origin = "*";
+ # set resp.http.Access-Control-Allow-Methods = "GET";
+ # }
+ # }
+
+ # Add X-Frame-Options
+ if (req.url ~ "^/livechat" || req.url ~ "^/(en/|fr/)?entity-browser/") {
+ set resp.http.X-Frame-Options = "SAMEORIGIN";
+ } else {
+ set resp.http.X-Frame-Options = "DENY";
+ }
+
+ set resp.http.X-Content-Type-Options = "nosniff";
+ set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # Happens when we have all the pieces we need, and are about to send the
+ # response to the client.
+ #
+ # You can do accounting or modifying the final object here.
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+ # Handle errors
+ if ( (resp.status >= 500 && resp.status <= 599)
+ || resp.status == 400
+ || resp.status == 401
+ || resp.status == 403
+ || resp.status == 404) {
+ return (synth(resp.status));
+ }
+}
+
+sub vcl_synth {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca data:;";
+ # set resp.http.X-Content-Type-Options = "nosniff";
+ # set resp.http.X-Frame-Options = "DENY";
+ # set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # if (resp.status >= 500 && resp.status <= 599) {
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+ # return (deliver);
+ # } elseif (resp.status == 400) { # 400 - Bad Request
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/400.html"));
+ # return (deliver);
+ # } elseif (resp.status == 401) { # 401 - Unauthorized
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/401.html"));
+ # return (deliver);
+ # } elseif (resp.status == 403) { # 403 - Forbidden
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/403.html"));
+ # return (deliver);
+ # } elseif (resp.status == 404) { # 404 - Not Found
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/404.html"));
+ # return (deliver);
+ # } else
+ if (resp.status == 700) { # Respond to healthcheck
+ set resp.status = 200;
+ set resp.http.Content-Type = "text/plain";
+ synthetic ( {"OK"} );
+ return (deliver);
+ }
+}
+
+##
+# ERROR HANDLING
+##
+# sub vcl_backend_error {
+# set beresp.http.Content-Type = "text/html; charset=utf-8";
+# synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+# return (deliver);
+# }
+
4 minute read
To properly configure Varnish with Drupal you should ensure the following configuration is your default.vcl
file.
Note: Some customizations might be necessary depending on your individual requirements.
vcl 4.0;
+
+import std;
+import directors;
+
+backend nginx {
+ .host = "hostname-nginx";
+ .host_header = "hostname-nginx";
+ .port = "80";
+}
+
+sub vcl_init {
+ new backends = directors.round_robin();
+ backends.add_backend(nginx);
+}
+
+sub vcl_recv {
+ set req.http.X-Forwarded-Host = req.http.Host;
+ if (!req.http.X-Forwarded-Proto) {
+ set req.http.X-Forwarded-Proto = "http";
+ }
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Answer healthcheck
+ if (req.url == "/_healthcheck" || req.url == "/healthcheck.txt") {
+ return (synth(700, "HEALTHCHECK"));
+ }
+ set req.backend_hint = backends.backend();
+
+ # Always cache certain file types
+ # Remove cookies that Drupal doesn't care about
+ if (req.url ~ "(?i)\.(asc|dat|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
+ unset req.http.Cookie;
+ } else if (req.http.Cookie) {
+ set req.http.Cookie = ";" + req.http.Cookie;
+ set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
+ set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
+ set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
+ if (req.http.Cookie == "") {
+ unset req.http.Cookie;
+ } else {
+ return (pass);
+ }
+ }
+ # If POST, PUT or DELETE, then don't cache
+ if (req.method == "POST" || req.method == "PUT" || req.method == "DELETE") {
+ return (pass);
+ }
+ # Happens before we check if we have this in cache already.
+ #
+ # Typically you clean up the request here, removing cookies you don't need,
+ # rewriting the request, etc.
+ return (hash);
+ #return (pass);
+}
+
+sub vcl_backend_fetch {
+ # NEW
+ set bereq.http.Host = "hostname-nginx";
+
+ # Don't add 127.0.0.1 to X-Forwarded-For
+ set bereq.http.X-Forwarded-For = regsub(bereq.http.X-Forwarded-For, "(, )?127\.0\.0\.1$", "");
+}
+
+sub vcl_backend_response {
+ if (beresp.http.Location) {
+ set beresp.http.Location = regsub(
+ beresp.http.Location,
+ "^https?://[^/]+/",
+ bereq.http.X-Forwarded-Proto + "://" + bereq.http.X-Forwarded-Host + "/"
+ );
+ }
+ # Only cache select response codes
+ if (beresp.status == 200 || beresp.status == 203 || beresp.status == 204 || beresp.status == 206 || beresp.status == 300 || beresp.status == 301 || beresp.status == 404 || beresp.status == 405 || beresp.status == 410 || beresp.status == 414 || beresp.status == 501) {
+ # Cache for 5 minutes
+ set beresp.ttl = 5m;
+ set beresp.grace = 12h;
+ set beresp.keep = 24h;
+ } else {
+ set beresp.ttl = 0s;
+ }
+}
+
+sub vcl_deliver {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Comment these for easier Drupal cache tag debugging in development.
+ unset resp.http.Cache-Tags;
+ unset resp.http.X-Drupal-Cache-Contexts;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net data:; font-src 'self' *.example.ca https://fonts.gstatic.com";
+
+ # Add CORS Headers
+ # if (req.http.Origin ~ "(?i)\.example\.ca$") {
+ # if (req.url ~ "\.(ttd|woff|woff2)(\?.*)?$") {
+ # set resp.http.Access-Control-Allow-Origin = "*";
+ # set resp.http.Access-Control-Allow-Methods = "GET";
+ # }
+ # }
+
+ # Add X-Frame-Options
+ if (req.url ~ "^/livechat" || req.url ~ "^/(en/|fr/)?entity-browser/") {
+ set resp.http.X-Frame-Options = "SAMEORIGIN";
+ } else {
+ set resp.http.X-Frame-Options = "DENY";
+ }
+
+ set resp.http.X-Content-Type-Options = "nosniff";
+ set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # Happens when we have all the pieces we need, and are about to send the
+ # response to the client.
+ #
+ # You can do accounting or modifying the final object here.
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+ # Handle errors
+ if ( (resp.status >= 500 && resp.status <= 599)
+ || resp.status == 400
+ || resp.status == 401
+ || resp.status == 403
+ || resp.status == 404) {
+ return (synth(resp.status));
+ }
+}
+
+sub vcl_synth {
+ # Remove identifying information
+ unset resp.http.Server;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+ unset resp.http.Via;
+
+ # Add Content-Security-Policy
+ # set resp.http.Content-Security-Policy = "default-src 'self' *.example.ca; style-src 'self' 'unsafe-inline' *.example.ca; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.example.ca *.adobedtm.com use.fontawesome.com blob:; connect-src 'self' *.example.ca *.omtrdc.net *.demdex.net *.everesttech.net; img-src 'self' *.example.ca data:;";
+ # set resp.http.X-Content-Type-Options = "nosniff";
+ # set resp.http.X-Frame-Options = "DENY";
+ # set resp.http.X-XSS-Protection = "1; mode=block";
+
+ # if (resp.status >= 500 && resp.status <= 599) {
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+ # return (deliver);
+ # } elseif (resp.status == 400) { # 400 - Bad Request
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/400.html"));
+ # return (deliver);
+ # } elseif (resp.status == 401) { # 401 - Unauthorized
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/401.html"));
+ # return (deliver);
+ # } elseif (resp.status == 403) { # 403 - Forbidden
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/403.html"));
+ # return (deliver);
+ # } elseif (resp.status == 404) { # 404 - Not Found
+ # set resp.http.Content-Type = "text/html; charset=utf-8";
+ # synthetic(std.fileread("/data/configuration/varnish/errors/404.html"));
+ # return (deliver);
+ # } else
+ if (resp.status == 700) { # Respond to healthcheck
+ set resp.status = 200;
+ set resp.http.Content-Type = "text/plain";
+ synthetic ( {"OK"} );
+ return (deliver);
+ }
+}
+
+##
+# ERROR HANDLING
+##
+# sub vcl_backend_error {
+# set beresp.http.Content-Type = "text/html; charset=utf-8";
+# synthetic(std.fileread("/data/configuration/varnish/errors/503.html"));
+# return (deliver);
+# }
+
This is the multi-page printable view of this section. +Click here to print.
WxT releases are numbered using a form of semantic versioning. More information can be found in our Versioning page.
MAJOR.FEATURE.SPRINT
In general, when preparing a release: increment the FEATURE when Drupal Core has a major release (ie. 9.5.x to 10.0.x) otherwise simply increment the SPRINT number.
Create an issue in the Drupal WxT project on GitHub for release tracking, title it Release x.x.x
(where x.x.x is the incremented version number).
This issue should contain the following checklist as well as any other related steps or information regarding preparing the release.
See the [full release documentation](https://drupalwxt.github.io/docs/development/release-process/) for more detail.
+
+- [ ] All related projects (wxt_library and wxt_bootstrap) tagged and released on GitHub.com and Drupal.org
+- [ ] Version number selected
+- [ ] CHANGELOG.md updated
+- [ ] composer.json updated
+- [ ] Run version.sh for hook_updates and wxt contrib
+- [ ] CI build passes
+- [ ] Releases tagged and pushed to GitHub.com and Drupal.org
+- [ ] WxT released on Drupal.org (https://drupalwxt.github.io/docs/development/release-process/#release)
+- [ ] Add changelog information to published tag once CI is done
+
Ensure the changelog contains an entry for the release and is updated as issues and changes are resolved (in the next steps or when committing code / changes).
composer.json
filecomposer.json
fileAll projects must be released on drupal.org (and github).
Note: Changes to
composer.json
file (specifically dev dependencies and repositories) should be mentioned in the CHANGELOG.
Drupal.org does not currently support semantic versioning. Instead, the version number on drupal.org is 10.x-X.YZZ
, where:
X = MAJOR
Y = FEATURE
ZZ = SPRINT
(two digits - add leading zero for < 10)If the wxt dependent modules are updated, we need to reflect this in wxt composer.json
and the CHANGELOG.
git clone https://github.com/drupalwxt/wxt.git
composer.json
)git tag MAJOR.FEATURE.SPRINT
git push $GITHUB_REMOTE MAJOR.FEATURE.SPRINT
release notes
The builds on Drupal.org are incomplete as they don’t fully support Composer yet which is why we host a tarball on GitHub for those not using Composer.
<strong>CHANGELOG</strong>
+
+See the <a href="https://github.com/drupalwxt/wxt/blob/5.2.x/CHANGELOG.md">changelog.md</a> file.
+
3 minute read
WxT releases are numbered using a form of semantic versioning. More information can be found in our Versioning page.
MAJOR.FEATURE.SPRINT
In general, when preparing a release: increment the FEATURE when Drupal Core has a major release (ie. 9.5.x to 10.0.x) otherwise simply increment the SPRINT number.
Create an issue in the Drupal WxT project on GitHub for release tracking, title it Release x.x.x
(where x.x.x is the incremented version number).
This issue should contain the following checklist as well as any other related steps or information regarding preparing the release.
See the [full release documentation](https://drupalwxt.github.io/docs/development/release-process/) for more detail.
+
+- [ ] All related projects (wxt_library and wxt_bootstrap) tagged and released on GitHub.com and Drupal.org
+- [ ] Version number selected
+- [ ] CHANGELOG.md updated
+- [ ] composer.json updated
+- [ ] Run version.sh for hook_updates and wxt contrib
+- [ ] CI build passes
+- [ ] Releases tagged and pushed to GitHub.com and Drupal.org
+- [ ] WxT released on Drupal.org (https://drupalwxt.github.io/docs/development/release-process/#release)
+- [ ] Add changelog information to published tag once CI is done
+
Ensure the changelog contains an entry for the release and is updated as issues and changes are resolved (in the next steps or when committing code / changes).
composer.json
filecomposer.json
fileAll projects must be released on drupal.org (and github).
Note: Changes to
composer.json
file (specifically dev dependencies and repositories) should be mentioned in the CHANGELOG.
Drupal.org does not currently support semantic versioning. Instead, the version number on drupal.org is 10.x-X.YZZ
, where:
X = MAJOR
Y = FEATURE
ZZ = SPRINT
(two digits - add leading zero for < 10)If the wxt dependent modules are updated, we need to reflect this in wxt composer.json
and the CHANGELOG.
git clone https://github.com/drupalwxt/wxt.git
composer.json
)git tag MAJOR.FEATURE.SPRINT
git push $GITHUB_REMOTE MAJOR.FEATURE.SPRINT
release notes
The builds on Drupal.org are incomplete as they don’t fully support Composer yet which is why we host a tarball on GitHub for those not using Composer.
<strong>CHANGELOG</strong>
+
+See the <a href="https://github.com/drupalwxt/wxt/blob/5.2.x/CHANGELOG.md">changelog.md</a> file.
+
This is the multi-page printable view of this section. +Click here to print.
Largely when doing any theme related work with Drupal WxT this almost always should be done in a sub-theme.
For more on creating sub-themes please consult the official documentation:
To assist with sub-theme creation WxT Bootstrap provides an example starterkit that should be of benefit.
Note: Sub-themes are just like any other theme except they inherit the parent theme’s resources.
a) Replace every instance of THEMENAME
with your chosen machine name often of the pattern <prefix>_bootstrap
.
b) Enable your new sub-theme preferably via drush:
drush en `<prefix>_bootstrap`
+drush cc css-js
+
c) Point to your new sub theme for WxT Library to properly load assets under Themes Visibility on the /admin/config/wxt/wxt_library
page.
If the theme you are extending has custom block templates these won’t be immediately inherited because a sub-theme creates copies of all the blocks in the parent theme and renames them with the sub-theme’s name as a prefix. Twig block templates are derived from the block’s name, so this breaks the link between these templates and their block.
Fixing this problem currently requires a hook in the THEMENAME.theme
file and should have the following contents:
/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function THEMENAME_theme_suggestions_block_alter(&$suggestions, $variables) {
+ // Load theme suggestions for blocks from parent theme.
+ // https://www.drupal.org/project/wxt/issues/3310485#comment-14715969
+ for ($i = 0; $i < count($suggestions); $i++) {
+ if (str_contains($suggestions[$i], 'THEMENAME_')) {
+ $new_suggestions = [
+ str_replace('THEMENAME_', '', $suggestions[$i]),
+ str_replace('THEMENAME_', 'wxt_bootstrap_', $suggestions[$i]),
+ ];
+ array_splice($suggestions, $i, 0, $new_suggestions);
+ $i += 2;
+ }
+ }
+}
+
The following provides an example of how you can configure your sub theme to be installed as the default on a module install:
/**
+ * Implements hook_modules_installed().
+ */
+function MODULENAME_modules_installed($modules) {
+ if (in_array('wxt', $modules)) {
+ \Drupal::configFactory()
+ ->getEditable('system.theme')
+ ->set('default', 'THEMENAME')
+ ->set('admin', 'claro')
+ ->save(TRUE);
+ }
+ }
+}
+
The following provides an example of how you can configure wxt_library
to use your sub theme by creating a config/install/wxt_library.settings.yml
file with the following contents:
url:
+ visibility: 0
+ pages:
+ - 'admin*'
+ - 'imagebrowser*'
+ - 'img_assist*'
+ - 'imce*'
+ - 'node/add/*'
+ - 'node/*/edit'
+ - 'print/*'
+ - 'printpdf/*'
+ - 'system/ajax'
+ - 'system/ajax/*'
+theme:
+ visibility: 1
+ themes:
+ THEMENAME: THEMENAME
+ wxt_bootstrap: wxt_bootstrap
+minimized:
+ options: 1
+files:
+ types:
+ css: css
+ js: js
+wxt:
+ theme: theme-gcweb
+
2 minute read
Largely when doing any theme related work with Drupal WxT this almost always should be done in a sub-theme.
For more on creating sub-themes please consult the official documentation:
To assist with sub-theme creation WxT Bootstrap provides an example starterkit that should be of benefit.
Note: Sub-themes are just like any other theme except they inherit the parent theme’s resources.
a) Replace every instance of THEMENAME
with your chosen machine name often of the pattern <prefix>_bootstrap
.
b) Enable your new sub-theme preferably via drush:
drush en `<prefix>_bootstrap`
+drush cc css-js
+
c) Point to your new sub theme for WxT Library to properly load assets under Themes Visibility on the /admin/config/wxt/wxt_library
page.
If the theme you are extending has custom block templates these won’t be immediately inherited because a sub-theme creates copies of all the blocks in the parent theme and renames them with the sub-theme’s name as a prefix. Twig block templates are derived from the block’s name, so this breaks the link between these templates and their block.
Fixing this problem currently requires a hook in the THEMENAME.theme
file and should have the following contents:
/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function THEMENAME_theme_suggestions_block_alter(&$suggestions, $variables) {
+ // Load theme suggestions for blocks from parent theme.
+ // https://www.drupal.org/project/wxt/issues/3310485#comment-14715969
+ for ($i = 0; $i < count($suggestions); $i++) {
+ if (str_contains($suggestions[$i], 'THEMENAME_')) {
+ $new_suggestions = [
+ str_replace('THEMENAME_', '', $suggestions[$i]),
+ str_replace('THEMENAME_', 'wxt_bootstrap_', $suggestions[$i]),
+ ];
+ array_splice($suggestions, $i, 0, $new_suggestions);
+ $i += 2;
+ }
+ }
+}
+
The following provides an example of how you can configure your sub theme to be installed as the default on a module install:
/**
+ * Implements hook_modules_installed().
+ */
+function MODULENAME_modules_installed($modules) {
+ if (in_array('wxt', $modules)) {
+ \Drupal::configFactory()
+ ->getEditable('system.theme')
+ ->set('default', 'THEMENAME')
+ ->set('admin', 'claro')
+ ->save(TRUE);
+ }
+ }
+}
+
The following provides an example of how you can configure wxt_library
to use your sub theme by creating a config/install/wxt_library.settings.yml
file with the following contents:
url:
+ visibility: 0
+ pages:
+ - 'admin*'
+ - 'imagebrowser*'
+ - 'img_assist*'
+ - 'imce*'
+ - 'node/add/*'
+ - 'node/*/edit'
+ - 'print/*'
+ - 'printpdf/*'
+ - 'system/ajax'
+ - 'system/ajax/*'
+theme:
+ visibility: 1
+ themes:
+ THEMENAME: THEMENAME
+ wxt_bootstrap: wxt_bootstrap
+minimized:
+ options: 1
+files:
+ types:
+ css: css
+ js: js
+wxt:
+ theme: theme-gcweb
+
This is the multi-page printable view of this section. +Click here to print.
The Drupal WxT distribution is following semantic versioning.
WxT typically makes a sprint release every four to six weeks. We will also use sprint releases to package new minor releases of Drupal Core with WxT as they become available.
In addition, we will also increment the major version number of WxT about once every four to six months.
Support for semantic versioning for extensions (modules, themes, etc) is still ongoing.
The three parts of our versioning system are MAJOR.FEATURE.SPRINT.
Given the following tag: 10.x-2.00:
10 | Major version of Drupal Core |
x | |
5 | Major version of WxT |
0 | Feature release of WxT. Also increments with minor core releases. |
0 | Sprint release between feature releases |
Note: Due to the constraints of drupal.org, there is no separator between the FEATURE and SPRINT digits.
less than a minute
The Drupal WxT distribution is following semantic versioning.
WxT typically makes a sprint release every four to six weeks. We will also use sprint releases to package new minor releases of Drupal Core with WxT as they become available.
In addition, we will also increment the major version number of WxT about once every four to six months.
Support for semantic versioning for extensions (modules, themes, etc) is still ongoing.
The three parts of our versioning system are MAJOR.FEATURE.SPRINT.
Given the following tag: 10.x-2.00:
10 | Major version of Drupal Core |
x | |
5 | Major version of WxT |
0 | Feature release of WxT. Also increments with minor core releases. |
0 | Sprint release between feature releases |
Note: Due to the constraints of drupal.org, there is no separator between the FEATURE and SPRINT digits.
This is the multi-page printable view of this section. +Click here to print.
This section documents best practices on how to deploy Drupal WxT to your chosen environment.
For the (optional) container based development workflow this is roughly the steps that are followed.
Clone the docker-scaffold repository:
git clone https://github.com/drupalwxt/docker-scaffold.git docker
+
Note: The
docker
folder should be added to your.gitignore
file.
The following are the steps you should follow for a Linux based environment.
Create the necessary symlinks:
ln -s docker/docker-compose.base.yml docker-compose.base.yml
+ln -s docker/docker-compose.ci.yml docker-compose.ci.yml
+ln -sf docker/docker-compose.yml docker-compose.yml
+
Create and adjust the following Makefile:
include .env
+NAME := $(or $(BASE_IMAGE),$(BASE_IMAGE),drupalwxt/site-wxt)
+VERSION := $(or $(VERSION),$(VERSION),'latest')
+PLATFORM := $(shell uname -s)
+$(eval GIT_USERNAME := $(if $(GIT_USERNAME),$(GIT_USERNAME),gitlab-ci-token))
+$(eval GIT_PASSWORD := $(if $(GIT_PASSWORD),$(GIT_PASSWORD),$(CI_JOB_TOKEN)))
+DOCKER_REPO := https://github.com/drupalwxt/docker-scaffold.git
+GET_DOCKER := $(shell [ -d docker ] || git clone $(DOCKER_REPO) docker)
+include docker/Makefile
+
Build and setup your environment with default content:
# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.yml build --no-cache
+docker compose -f docker-compose.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you have Docker for Desktop
and a new enough OSX environment (Monterey or higher) then the steps are the exact same as those for the Linux environment given above.
All that is required in advance is to enable VirtioFS
accelerated directory sharing which you can see in the attached picture below.
Docker for Desktop VirtioFS
+
Image: Drupal / CC-BY-CA
For older environments you may still use mutagen which is discussed below.
While this is fixed with the new virtualization framework discussed above.
For older environments mutagen will have to be used instead and as such requires a few additional steps.
# Mutagen Setup
+export VOLUME=site-wxt-mutagen-cache
+docker volume create $VOLUME
+docker container create --name $VOLUME -v $VOLUME:/volumes/$VOLUME mutagenio/sidecar:0.13.0-beta3
+docker start $VOLUME
+mutagen sync create --name $VOLUME --sync-mode=two-way-resolved --default-file-mode-beta 0666 --default-directory-mode-beta 0777 $(pwd) docker://$VOLUME/volumes/$VOLUME
+
+# Create symlinks
+ln -s docker/docker-compose.mutagen.yml docker-compose.mutagen.yml
+
+# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.mutagen.yml build --no-cache
+docker compose -f docker-compose.mutagen.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you wish to have a pristine docker environment you may execute the following commands.
docker rm $(docker ps -a -q) --force
+docker rmi $(docker images -q) --force
+docker volume prune -f
+
For those still using Mutagen you may also need to execute the following command:
mutagen sync terminate <sync_xxxxx>
+
This document represents a high-level technical overview of how the Helm Chart for Drupal WxT was built and how we envision Drupal itself should be architected in the cloud to support any of the Government of Canada procured cloud service providers (AWS
, Azure
, and GCP
). It should be noted that this Helm chart would also work in an on-premise environment with the appropriate Kubernetes infrastructure.
A key mandate when creating this architecture was to follow the Open Source Directive as given by the Treasury Board Secretariat (C.2.3.8) which states that you should try to use open standards and open source software first. Additionally, where possible all functionality should be exposed as restful services and leverage microservices via a containerized approach (C2.3.10).
We are leveraging a microservices design pattern utilizing immutable and scanned images through containerization running on Kubernetes with a platform that has been built and open sourced by Statistics Canada. While the platform will be discussed briefly to provide context the bulk of the document discusses how Drupal is installed and configured on top of it.
Kubernetes orchestrates the computing, networking, and storage infrastructure on behalf of user workloads. It assigns workloads and resources to a series of nearly identically-configured virtual machines.
Kukbernetes supports workloads running anywhere, from IoT devices, to private cloud and all the way to public cloud. This is possible due to Kubernetes’ pluggable architecture, which defines interfaces that are then implemented for the different environments. Kubernetes provides an Infrastructure as Code environment defined through declarative configuration. Because Kubernetes abstracts away the implementation of the computing environment, application dependencies such as storage, networking, etc., applications do not have to concern themselves with these differences.
Kubernetes is backed by a huge (10,000+) and vibrant growing community, consisting of end users, business, vendors and large cloud providers.
This architecture brings many benefits to the Government of Canada:
Kubernetes is supported across all cloud service providers (fully managed and self managed), preventing vendor lock-in. Managed offerings are available from Google, IBM, Azure, Digital Ocean, Amazon, Oracle and more. The choice whether to roll your own, using a managed service (AKS, EKS, GKE) or a Platform as a Service (OpenShift, Pivotal) is up to the organization to decide based on their requirements and risks. Our preference is to stay as close as possible to the open source version of Kubernetes as well as tooling in order to remain compatible with the different Kubernetes offerings (raw, managed, platform, etc.).
Kubernetes is being actively investigated and/or used by many departments across the Government of Canada. Departments are starting to collaborate more and work together towards a common, well-vetted solution and this is why we have have Open Sourced our platform on the GC Accelerators hoping to foster this collaboration and form a community of practice.
Provided below is the Terraform (Infrastructure as Code) necessarily to install the Azure Kubernetes Service Infrastructure as well as configure with optional platform components (RBAC, Service Mesh, Policies, etc).
A managed Drupal Platform as a Service is a strong candidate to take advantage of what a Kubernetes platform offers. The design enables a quick onboarding of new workloads through the repeatable deployment methodology provided by Kubernetes.
Recommendation: Kubernetes
Kubernetes is the basis of the Drupal platform and was further discussed above.
The whole Drupal application stack can be easily installed in a distributed fashion in minutes using our Helm chart, The chart facilitates a managed service workflow (rolling updates, cronjobs, health checks, auto-scaling, etc.) without user intervention.
Recommendation: Istio
The ingress controller is responsible for accepting external HTTPS connections and routing them to backend applications based on configuration defined in Kubernetes Ingress objects. Routing can be done by domain and/or path.
Recommendation: Varnish
Varnish is a highly customizable reverse proxy cache. This will aid in supporting a large number of concurrent visitors as the final rendered pages can be served from cache. Varnish is only required on the public environment and is not used in the content staging environment.
Nginx can technically address some of the cache requirements needed, however the open source version does not support purging selective pages. We need to clear caches based on content being updated / saved which Varnish supports along with the Expire Drupal module quite readily
Recommendation: Nginx
Nginx is an open source web server that can also be used a reverse proxy, HTTP cache, and load balancer. Due to its root in performance optimization under scale, Nginx often outperforms similarly popular web servers and is built to offer low memory usage, and high concurrency.
Recommendation: PHP-FPM
Drupal runs in the PHP runtime environment. PHP-FPM is the process manager organized as a master process managing pools of individual worker processes. Its architecture shares design similarities with event-driven web servers such as Nginx and allows for PHP scripts to use as much of the server's available resources as necessary without additional overhead that comes from running them inside of web server processes.
The PHP-FPM master process dynamically creates and terminates worker processes (within configurable limits) as traffic to PHP scripts increases and decreases. Processing scripts in this way allows for much higher processing performance, improved security, and better stability. The primary performance benefits from using PHP-FPM are more efficient PHP handling and ability to use opcode caching.
Recommendation: Redis
Redis is an advanced key-value cache and store.
It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps, etc.
Redis is particularly useful when using cloud managed databases to limit the overall database load and to make performance more consistent.
Recommendation: MySQL or PostgreSQL
Drupal maintains its state in a database and while supports several types only MySQL or PostgreSQL should be considered. Personally, we highly recommend PostgreSQL based on the experience we had building / launching quite a few Drupal sites in the cloud with it. However both run quite well with minimal operational concerns. Additionally the Helm Chart supports connection pooling using either ProxySQL and / or PGBouncer depending on the database used.
Note: Our recommendation would be to use a managed database offering from the cloud providers for a production environment. Coupled with a managed file service, this removes all stateful components from the cluster enabling the best application experience possible.
Drupal stores generated CSS/JS assets and uploaded content (images, videos, etc.) in a file storage. As the architecture is designed to be distributed, this present some design considerations for us.
Fully managed file shares in the cloud that are accessible via Server Message Block (SMB) or NFS protocol. Support is provided for dynamically creating and using a persistent volume with Azure Files in the Azure Kubernetes Service.
For more information on Azure Files, please see Azure Files and AKS.
Note: This is currently our recommended choice as it results in a simpler installation in Azure then relying on an S3 compatible object store discussed below. Similar storage solutions exist with the other cloud providers.
This page provides an overview for the process of creating a monolith container to deploy to Azure App Service (appsvc). It assumes you already have your project setup to work with the docker-scaffold repository. For initial project setup using docker-scaffold, see the beginning of the container based development workflow here - Local Docker setup
# Make our base docker image
+make build
+
+# Build the appsvc image
+docker compose -f docker-compose.appsvc.yml up -d
+
Note: After making changes to the project, you will need to remove your base image and build it again. This will ensure all changed files are copied into the base image as needed.
docker rmi $(docker images -q) --force
+
Now that you have build your appsvc image, you need to tag and push it to the ACR in order to deploy to it App Service.
docker login MY-CONTAINER-REGISTRY.azurecr.io
+docker tag site-XYZ-appsvc:latest MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+docker push MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+
Once this is done, you should be able to see your new image in the ACR.
In order to automate the build process using Azure DevOps, you can create a pipeline file in the root of your Drupal repo - Example pipeline file
This pipeline script will build the appsvc image and push it to your container registry. Make sure you have cretaed the required Service Connection in Azure DevOps (Git repository, ACR).
This is the multi-page printable view of this section. +Click here to print.
This page provides an overview for the process of creating a monolith container to deploy to Azure App Service (appsvc). It assumes you already have your project setup to work with the docker-scaffold repository. For initial project setup using docker-scaffold, see the beginning of the container based development workflow here - Local Docker setup
# Make our base docker image
+make build
+
+# Build the appsvc image
+docker compose -f docker-compose.appsvc.yml up -d
+
Note: After making changes to the project, you will need to remove your base image and build it again. This will ensure all changed files are copied into the base image as needed.
docker rmi $(docker images -q) --force
+
Now that you have build your appsvc image, you need to tag and push it to the ACR in order to deploy to it App Service.
docker login MY-CONTAINER-REGISTRY.azurecr.io
+docker tag site-XYZ-appsvc:latest MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+docker push MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+
Once this is done, you should be able to see your new image in the ACR.
In order to automate the build process using Azure DevOps, you can create a pipeline file in the root of your Drupal repo - Example pipeline file
This pipeline script will build the appsvc image and push it to your container registry. Make sure you have cretaed the required Service Connection in Azure DevOps (Git repository, ACR).
2 minute read
This page provides an overview for the process of creating a monolith container to deploy to Azure App Service (appsvc). It assumes you already have your project setup to work with the docker-scaffold repository. For initial project setup using docker-scaffold, see the beginning of the container based development workflow here - Local Docker setup
# Make our base docker image
+make build
+
+# Build the appsvc image
+docker compose -f docker-compose.appsvc.yml up -d
+
Note: After making changes to the project, you will need to remove your base image and build it again. This will ensure all changed files are copied into the base image as needed.
docker rmi $(docker images -q) --force
+
Now that you have build your appsvc image, you need to tag and push it to the ACR in order to deploy to it App Service.
docker login MY-CONTAINER-REGISTRY.azurecr.io
+docker tag site-XYZ-appsvc:latest MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+docker push MY-CONTAINER-REGISTRY.azurecr.io/site-XYZ-appsvc:[tag]
+
Once this is done, you should be able to see your new image in the ACR.
In order to automate the build process using Azure DevOps, you can create a pipeline file in the root of your Drupal repo - Example pipeline file
This pipeline script will build the appsvc image and push it to your container registry. Make sure you have cretaed the required Service Connection in Azure DevOps (Git repository, ACR).
This is the multi-page printable view of this section. +Click here to print.
For the (optional) container based development workflow this is roughly the steps that are followed.
Clone the docker-scaffold repository:
git clone https://github.com/drupalwxt/docker-scaffold.git docker
+
Note: The
docker
folder should be added to your.gitignore
file.
The following are the steps you should follow for a Linux based environment.
Create the necessary symlinks:
ln -s docker/docker-compose.base.yml docker-compose.base.yml
+ln -s docker/docker-compose.ci.yml docker-compose.ci.yml
+ln -sf docker/docker-compose.yml docker-compose.yml
+
Create and adjust the following Makefile:
include .env
+NAME := $(or $(BASE_IMAGE),$(BASE_IMAGE),drupalwxt/site-wxt)
+VERSION := $(or $(VERSION),$(VERSION),'latest')
+PLATFORM := $(shell uname -s)
+$(eval GIT_USERNAME := $(if $(GIT_USERNAME),$(GIT_USERNAME),gitlab-ci-token))
+$(eval GIT_PASSWORD := $(if $(GIT_PASSWORD),$(GIT_PASSWORD),$(CI_JOB_TOKEN)))
+DOCKER_REPO := https://github.com/drupalwxt/docker-scaffold.git
+GET_DOCKER := $(shell [ -d docker ] || git clone $(DOCKER_REPO) docker)
+include docker/Makefile
+
Build and setup your environment with default content:
# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.yml build --no-cache
+docker compose -f docker-compose.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you have Docker for Desktop
and a new enough OSX environment (Monterey or higher) then the steps are the exact same as those for the Linux environment given above.
All that is required in advance is to enable VirtioFS
accelerated directory sharing which you can see in the attached picture below.
Docker for Desktop VirtioFS
+
Image: Drupal / CC-BY-CA
For older environments you may still use mutagen which is discussed below.
While this is fixed with the new virtualization framework discussed above.
For older environments mutagen will have to be used instead and as such requires a few additional steps.
# Mutagen Setup
+export VOLUME=site-wxt-mutagen-cache
+docker volume create $VOLUME
+docker container create --name $VOLUME -v $VOLUME:/volumes/$VOLUME mutagenio/sidecar:0.13.0-beta3
+docker start $VOLUME
+mutagen sync create --name $VOLUME --sync-mode=two-way-resolved --default-file-mode-beta 0666 --default-directory-mode-beta 0777 $(pwd) docker://$VOLUME/volumes/$VOLUME
+
+# Create symlinks
+ln -s docker/docker-compose.mutagen.yml docker-compose.mutagen.yml
+
+# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.mutagen.yml build --no-cache
+docker compose -f docker-compose.mutagen.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you wish to have a pristine docker environment you may execute the following commands.
docker rm $(docker ps -a -q) --force
+docker rmi $(docker images -q) --force
+docker volume prune -f
+
For those still using Mutagen you may also need to execute the following command:
mutagen sync terminate <sync_xxxxx>
+
3 minute read
For the (optional) container based development workflow this is roughly the steps that are followed.
Clone the docker-scaffold repository:
git clone https://github.com/drupalwxt/docker-scaffold.git docker
+
Note: The
docker
folder should be added to your.gitignore
file.
The following are the steps you should follow for a Linux based environment.
Create the necessary symlinks:
ln -s docker/docker-compose.base.yml docker-compose.base.yml
+ln -s docker/docker-compose.ci.yml docker-compose.ci.yml
+ln -sf docker/docker-compose.yml docker-compose.yml
+
Create and adjust the following Makefile:
include .env
+NAME := $(or $(BASE_IMAGE),$(BASE_IMAGE),drupalwxt/site-wxt)
+VERSION := $(or $(VERSION),$(VERSION),'latest')
+PLATFORM := $(shell uname -s)
+$(eval GIT_USERNAME := $(if $(GIT_USERNAME),$(GIT_USERNAME),gitlab-ci-token))
+$(eval GIT_PASSWORD := $(if $(GIT_PASSWORD),$(GIT_PASSWORD),$(CI_JOB_TOKEN)))
+DOCKER_REPO := https://github.com/drupalwxt/docker-scaffold.git
+GET_DOCKER := $(shell [ -d docker ] || git clone $(DOCKER_REPO) docker)
+include docker/Makefile
+
Build and setup your environment with default content:
# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.yml build --no-cache
+docker compose -f docker-compose.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you have Docker for Desktop
and a new enough OSX environment (Monterey or higher) then the steps are the exact same as those for the Linux environment given above.
All that is required in advance is to enable VirtioFS
accelerated directory sharing which you can see in the attached picture below.
Docker for Desktop VirtioFS
+
Image: Drupal / CC-BY-CA
For older environments you may still use mutagen which is discussed below.
While this is fixed with the new virtualization framework discussed above.
For older environments mutagen will have to be used instead and as such requires a few additional steps.
# Mutagen Setup
+export VOLUME=site-wxt-mutagen-cache
+docker volume create $VOLUME
+docker container create --name $VOLUME -v $VOLUME:/volumes/$VOLUME mutagenio/sidecar:0.13.0-beta3
+docker start $VOLUME
+mutagen sync create --name $VOLUME --sync-mode=two-way-resolved --default-file-mode-beta 0666 --default-directory-mode-beta 0777 $(pwd) docker://$VOLUME/volumes/$VOLUME
+
+# Create symlinks
+ln -s docker/docker-compose.mutagen.yml docker-compose.mutagen.yml
+
+# Composer install
+export COMPOSER_MEMORY_LIMIT=-1 && composer install
+
+# Make our base docker image
+make build
+
+# Bring up the dev stack
+docker compose -f docker-compose.mutagen.yml build --no-cache
+docker compose -f docker-compose.mutagen.yml up -d
+
+# Install Drupal
+make drupal_install
+
+# Development configuration
+./docker/bin/drush config-set system.performance js.preprocess 0 -y && \
+./docker/bin/drush config-set system.performance css.preprocess 0 -y && \
+./docker/bin/drush php-eval 'node_access_rebuild();' && \
+./docker/bin/drush config-set wxt_library.settings wxt.theme theme-gcweb -y && \
+./docker/bin/drush cr
+
+# Migrate default content
+./docker/bin/drush migrate:import --group wxt --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Core' && \
+./docker/bin/drush migrate:import --group gcweb --tag 'Menu'
+
If you wish to have a pristine docker environment you may execute the following commands.
docker rm $(docker ps -a -q) --force
+docker rmi $(docker images -q) --force
+docker volume prune -f
+
For those still using Mutagen you may also need to execute the following command:
mutagen sync terminate <sync_xxxxx>
+
less than a minute
This section documents best practices on how to deploy Drupal WxT to your chosen environment.
This is the multi-page printable view of this section. +Click here to print.
This document represents a high-level technical overview of how the Helm Chart for Drupal WxT was built and how we envision Drupal itself should be architected in the cloud to support any of the Government of Canada procured cloud service providers (AWS
, Azure
, and GCP
). It should be noted that this Helm chart would also work in an on-premise environment with the appropriate Kubernetes infrastructure.
A key mandate when creating this architecture was to follow the Open Source Directive as given by the Treasury Board Secretariat (C.2.3.8) which states that you should try to use open standards and open source software first. Additionally, where possible all functionality should be exposed as restful services and leverage microservices via a containerized approach (C2.3.10).
We are leveraging a microservices design pattern utilizing immutable and scanned images through containerization running on Kubernetes with a platform that has been built and open sourced by Statistics Canada. While the platform will be discussed briefly to provide context the bulk of the document discusses how Drupal is installed and configured on top of it.
Kubernetes orchestrates the computing, networking, and storage infrastructure on behalf of user workloads. It assigns workloads and resources to a series of nearly identically-configured virtual machines.
Kukbernetes supports workloads running anywhere, from IoT devices, to private cloud and all the way to public cloud. This is possible due to Kubernetes’ pluggable architecture, which defines interfaces that are then implemented for the different environments. Kubernetes provides an Infrastructure as Code environment defined through declarative configuration. Because Kubernetes abstracts away the implementation of the computing environment, application dependencies such as storage, networking, etc., applications do not have to concern themselves with these differences.
Kubernetes is backed by a huge (10,000+) and vibrant growing community, consisting of end users, business, vendors and large cloud providers.
This architecture brings many benefits to the Government of Canada:
Kubernetes is supported across all cloud service providers (fully managed and self managed), preventing vendor lock-in. Managed offerings are available from Google, IBM, Azure, Digital Ocean, Amazon, Oracle and more. The choice whether to roll your own, using a managed service (AKS, EKS, GKE) or a Platform as a Service (OpenShift, Pivotal) is up to the organization to decide based on their requirements and risks. Our preference is to stay as close as possible to the open source version of Kubernetes as well as tooling in order to remain compatible with the different Kubernetes offerings (raw, managed, platform, etc.).
Kubernetes is being actively investigated and/or used by many departments across the Government of Canada. Departments are starting to collaborate more and work together towards a common, well-vetted solution and this is why we have have Open Sourced our platform on the GC Accelerators hoping to foster this collaboration and form a community of practice.
Provided below is the Terraform (Infrastructure as Code) necessarily to install the Azure Kubernetes Service Infrastructure as well as configure with optional platform components (RBAC, Service Mesh, Policies, etc).
A managed Drupal Platform as a Service is a strong candidate to take advantage of what a Kubernetes platform offers. The design enables a quick onboarding of new workloads through the repeatable deployment methodology provided by Kubernetes.
Recommendation: Kubernetes
Kubernetes is the basis of the Drupal platform and was further discussed above.
The whole Drupal application stack can be easily installed in a distributed fashion in minutes using our Helm chart, The chart facilitates a managed service workflow (rolling updates, cronjobs, health checks, auto-scaling, etc.) without user intervention.
Recommendation: Istio
The ingress controller is responsible for accepting external HTTPS connections and routing them to backend applications based on configuration defined in Kubernetes Ingress objects. Routing can be done by domain and/or path.
Recommendation: Varnish
Varnish is a highly customizable reverse proxy cache. This will aid in supporting a large number of concurrent visitors as the final rendered pages can be served from cache. Varnish is only required on the public environment and is not used in the content staging environment.
Nginx can technically address some of the cache requirements needed, however the open source version does not support purging selective pages. We need to clear caches based on content being updated / saved which Varnish supports along with the Expire Drupal module quite readily
Recommendation: Nginx
Nginx is an open source web server that can also be used a reverse proxy, HTTP cache, and load balancer. Due to its root in performance optimization under scale, Nginx often outperforms similarly popular web servers and is built to offer low memory usage, and high concurrency.
Recommendation: PHP-FPM
Drupal runs in the PHP runtime environment. PHP-FPM is the process manager organized as a master process managing pools of individual worker processes. Its architecture shares design similarities with event-driven web servers such as Nginx and allows for PHP scripts to use as much of the server's available resources as necessary without additional overhead that comes from running them inside of web server processes.
The PHP-FPM master process dynamically creates and terminates worker processes (within configurable limits) as traffic to PHP scripts increases and decreases. Processing scripts in this way allows for much higher processing performance, improved security, and better stability. The primary performance benefits from using PHP-FPM are more efficient PHP handling and ability to use opcode caching.
Recommendation: Redis
Redis is an advanced key-value cache and store.
It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps, etc.
Redis is particularly useful when using cloud managed databases to limit the overall database load and to make performance more consistent.
Recommendation: MySQL or PostgreSQL
Drupal maintains its state in a database and while supports several types only MySQL or PostgreSQL should be considered. Personally, we highly recommend PostgreSQL based on the experience we had building / launching quite a few Drupal sites in the cloud with it. However both run quite well with minimal operational concerns. Additionally the Helm Chart supports connection pooling using either ProxySQL and / or PGBouncer depending on the database used.
Note: Our recommendation would be to use a managed database offering from the cloud providers for a production environment. Coupled with a managed file service, this removes all stateful components from the cluster enabling the best application experience possible.
Drupal stores generated CSS/JS assets and uploaded content (images, videos, etc.) in a file storage. As the architecture is designed to be distributed, this present some design considerations for us.
Fully managed file shares in the cloud that are accessible via Server Message Block (SMB) or NFS protocol. Support is provided for dynamically creating and using a persistent volume with Azure Files in the Azure Kubernetes Service.
For more information on Azure Files, please see Azure Files and AKS.
Note: This is currently our recommended choice as it results in a simpler installation in Azure then relying on an S3 compatible object store discussed below. Similar storage solutions exist with the other cloud providers.
7 minute read
This document represents a high-level technical overview of how the Helm Chart for Drupal WxT was built and how we envision Drupal itself should be architected in the cloud to support any of the Government of Canada procured cloud service providers (AWS
, Azure
, and GCP
). It should be noted that this Helm chart would also work in an on-premise environment with the appropriate Kubernetes infrastructure.
A key mandate when creating this architecture was to follow the Open Source Directive as given by the Treasury Board Secretariat (C.2.3.8) which states that you should try to use open standards and open source software first. Additionally, where possible all functionality should be exposed as restful services and leverage microservices via a containerized approach (C2.3.10).
We are leveraging a microservices design pattern utilizing immutable and scanned images through containerization running on Kubernetes with a platform that has been built and open sourced by Statistics Canada. While the platform will be discussed briefly to provide context the bulk of the document discusses how Drupal is installed and configured on top of it.
Kubernetes orchestrates the computing, networking, and storage infrastructure on behalf of user workloads. It assigns workloads and resources to a series of nearly identically-configured virtual machines.
Kukbernetes supports workloads running anywhere, from IoT devices, to private cloud and all the way to public cloud. This is possible due to Kubernetes’ pluggable architecture, which defines interfaces that are then implemented for the different environments. Kubernetes provides an Infrastructure as Code environment defined through declarative configuration. Because Kubernetes abstracts away the implementation of the computing environment, application dependencies such as storage, networking, etc., applications do not have to concern themselves with these differences.
Kubernetes is backed by a huge (10,000+) and vibrant growing community, consisting of end users, business, vendors and large cloud providers.
This architecture brings many benefits to the Government of Canada:
Kubernetes is supported across all cloud service providers (fully managed and self managed), preventing vendor lock-in. Managed offerings are available from Google, IBM, Azure, Digital Ocean, Amazon, Oracle and more. The choice whether to roll your own, using a managed service (AKS, EKS, GKE) or a Platform as a Service (OpenShift, Pivotal) is up to the organization to decide based on their requirements and risks. Our preference is to stay as close as possible to the open source version of Kubernetes as well as tooling in order to remain compatible with the different Kubernetes offerings (raw, managed, platform, etc.).
Kubernetes is being actively investigated and/or used by many departments across the Government of Canada. Departments are starting to collaborate more and work together towards a common, well-vetted solution and this is why we have have Open Sourced our platform on the GC Accelerators hoping to foster this collaboration and form a community of practice.
Provided below is the Terraform (Infrastructure as Code) necessarily to install the Azure Kubernetes Service Infrastructure as well as configure with optional platform components (RBAC, Service Mesh, Policies, etc).
A managed Drupal Platform as a Service is a strong candidate to take advantage of what a Kubernetes platform offers. The design enables a quick onboarding of new workloads through the repeatable deployment methodology provided by Kubernetes.
Recommendation: Kubernetes
Kubernetes is the basis of the Drupal platform and was further discussed above.
The whole Drupal application stack can be easily installed in a distributed fashion in minutes using our Helm chart, The chart facilitates a managed service workflow (rolling updates, cronjobs, health checks, auto-scaling, etc.) without user intervention.
Recommendation: Istio
The ingress controller is responsible for accepting external HTTPS connections and routing them to backend applications based on configuration defined in Kubernetes Ingress objects. Routing can be done by domain and/or path.
Recommendation: Varnish
Varnish is a highly customizable reverse proxy cache. This will aid in supporting a large number of concurrent visitors as the final rendered pages can be served from cache. Varnish is only required on the public environment and is not used in the content staging environment.
Nginx can technically address some of the cache requirements needed, however the open source version does not support purging selective pages. We need to clear caches based on content being updated / saved which Varnish supports along with the Expire Drupal module quite readily
Recommendation: Nginx
Nginx is an open source web server that can also be used a reverse proxy, HTTP cache, and load balancer. Due to its root in performance optimization under scale, Nginx often outperforms similarly popular web servers and is built to offer low memory usage, and high concurrency.
Recommendation: PHP-FPM
Drupal runs in the PHP runtime environment. PHP-FPM is the process manager organized as a master process managing pools of individual worker processes. Its architecture shares design similarities with event-driven web servers such as Nginx and allows for PHP scripts to use as much of the server's available resources as necessary without additional overhead that comes from running them inside of web server processes.
The PHP-FPM master process dynamically creates and terminates worker processes (within configurable limits) as traffic to PHP scripts increases and decreases. Processing scripts in this way allows for much higher processing performance, improved security, and better stability. The primary performance benefits from using PHP-FPM are more efficient PHP handling and ability to use opcode caching.
Recommendation: Redis
Redis is an advanced key-value cache and store.
It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps, etc.
Redis is particularly useful when using cloud managed databases to limit the overall database load and to make performance more consistent.
Recommendation: MySQL or PostgreSQL
Drupal maintains its state in a database and while supports several types only MySQL or PostgreSQL should be considered. Personally, we highly recommend PostgreSQL based on the experience we had building / launching quite a few Drupal sites in the cloud with it. However both run quite well with minimal operational concerns. Additionally the Helm Chart supports connection pooling using either ProxySQL and / or PGBouncer depending on the database used.
Note: Our recommendation would be to use a managed database offering from the cloud providers for a production environment. Coupled with a managed file service, this removes all stateful components from the cluster enabling the best application experience possible.
Drupal stores generated CSS/JS assets and uploaded content (images, videos, etc.) in a file storage. As the architecture is designed to be distributed, this present some design considerations for us.
Fully managed file shares in the cloud that are accessible via Server Message Block (SMB) or NFS protocol. Support is provided for dynamically creating and using a persistent volume with Azure Files in the Azure Kubernetes Service.
For more information on Azure Files, please see Azure Files and AKS.
Note: This is currently our recommended choice as it results in a simpler installation in Azure then relying on an S3 compatible object store discussed below. Similar storage solutions exist with the other cloud providers.
This is the multi-page printable view of this section. +Click here to print.
Userguide for all of the general information related to the maintenance and operation of Drupal WxT.
The Drupal WxT distribution is designed for organizations that must meet accessibility and bilingualism standards. It attempts to integrate with the design patterns found in the WET-BOEW and Canada.ca design system, including the mandatory Content and Information Architecture (C&IA) Specification for the Government of Canada.
To make working with Drupal WxT easier, there are potentially three ways you can approach it.
The Drupal WxT distribution method stands out as a preferred choice for web developers and organizations seeking a robust web development solution.
Unlike a standalone installation, the distribution provides a comprehensive package of features and workflows that have been vetted and tested by the Drupal WxT community based on real world use cases.
This means users can leverage a well-established framework with proven capabilities, saving time and effort in development while ensuring stability and reliability.
By opting for the distribution method, teams gain access to shared resources, ongoing support, and a community-driven ecosystem, hopefully helping to build accessible, and bilingual web experiences with confidence.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT |
| wxt | Distribution |
WxT Bootstrap |
| wxt_bootstrap | Standalone |
WxT Library |
| wxt_library | Standalone |
WxT Admin |
| wxt_admin | Distribution |
WxT Core |
| wxt_core | Distribution |
WxT Extend |
| wxt_extend | Distribution |
WxT Translation |
| wxt_translation | Distribution |
A standalone installation allows you to install and configure the standalone components type discussed in the previous section separately without relying on a pre-packaged distribution (composer project).
A composer project will often include multiple modules whether both custom and contributed along with the various configuration and dependencies they will rely on.
Drupal WxT offers a standalone installation as an alternative for those users who don’t want the full weight of a distribution and prefer more control over their setup while still conforming to the Government of Canada C&IA Specification.
Instead users can opt to create their own distribution (composer project) and install only the specific modules and themes required for their needs.
At a minimum and to comply with the WET-BOEW and Canada.ca design system you only need use 2 components.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT Bootstrap (Theme) |
| wxt_bootstrap | Standalone |
WxT Library (Module) |
| wxt_library | Standalone |
For the WET-BOEW Framework Assets it is mandatory that you follow the expected naming convention and that these files be placed within the /libraries
folder.
For you convenience all of these components are already part of a composer repository that can be added very easily to your new or existing composer project.
{
+ ...
+ "require": {
+ ...
+ "drupal/wxt_bootstrap": "^8.0",
+ "drupal/wxt_library": "^8.0",
+ },
+ ...
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://drupalwxt.github.io/composer-extdeps/"
+ }
+ ],
+ ...
+}
+
Note: It is still recommended to use the distribution method, as the standalone option receives limited support and you will lose out on some of the functionality / plugins that help to more fully integrate with the WET-BOEW and Canada.ca design system.
If you prefer full control over your codebase and want to reduce external dependencies, you can use Drupal WxT as a reference implementation.
This means that, as long as you provide proper attribution, you have the freedom to copy or fork any part of the codebase and incorporate it into your own project.
The main drawback of this approach is that you won’t receive community support and also won’t have the same tight integration of features with the WET-BOEW and Canada.ca design system.
However you can selectively choose exactly what you need for your project, potentially saving some time and reducing additional external dependencies.
Our advice at the end of the day is you must consider what is best for your department or organization in the long term.
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
As Drupal WxT is a Drupal distribution, the official guide for Drupal system requirements will apply.
Run the following commands (choosing your version) and replace site-name with the directory of your choice this is where WxT will be installed.
# Requires PHP 8.2 (Drupal 10 LTS)
+composer self-update
+composer create-project drupalwxt/site-wxt:10.4.x-dev <site-name> --no-interaction
+
+# Requires PHP 8.3 (Drupal 11 - alpha release)
+composer self-update
+composer create-project drupalwxt/site-wxt:11.1.x-dev <site-name> --no-interaction
+
Note: Normally you would pass a stable tag to the above command rather then just pulling from the development branch.
If you don’t want to use Composer, you can install WxT the traditional way by downloading a tarball from WxT’s GitHub releases page.
Note: That the tarball generated by the Drupal.org packager does not include the required Composer dependencies and should not be used without following the specialized instructions.
For the (optional) container based local development workflow please consult our documentation site:
a) The Drupal Root is in <site-name>/html
b) You can install Drupal WxT through the browser as any other drupal installation or use drush site-install
to install the WxT installation profile:
drush si wxt \
+ --sites-subdir=default \
+ --db-url=mysql://root:root@db:3306/wxt \
+ --account-name=admin \
+ --account-pass=Drupal@2024 \
+ --site-mail=admin@example.com \
+ --site-name="Drupal Install Profile (WxT)" \
+ wxt_extension_configure_form.select_all='TRUE' \
+ install_configure_form.update_status_module='array(FALSE,FALSE)' \
+ --yes
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
c) You can download up-to-date translations using:
drush locale-check
+drush locale-update
+
d) If you work for the Government of Canada you will want to enable the canada.ca
theme:
drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
Note: You can navigate to the
admin/config/wxt/wxt_library
settings page.
e) The Drupal WxT site should now be sucessfully installed and you can loging via the /user
page.
Note: Please always go to the
admin/report/status
page and confirm there are no warnings and / or errors.
The standalone install is provided as an additional method for those who do not wish to have the full weight of a distribution and its required dependencies. You will need to add at the minimum the below listed modules and themes (including Bootstrap base theme) as well as the WxT jQuery Framework assets installed into the /libraries
folder with the proper naming scheme.
Note: We highly recommend that you use the distribution method as limited support is provided for the standalone method.
The following is an example of how to use the Migrate API module to import common design patterns for Canada.ca aligning to the C&IA specifications:
# Set the WxT theme to GCWeb
+drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
+# Import design patterns for Canada.ca
+drush migrate:import --group wxt --tag 'Core'
+drush migrate:import --group gcweb --tag 'Core'
+drush migrate:import --group gcweb --tag 'Menu'
+
+drush cr
+
Note: There is a corresponding group
wxt_translation
andgcweb_translation
for importing the corresponding french content.
Drupal WxT relies on Drupal’s configuration system for configuring default features and functionality. A consequence of this is, once you have installed Drupal WxT, that we cannot modify the sites configuration without having an impact on your site. Drupal WxT will, however, offer to make changes to your configuration as part of the update process.
If you’ve installed WxT using our Composer-based project template, all you need to do is following the given steps below.
These are the typical steps you should following when updating Drupal WxT:
a) Read the release notes for the release to which you are updating along with any releases in between.
b) To update your WxT codebase you would replace [VERSION]
with the release version you wish to use.
composer self update
+composer require drupalwxt/wxt:[VERSION]
+composer update
+
Note: We highly recommend that you are using the v2.x.x line of Composer.
c) Run any database updates:
drush cache:rebuild
+drush updatedb
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run database updates.
d) Run any WxT configuration updates:
drush cache:rebuild
+drush update:wxt
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run WxT updates.
If you are using configuration management to move your configuration between development, staging, and production environments, you should follow the standard Drupal process.
a) Export the new configuration:
drush cache:rebuild
+drush config:export
+
b) Commit the code and configuration changes to your source code repository and push them to your environment.
c) Import any configuration changes:
drush cache:rebuild
+drush config:import
+
The following table is a list of all the releases that are housed under the Drupal WxT organization on GitHub:
Release | Created Date | Description |
---|---|---|
5.4.1 | 2025-03-14 |
Upgrade path:
Note(s): N/A |
5.4.0 | 2024-12-20 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.3.0 | 2024-11-04 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.2.3 | 2024-07-02 |
Upgrade path:
Note(s): The Group module has been updated to the 2.2.x branch as an intermediate step required to get to the recommended 3.2.x branch. |
5.2.2 | 2024-04-15 |
Upgrade path:
Note(s): The Group module has been downgraded to the 8.x-1.6 release + alongside the flexible permissions module which has been added. This is needed for an immediate upgrade path for groups and the next release will have groups using again the 2.2.x branch. |
The following table is a list of all the repositories that are housed under the Drupal WxT organization on GitHub:
Name | Website | Description | Size |
---|---|---|---|
composer-extdeps | drupalwxt/composer-extdeps | Composer repository for external dependencies on Drupal WxT | Size: 83 Bytes |
docker-scaffold | drupalwxt/docker-scaffold | Docker Scaffold for Drupal WxT | Size: 219 Bytes |
drupalwxt.github.io | drupalwxt/drupalwxt.github.io | GitHub Pages for Drupal WxT. | Size: 15010 Bytes |
helm-drupal | drupalwxt/helm-drupal | Helm Chart for deploying an enterprise-grade Drupal environment. | Size: 67695 Bytes |
site-wxt | drupalwxt/site-wxt | An example composer project for the Drupal WxT distribution used for integration testing. | Size: 4101 Bytes |
terraform-kubernetes-drupalwxt | drupalwxt/terraform-kubernetes-drupalwxt | Terraform module for Drupal WxT | Size: 35 Bytes |
themes-cdn | drupalwxt/themes-cdn | Content Delivery Network (CDN) files for the theme repositories of the Web Experience Toolkit (WET) | Size: 12445 Bytes |
wxt | drupalwxt/wxt | Drupal variant of the Web Experience Toolkit (WxT). | Size: 3482 Bytes |
wxt-project | drupalwxt/wxt-project | Composer project template for Drupal 9 sites built with the WxT distribution. | Size: 76 Bytes |
wxt_bootstrap | drupalwxt/wxt_bootstrap | Bootstrap derived sub-theme aligned for use with the Web Experience Toolkit jQuery Framework. | Size: 1163 Bytes |
wxt_library | drupalwxt/wxt_library | Web Experience Toolkit Framework integration for Drupal. | Size: 118 Bytes |
The following are links to some useful resources:
The core distribution will always strive to be:
Beyond the above the distribution will provide extensible features that can be opted into through the wxt_ext suite of modules:
modulename.wxt_extension.yml
file so can be enabled as optional extension during profile installationIn addition, Drupal WxT will offer out of tree (external) modules that implement specific features:
Note: The governance around the core distribution will always be much stricter then the governance around adding a
wxt_ext
or an out of tree module.
less than a minute
Userguide for all of the general information related to the maintenance and operation of Drupal WxT.
This is the multi-page printable view of this section. +Click here to print.
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
As Drupal WxT is a Drupal distribution, the official guide for Drupal system requirements will apply.
Run the following commands (choosing your version) and replace site-name with the directory of your choice this is where WxT will be installed.
# Requires PHP 8.2 (Drupal 10 LTS)
+composer self-update
+composer create-project drupalwxt/site-wxt:10.4.x-dev <site-name> --no-interaction
+
+# Requires PHP 8.3 (Drupal 11 - alpha release)
+composer self-update
+composer create-project drupalwxt/site-wxt:11.1.x-dev <site-name> --no-interaction
+
Note: Normally you would pass a stable tag to the above command rather then just pulling from the development branch.
If you don’t want to use Composer, you can install WxT the traditional way by downloading a tarball from WxT’s GitHub releases page.
Note: That the tarball generated by the Drupal.org packager does not include the required Composer dependencies and should not be used without following the specialized instructions.
For the (optional) container based local development workflow please consult our documentation site:
a) The Drupal Root is in <site-name>/html
b) You can install Drupal WxT through the browser as any other drupal installation or use drush site-install
to install the WxT installation profile:
drush si wxt \
+ --sites-subdir=default \
+ --db-url=mysql://root:root@db:3306/wxt \
+ --account-name=admin \
+ --account-pass=Drupal@2024 \
+ --site-mail=admin@example.com \
+ --site-name="Drupal Install Profile (WxT)" \
+ wxt_extension_configure_form.select_all='TRUE' \
+ install_configure_form.update_status_module='array(FALSE,FALSE)' \
+ --yes
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
c) You can download up-to-date translations using:
drush locale-check
+drush locale-update
+
d) If you work for the Government of Canada you will want to enable the canada.ca
theme:
drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
Note: You can navigate to the
admin/config/wxt/wxt_library
settings page.
e) The Drupal WxT site should now be sucessfully installed and you can loging via the /user
page.
Note: Please always go to the
admin/report/status
page and confirm there are no warnings and / or errors.
The standalone install is provided as an additional method for those who do not wish to have the full weight of a distribution and its required dependencies. You will need to add at the minimum the below listed modules and themes (including Bootstrap base theme) as well as the WxT jQuery Framework assets installed into the /libraries
folder with the proper naming scheme.
Note: We highly recommend that you use the distribution method as limited support is provided for the standalone method.
The following is an example of how to use the Migrate API module to import common design patterns for Canada.ca aligning to the C&IA specifications:
# Set the WxT theme to GCWeb
+drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
+# Import design patterns for Canada.ca
+drush migrate:import --group wxt --tag 'Core'
+drush migrate:import --group gcweb --tag 'Core'
+drush migrate:import --group gcweb --tag 'Menu'
+
+drush cr
+
Note: There is a corresponding group
wxt_translation
andgcweb_translation
for importing the corresponding french content.
3 minute read
We highly recommend using our Composer Project Template to build and maintain your WxT derived project’s codebase.
As Drupal WxT is a Drupal distribution, the official guide for Drupal system requirements will apply.
Run the following commands (choosing your version) and replace site-name with the directory of your choice this is where WxT will be installed.
# Requires PHP 8.2 (Drupal 10 LTS)
+composer self-update
+composer create-project drupalwxt/site-wxt:10.4.x-dev <site-name> --no-interaction
+
+# Requires PHP 8.3 (Drupal 11 - alpha release)
+composer self-update
+composer create-project drupalwxt/site-wxt:11.1.x-dev <site-name> --no-interaction
+
Note: Normally you would pass a stable tag to the above command rather then just pulling from the development branch.
If you don’t want to use Composer, you can install WxT the traditional way by downloading a tarball from WxT’s GitHub releases page.
Note: That the tarball generated by the Drupal.org packager does not include the required Composer dependencies and should not be used without following the specialized instructions.
For the (optional) container based local development workflow please consult our documentation site:
a) The Drupal Root is in <site-name>/html
b) You can install Drupal WxT through the browser as any other drupal installation or use drush site-install
to install the WxT installation profile:
drush si wxt \
+ --sites-subdir=default \
+ --db-url=mysql://root:root@db:3306/wxt \
+ --account-name=admin \
+ --account-pass=Drupal@2024 \
+ --site-mail=admin@example.com \
+ --site-name="Drupal Install Profile (WxT)" \
+ wxt_extension_configure_form.select_all='TRUE' \
+ install_configure_form.update_status_module='array(FALSE,FALSE)' \
+ --yes
+
Note: If you wish to only install the minimum set of dependencies please remove the
wxt_extension_configure_form.select_all='TRUE'
flag in its entirety.
c) You can download up-to-date translations using:
drush locale-check
+drush locale-update
+
d) If you work for the Government of Canada you will want to enable the canada.ca
theme:
drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
Note: You can navigate to the
admin/config/wxt/wxt_library
settings page.
e) The Drupal WxT site should now be sucessfully installed and you can loging via the /user
page.
Note: Please always go to the
admin/report/status
page and confirm there are no warnings and / or errors.
The standalone install is provided as an additional method for those who do not wish to have the full weight of a distribution and its required dependencies. You will need to add at the minimum the below listed modules and themes (including Bootstrap base theme) as well as the WxT jQuery Framework assets installed into the /libraries
folder with the proper naming scheme.
Note: We highly recommend that you use the distribution method as limited support is provided for the standalone method.
The following is an example of how to use the Migrate API module to import common design patterns for Canada.ca aligning to the C&IA specifications:
# Set the WxT theme to GCWeb
+drush config-set wxt_library.settings wxt.theme theme-gcweb -y
+
+# Import design patterns for Canada.ca
+drush migrate:import --group wxt --tag 'Core'
+drush migrate:import --group gcweb --tag 'Core'
+drush migrate:import --group gcweb --tag 'Menu'
+
+drush cr
+
Note: There is a corresponding group
wxt_translation
andgcweb_translation
for importing the corresponding french content.
This is the multi-page printable view of this section. +Click here to print.
The Drupal WxT distribution is designed for organizations that must meet accessibility and bilingualism standards. It attempts to integrate with the design patterns found in the WET-BOEW and Canada.ca design system, including the mandatory Content and Information Architecture (C&IA) Specification for the Government of Canada.
To make working with Drupal WxT easier, there are potentially three ways you can approach it.
The Drupal WxT distribution method stands out as a preferred choice for web developers and organizations seeking a robust web development solution.
Unlike a standalone installation, the distribution provides a comprehensive package of features and workflows that have been vetted and tested by the Drupal WxT community based on real world use cases.
This means users can leverage a well-established framework with proven capabilities, saving time and effort in development while ensuring stability and reliability.
By opting for the distribution method, teams gain access to shared resources, ongoing support, and a community-driven ecosystem, hopefully helping to build accessible, and bilingual web experiences with confidence.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT |
| wxt | Distribution |
WxT Bootstrap |
| wxt_bootstrap | Standalone |
WxT Library |
| wxt_library | Standalone |
WxT Admin |
| wxt_admin | Distribution |
WxT Core |
| wxt_core | Distribution |
WxT Extend |
| wxt_extend | Distribution |
WxT Translation |
| wxt_translation | Distribution |
A standalone installation allows you to install and configure the standalone components type discussed in the previous section separately without relying on a pre-packaged distribution (composer project).
A composer project will often include multiple modules whether both custom and contributed along with the various configuration and dependencies they will rely on.
Drupal WxT offers a standalone installation as an alternative for those users who don’t want the full weight of a distribution and prefer more control over their setup while still conforming to the Government of Canada C&IA Specification.
Instead users can opt to create their own distribution (composer project) and install only the specific modules and themes required for their needs.
At a minimum and to comply with the WET-BOEW and Canada.ca design system you only need use 2 components.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT Bootstrap (Theme) |
| wxt_bootstrap | Standalone |
WxT Library (Module) |
| wxt_library | Standalone |
For the WET-BOEW Framework Assets it is mandatory that you follow the expected naming convention and that these files be placed within the /libraries
folder.
For you convenience all of these components are already part of a composer repository that can be added very easily to your new or existing composer project.
{
+ ...
+ "require": {
+ ...
+ "drupal/wxt_bootstrap": "^8.0",
+ "drupal/wxt_library": "^8.0",
+ },
+ ...
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://drupalwxt.github.io/composer-extdeps/"
+ }
+ ],
+ ...
+}
+
Note: It is still recommended to use the distribution method, as the standalone option receives limited support and you will lose out on some of the functionality / plugins that help to more fully integrate with the WET-BOEW and Canada.ca design system.
If you prefer full control over your codebase and want to reduce external dependencies, you can use Drupal WxT as a reference implementation.
This means that, as long as you provide proper attribution, you have the freedom to copy or fork any part of the codebase and incorporate it into your own project.
The main drawback of this approach is that you won’t receive community support and also won’t have the same tight integration of features with the WET-BOEW and Canada.ca design system.
However you can selectively choose exactly what you need for your project, potentially saving some time and reducing additional external dependencies.
Our advice at the end of the day is you must consider what is best for your department or organization in the long term.
6 minute read
The Drupal WxT distribution is designed for organizations that must meet accessibility and bilingualism standards. It attempts to integrate with the design patterns found in the WET-BOEW and Canada.ca design system, including the mandatory Content and Information Architecture (C&IA) Specification for the Government of Canada.
To make working with Drupal WxT easier, there are potentially three ways you can approach it.
The Drupal WxT distribution method stands out as a preferred choice for web developers and organizations seeking a robust web development solution.
Unlike a standalone installation, the distribution provides a comprehensive package of features and workflows that have been vetted and tested by the Drupal WxT community based on real world use cases.
This means users can leverage a well-established framework with proven capabilities, saving time and effort in development while ensuring stability and reliability.
By opting for the distribution method, teams gain access to shared resources, ongoing support, and a community-driven ecosystem, hopefully helping to build accessible, and bilingual web experiences with confidence.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT |
| wxt | Distribution |
WxT Bootstrap |
| wxt_bootstrap | Standalone |
WxT Library |
| wxt_library | Standalone |
WxT Admin |
| wxt_admin | Distribution |
WxT Core |
| wxt_core | Distribution |
WxT Extend |
| wxt_extend | Distribution |
WxT Translation |
| wxt_translation | Distribution |
A standalone installation allows you to install and configure the standalone components type discussed in the previous section separately without relying on a pre-packaged distribution (composer project).
A composer project will often include multiple modules whether both custom and contributed along with the various configuration and dependencies they will rely on.
Drupal WxT offers a standalone installation as an alternative for those users who don’t want the full weight of a distribution and prefer more control over their setup while still conforming to the Government of Canada C&IA Specification.
Instead users can opt to create their own distribution (composer project) and install only the specific modules and themes required for their needs.
At a minimum and to comply with the WET-BOEW and Canada.ca design system you only need use 2 components.
Component | Features | Machine Name | Type |
---|---|---|---|
WxT Bootstrap (Theme) |
| wxt_bootstrap | Standalone |
WxT Library (Module) |
| wxt_library | Standalone |
For the WET-BOEW Framework Assets it is mandatory that you follow the expected naming convention and that these files be placed within the /libraries
folder.
For you convenience all of these components are already part of a composer repository that can be added very easily to your new or existing composer project.
{
+ ...
+ "require": {
+ ...
+ "drupal/wxt_bootstrap": "^8.0",
+ "drupal/wxt_library": "^8.0",
+ },
+ ...
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://drupalwxt.github.io/composer-extdeps/"
+ }
+ ],
+ ...
+}
+
Note: It is still recommended to use the distribution method, as the standalone option receives limited support and you will lose out on some of the functionality / plugins that help to more fully integrate with the WET-BOEW and Canada.ca design system.
If you prefer full control over your codebase and want to reduce external dependencies, you can use Drupal WxT as a reference implementation.
This means that, as long as you provide proper attribution, you have the freedom to copy or fork any part of the codebase and incorporate it into your own project.
The main drawback of this approach is that you won’t receive community support and also won’t have the same tight integration of features with the WET-BOEW and Canada.ca design system.
However you can selectively choose exactly what you need for your project, potentially saving some time and reducing additional external dependencies.
Our advice at the end of the day is you must consider what is best for your department or organization in the long term.
This is the multi-page printable view of this section. +Click here to print.
The following table is a list of all the releases that are housed under the Drupal WxT organization on GitHub:
Release | Created Date | Description |
---|---|---|
5.4.1 | 2025-03-14 |
Upgrade path:
Note(s): N/A |
5.4.0 | 2024-12-20 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.3.0 | 2024-11-04 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.2.3 | 2024-07-02 |
Upgrade path:
Note(s): The Group module has been updated to the 2.2.x branch as an intermediate step required to get to the recommended 3.2.x branch. |
5.2.2 | 2024-04-15 |
Upgrade path:
Note(s): The Group module has been downgraded to the 8.x-1.6 release + alongside the flexible permissions module which has been added. This is needed for an immediate upgrade path for groups and the next release will have groups using again the 2.2.x branch. |
The following table is a list of all the repositories that are housed under the Drupal WxT organization on GitHub:
Name | Website | Description | Size |
---|---|---|---|
composer-extdeps | drupalwxt/composer-extdeps | Composer repository for external dependencies on Drupal WxT | Size: 83 Bytes |
docker-scaffold | drupalwxt/docker-scaffold | Docker Scaffold for Drupal WxT | Size: 219 Bytes |
drupalwxt.github.io | drupalwxt/drupalwxt.github.io | GitHub Pages for Drupal WxT. | Size: 15010 Bytes |
helm-drupal | drupalwxt/helm-drupal | Helm Chart for deploying an enterprise-grade Drupal environment. | Size: 67695 Bytes |
site-wxt | drupalwxt/site-wxt | An example composer project for the Drupal WxT distribution used for integration testing. | Size: 4101 Bytes |
terraform-kubernetes-drupalwxt | drupalwxt/terraform-kubernetes-drupalwxt | Terraform module for Drupal WxT | Size: 35 Bytes |
themes-cdn | drupalwxt/themes-cdn | Content Delivery Network (CDN) files for the theme repositories of the Web Experience Toolkit (WET) | Size: 12445 Bytes |
wxt | drupalwxt/wxt | Drupal variant of the Web Experience Toolkit (WxT). | Size: 3482 Bytes |
wxt-project | drupalwxt/wxt-project | Composer project template for Drupal 9 sites built with the WxT distribution. | Size: 76 Bytes |
wxt_bootstrap | drupalwxt/wxt_bootstrap | Bootstrap derived sub-theme aligned for use with the Web Experience Toolkit jQuery Framework. | Size: 1163 Bytes |
wxt_library | drupalwxt/wxt_library | Web Experience Toolkit Framework integration for Drupal. | Size: 118 Bytes |
5 minute read
The following table is a list of all the releases that are housed under the Drupal WxT organization on GitHub:
Release | Created Date | Description |
---|---|---|
5.4.1 | 2025-03-14 |
Upgrade path:
Note(s): N/A |
5.4.0 | 2024-12-20 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.3.0 | 2024-11-04 |
Upgrade path:
Note(s): Update to Drupal Core 10.3.x line. |
5.2.3 | 2024-07-02 |
Upgrade path:
Note(s): The Group module has been updated to the 2.2.x branch as an intermediate step required to get to the recommended 3.2.x branch. |
5.2.2 | 2024-04-15 |
Upgrade path:
Note(s): The Group module has been downgraded to the 8.x-1.6 release + alongside the flexible permissions module which has been added. This is needed for an immediate upgrade path for groups and the next release will have groups using again the 2.2.x branch. |
The following table is a list of all the repositories that are housed under the Drupal WxT organization on GitHub:
Name | Website | Description | Size |
---|---|---|---|
composer-extdeps | drupalwxt/composer-extdeps | Composer repository for external dependencies on Drupal WxT | Size: 83 Bytes |
docker-scaffold | drupalwxt/docker-scaffold | Docker Scaffold for Drupal WxT | Size: 219 Bytes |
drupalwxt.github.io | drupalwxt/drupalwxt.github.io | GitHub Pages for Drupal WxT. | Size: 15010 Bytes |
helm-drupal | drupalwxt/helm-drupal | Helm Chart for deploying an enterprise-grade Drupal environment. | Size: 67695 Bytes |
site-wxt | drupalwxt/site-wxt | An example composer project for the Drupal WxT distribution used for integration testing. | Size: 4101 Bytes |
terraform-kubernetes-drupalwxt | drupalwxt/terraform-kubernetes-drupalwxt | Terraform module for Drupal WxT | Size: 35 Bytes |
themes-cdn | drupalwxt/themes-cdn | Content Delivery Network (CDN) files for the theme repositories of the Web Experience Toolkit (WET) | Size: 12445 Bytes |
wxt | drupalwxt/wxt | Drupal variant of the Web Experience Toolkit (WxT). | Size: 3482 Bytes |
wxt-project | drupalwxt/wxt-project | Composer project template for Drupal 9 sites built with the WxT distribution. | Size: 76 Bytes |
wxt_bootstrap | drupalwxt/wxt_bootstrap | Bootstrap derived sub-theme aligned for use with the Web Experience Toolkit jQuery Framework. | Size: 1163 Bytes |
wxt_library | drupalwxt/wxt_library | Web Experience Toolkit Framework integration for Drupal. | Size: 118 Bytes |
This is the multi-page printable view of this section. +Click here to print.
The following are links to some useful resources:
less than a minute
The following are links to some useful resources:
This is the multi-page printable view of this section. +Click here to print.
The core distribution will always strive to be:
Beyond the above the distribution will provide extensible features that can be opted into through the wxt_ext suite of modules:
modulename.wxt_extension.yml
file so can be enabled as optional extension during profile installationIn addition, Drupal WxT will offer out of tree (external) modules that implement specific features:
Note: The governance around the core distribution will always be much stricter then the governance around adding a
wxt_ext
or an out of tree module.
2 minute read
The core distribution will always strive to be:
Beyond the above the distribution will provide extensible features that can be opted into through the wxt_ext suite of modules:
modulename.wxt_extension.yml
file so can be enabled as optional extension during profile installationIn addition, Drupal WxT will offer out of tree (external) modules that implement specific features:
Note: The governance around the core distribution will always be much stricter then the governance around adding a
wxt_ext
or an out of tree module.
This is the multi-page printable view of this section. +Click here to print.
Drupal WxT relies on Drupal’s configuration system for configuring default features and functionality. A consequence of this is, once you have installed Drupal WxT, that we cannot modify the sites configuration without having an impact on your site. Drupal WxT will, however, offer to make changes to your configuration as part of the update process.
If you’ve installed WxT using our Composer-based project template, all you need to do is following the given steps below.
These are the typical steps you should following when updating Drupal WxT:
a) Read the release notes for the release to which you are updating along with any releases in between.
b) To update your WxT codebase you would replace [VERSION]
with the release version you wish to use.
composer self update
+composer require drupalwxt/wxt:[VERSION]
+composer update
+
Note: We highly recommend that you are using the v2.x.x line of Composer.
c) Run any database updates:
drush cache:rebuild
+drush updatedb
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run database updates.
d) Run any WxT configuration updates:
drush cache:rebuild
+drush update:wxt
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run WxT updates.
If you are using configuration management to move your configuration between development, staging, and production environments, you should follow the standard Drupal process.
a) Export the new configuration:
drush cache:rebuild
+drush config:export
+
b) Commit the code and configuration changes to your source code repository and push them to your environment.
c) Import any configuration changes:
drush cache:rebuild
+drush config:import
+
2 minute read
Drupal WxT relies on Drupal’s configuration system for configuring default features and functionality. A consequence of this is, once you have installed Drupal WxT, that we cannot modify the sites configuration without having an impact on your site. Drupal WxT will, however, offer to make changes to your configuration as part of the update process.
If you’ve installed WxT using our Composer-based project template, all you need to do is following the given steps below.
These are the typical steps you should following when updating Drupal WxT:
a) Read the release notes for the release to which you are updating along with any releases in between.
b) To update your WxT codebase you would replace [VERSION]
with the release version you wish to use.
composer self update
+composer require drupalwxt/wxt:[VERSION]
+composer update
+
Note: We highly recommend that you are using the v2.x.x line of Composer.
c) Run any database updates:
drush cache:rebuild
+drush updatedb
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run database updates.
d) Run any WxT configuration updates:
drush cache:rebuild
+drush update:wxt
+
Note: You may instead go to
/admin/config/development/performance
to clear caches and/update.php
to run WxT updates.
If you are using configuration management to move your configuration between development, staging, and production environments, you should follow the standard Drupal process.
a) Export the new configuration:
drush cache:rebuild
+drush config:export
+
b) Commit the code and configuration changes to your source code repository and push them to your environment.
c) Import any configuration changes:
drush cache:rebuild
+drush config:import
+
less than a minute
This user guide is for project teams who are using the Drupal WxT distribution.
The Drupal WxT distribution is +a web content management system which assists in building and +maintaining multilingual web sites that are accessible, usable, and interoperable.
This distribution complies with the mandatory requirement to implement the Content +and Information Architecture (C&IA) Specification as well as consulting the reference +implementation and +design patterns provided by the Canada.ca design +system.
This is accomplished through our integration and use of the components provided by the +Web Experience Toolkit which undergoes routine +usability testing as well as provides conformance to the Web Content Accessibility Guideline (WCAG 2.0) and +complies to the standards on Web +Accessibility, +Web Usability, +and Web +Interoperability.
").text(`No results found for query "${i}"`)):c.forEach(n=>{const i=s.get(n.ref),a=t.data("offline-search-base-href")+n.ref.replace(/^\//,""),o=e("
").text(i.excerpt)),r.append(o)}),o.one("shown.bs.popover",()=>{e(".td-offline-search-results__close-button").on("click",()=>{o.val(""),o.trigger("change")})});const l=new bootstrap.Popover(o,{content:a[0],html:!0,customClass:"td-offline-search-results",placement:"bottom"});l.show()}})}(jQuery),function(e){var t,n,o,s=!1;if(e(".mermaid").length>0&&(s=!0),!s){mermaid.initialize({startOnLoad:!1});return}o={enable:!0},t=function(e,n){var s={};for(const o in e){const i=o.toLowerCase();e.hasOwnProperty(o)&&n.hasOwnProperty(i)&&(typeof e[o]=="object"?s[o]=t(e[o],n[i]):s[o]=n[i])}return s},n=t(mermaid.mermaidAPI.defaultConfig,o),n.startOnLoad=!0,mermaid.initialize(n)}(jQuery)
\ No newline at end of file
diff --git a/js/prism.js b/js/prism.js
new file mode 100644
index 000000000..15f0f7825
--- /dev/null
+++ b/js/prism.js
@@ -0,0 +1,21 @@
+/* PrismJS 1.28.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+csharp+cpp+go+java+markdown+python+scss+sql+toml+yaml&plugins=toolbar+copy-to-clipboard */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(j >>u&g;if(_!==f>>>u&g)break;_&&(l+=(1<o&&(c=c.removeBefore(r,u,a-l)),c&&f i&&(i=c.size),a(u)||(c=c.map(function(e){return pe(e)})),r.push(c)}return i>e.size&&(e=e.setSize(i)),at(e,t,r)}function At(e){return e 2 minute read =lt)return function(e,t,n,r,o){for(var i=0,a=new Array(v),s=0;0!==n;s++,n>>>=1)a[s]=1&n?t[i++]:void 0;return a[r]=o,new We(e,i+1,a)}(e,f,c,s,d);if(l&&!d&&2===f.length&&tt(f[1^p]))return f[1^p];if(l&&d&&1===f.length&&tt(d))return d;var b=e&&e===this.ownerID,_=l?d?c:c^u:c|u,w=l?d?ut(f,p,d,b):function(e,t,n){var r=e.length-1;if(n&&t===r)return e.pop(),e;for(var o=new Array(r),i=0,a=0;as)return{value:void 0,done:!0};var e=o.next();return r||t===M?e:q(t,u-1,t===I?void 0:e.value[1],e)})},c}function Vt(e,t,n,r){var o=en(e);return o.__iterateUncached=function(o,i){var a=this;if(i)return this.cacheResult().__iterate(o,i);var s=!0,u=0;return e.__iterate(function(e,i,c){if(!s||!(s=t.call(n,e,i,c)))return u++,o(e,r?i:u-1,a)}),u},o.__iteratorUncached=function(o,i){var a=this;if(i)return this.cacheResult().__iterator(o,i);var s=e.__iterator(N,i),u=!0,c=0;return new U(function(){var e,i,l;do{if((e=s.next()).done)return r||o===M?e:q(o,c++,o===I?void 0:e.value[1],e);var p=e.value;i=p[0],l=p[1],u&&(u=t.call(n,l,i,a))}while(u);return o===N?e:q(o,i,l,e)})},o}function Ht(e,t){var n=s(e),o=[e].concat(t).map(function(e){return a(e)?n&&(e=r(e)):e=n?ae(e):se(Array.isArray(e)?e:[e]),e}).filter(function(e){return 0!==e.size});if(0===o.length)return e;if(1===o.length){var i=o[0];if(i===e||n&&s(i)||u(e)&&u(i))return i}var c=new ee(o);return n?c=c.toKeyedSeq():u(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce(function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}},0),c}function Wt(e,t,n){var r=en(e);return r.__iterateUncached=function(r,o){var i=0,s=!1;return function e(u,c){var l=this;u.__iterate(function(o,u){return(!t||cs&&(n=s-u),i=n;i>=0;i--){for(var p=!0,f=0;fo&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;ao)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var O=4096;function A(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;oTag: appsvc
Azure App Service
Categories: