A development workflow for Drupal 8 projects

Seamlessly create and maintain Drupal 8 projects within a team

Juampy NR


Disclaimer

I am writing this book openly in order to receive feedback as early as possible while giving credit to everyone who does it by listing them at the Acknowledgments section. The reason of this is that, while this is a highly opinionated book, I want to make it as useful as possible for new people who want to build projects with Drupal 8 so getting feedback from a diverse group would help immensely on such purpose.

Notice though that this is my book so I may choose not to make changes based on some of the feedback that I will get even though it makes sense to some people. However, I may mention it as an alternative approach and definitely will give credit at the acknowledgements section. Finally, be aware that at some point I will restrict access to this document and prepare it for publishing for selling online and on paper.

Acknowledgements

The following people (in no particular order) have helped in different ways into this book:

I also want to thank my employer, Lullabot, which is one of the main reasons why I love my job so much and I am so joyful.


Acknowledgements        2

1 Introduction        5

About me, Juampy        5

Why I am writing this book        6

Who is the target audience        6

Why do I think that it is useful        6

What is my approach like        6

What will be covered        7

2 Local setup        8

Available tools for local development        8

PHP and composer        9

Web server        11

Database server        12

Text editor / IDE        13

3 Project creation        15

Create the project with Composer        15

Install Drush        16

Install Drupal        17

Create a repository and push changes        19

Export configuration        23

Create and share a database dump        28

Prepare the local settings file        29

Document the setup in the README file        34

4 Development environment setup        39

Tools for environment management        39

Configure environment. Docker based ideally        39

Connect GitHub        39

Install database        39

Set up Drush alias        39

Update README        39

5 Define a development workflow        39

https://www.lullabot.com/articles/development-workflow-drupal-8-projects        39

Patching workflow        39

https://www.lullabot.com/articles/the-peer-review-howto-guide        39

Update README        39

6 Introduce Continuous Integration        39

Pull request checks        39

Auto deploy on merge        39

Checking for updates for core and contrib        40

Update README        40

https://www.lullabot.com/articles/continuous-integration-drupal-8-circleci        40

https://www.lullabot.com/articles/continuous-integration-in-drupal-8-with-travis-ci        40

7 Staging and Production environments setup        40

Configure and connect with repository and database        40

Set up a tag-based deployment job with… CircleCI ?        40

Clone Staging environment into Production        40

Set up Drush aliases        40

Update README        40

8 Running the whole workflow        40

Working on a ticket        40

Pull request creation        40

Peer review process        40

Preparing a release candidate for Staging        41

Deployment to Production        41

9 Conclusion        41


1 Introduction

About me, Juampy

Hi! This is Juampy. I am a Senior Developer at Lullabot, a design, strategy, and development company. We are distributed so there is no office. I live by the mountains of Madrid, Spain. Here is a bit about how I got here:

I was lazy at school. I just did not enjoy the learning process. However, my dad got me very early into playing golf and that was my escape route throughout my childhood and teens.

I did not love playing golf but I had fun doing it. When I wasn’t playing it or catching up with homework for school, I liked tinkering with my brother’s personal computer. He was learning 3D modeling and had installed Windows NT on it. Not many video games worked on Windows NT so there I was at thirteen years old trying to find my way out to get Command & Conquer: Red Alert to run there by browsing the Web. At some point I discovered that I enjoyed more crashing the system and fixing it (except from my brother’s fury) rather than getting the game to play.

It was later when I found my real passion, when I was seventeen and attended to my first programming class at university. It was a c++ subject given by a very motivated teacher that walked frantically up and down the class while speaking. I fell in love with coding. It seemed so logical to me. My dad was gladly surprised when he saw my first grades.

Years later I got a full time job as a web developer and I started building sites with PHP (also did some ASP before it but not much). And then, the evolution was moving to Symfony 1 where I spent a few years using. One day, Pablo Cerda, my manager at that time asked me to start a project that the client wanted it built with Drupal 6. At that time I just have read Pro Drupal Development by John VanDyk (who surprisingly I had the pleasure to work with). Pablo told me that he knew someone who lived in India and knew a lot about Drupal: Tushar Mahajan.

Pablo’s plan was that I would work both as a Project Manager and Developer and will learn Drupal via Tushar, who would do the implementation. My first impressions about Drupal were jaw-dropping: in just a week time we had a ton of the requirements met, and this was mostly by simply installing and configuring modules! Notice though, Tushar knew very well which modules to use, how to configure them, and what glue code was needed to extend them. I could see how he would not only read the source code of a module to understand it, but also reach out to its maintainers via IRC and chat with them. This was all new to me.

What came after those first experiences with Drupal changed my life. I got immersed into the code, met wonderful people, travelled the world, and got a job at Lullabot, my dream company in this field. All thanks to this tool to build websites. Thanks Drupal!

Here are a few ways to find me on the web:

Why I am writing this book

This is my third book. The first two were focused on Drush, a command line interface for Drupal projects. This time I am stepping forward and explaining how I set up and maintain a development workflow so a team can work efficiently. It’s a lot of fun!

This book is a set of best practices that I have used in Drupal 8 projects along with my peers at Lullabot. It’s an evolution of what we did with previous versions of Drupal plus new additions. The goal is to give you a starting point to quickly set up and run a Drupal 8 project within a team.

Who is the target audience

Individuals or development teams who are landing into Drupal 8 and need to start building a site as quickly as possible within a structured process.

Why do I think that it is useful

Drupal 8 is vast and evolves at a higher pace than previous versions did. The fact that new features can be introduced every 6 months in minor versions means that we developers have more chances to contribute to core and to discover new features that come by just upgrading. As I started working in Drupal 8 projects I discovered that whenever I needed to understand a system within Drupal I had to look into several sources like the official docs, then the actual source code, and finally a few blog posts. This is time consuming and requires a lot of practice in order to do it efficiently.

What is my approach like[a][b]

While the development workflow (ticketing, branching, README documentation, patching) is the result of past experience, the tooling is my personal preference given the options that are available at the time of this writing. Instead of going for the fastest and simplest infrastructure such as a Docker-based development environment, I am opting for a custom installation with its database server and web server. The reason is that by understanding how these two work with Drupal you are then able to apply this to any of the other tools available, which I will mention as well so you can see if you find something that you feel more comfortable with.

As for hosting, I am following the same approach as with my development environment: a custom set of servers where I will set up each of the environments. Again, I will also cover some of the alternative options available so you can explore and perhaps pick, but using the knowledge that I am sharing in this book as your foundation to understand how these hosting platforms leverage Drupal.

What will be covered

I am taking the simplest possible scenario so I can focus on the workflow[c][d][e]. In order to launch a Drupal 8 site to production we will:

  1. Set up a local environment.
  2. Push our code changes to GitHub.
  3. Set up a development environment and use CircleCI to auto-deploy code changes.
  4. Define a workflow so the team can work with minimum collisions.
  5. Set up a staging environment and use CircleCI to deploy release candidates.
  6. Set up a production environment and use CircleCI to deploy releases.
  7. Set up CircleCI jobs to update the database downstream from Production to Staging and to Development.

This concludes the introduction. We are now ready to start installing and configuring the required systems to run a Drupal project locally.


2 Local setup

Available tools for local development

At the time of this writing there is a long list of tools for doing local development. The Drupal Developer Guide classifies them in total solutions, Drupal modules, command line tools, browser tools, text editors, database tools, and other tools. Going through this list having no previous experience could take me a whole year in order to make my own choices. In order to save you time I will share here what do I use and what I have seen others use.

Since I started working with Drupal and development tools started to become mature, I have seen two kinds of developers:

  • The ones who like to have a simple stack locally, at the cost of harder customizations when having to maintain several projects with different dependencies such as PHP versions.
  • The ones who like to have an encapsulated environment per project, which make use of tools such as Vagrant for virtualization or Docker for containerization. While these approaches work great as they treat system configuration as code, they also add another layer of complexity to the stack.

Here is my take: I am the kind that installs projects locally. The main reason for this is that I usually work just on one project at a time so I don’t have issues with dependencies between projects. Notice that this does not necessarily mean that if I start on project A, which uses PHP 7.2 I would downgrade from 7.3. Instead, I would stick to the latest PHP release available and let the Continuous Integration system (we will cover it) to take care of finding bugs related with version disparity.

There are times though when I am working on a POC (proof of concept) with a technology that I don’t use everyday such as a Node.js app where I prefer to keep everything containerized so I can a) launch it locally and tear it down once I am done and b) launch it easily in a development environment or a hosting system such as Heroku. For this cases I usually go with a Docker based environment where I can install and launch the app.

In this book I am opting for the basic tools to install and use locally, same as for communicating with other environments such as development. The reason of this choice is that, while currently there are great tools available for local development such as DDEV and Lando, this ecosystem is changing at such a high pace that it is hard to track this in a book in a way that will persist valid for a reasonable time. Therefore, I am teaching here how to fish so then you can go “tool fishing” by yourself and, if you evaluate a tool, you would be able to understand better what it does and how it manages the stack that we will use.[f][g][h][i][j][k][l]

Finally, the operating system I use is Ubuntu (A Linux distribution based on Debian). If you are a Mac or Windows user, you will need to adjust some of the commands that I use in this book. Again, since I am using a basic stack for local development, it should be straightforward to find the alternative commands by just searching it on the Web.

The official documentation splits Drupal 8’s system requirements in three areas:

In the following sections I will give some tips for the above sections.

PHP and composer

We will start by installing the latest PHP version available plus Composer, a dependency manager. At the time of this writing, the latest Drupal core version available is 8.6.10 and the official documentation recommends installing PHP 7.3 for it.

Taking as a guide this blog post from RoseHosting, here are the commands that I ran to install PHP 7.3 in Ubuntu 18.04:

$ sudo apt update

$ sudo apt upgrade

$ sudo apt install software-properties-common

$ sudo add-apt-repository ppa:ondrej/php

$ sudo apt update

$ sudo apt install php7.3 php7.3-cli php7.3-common php7.3-opcache php7.3-curl php7.3-mbstring php7.3-mysql php7.3-zip php7.3-xml php7.3-gd wget

And here is the confirmation that now I have PHP running:

$ php -v

PHP 7.3.2-3+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Feb  8 2019 15:43:57) ( NTS )

Copyright (c) 1997-2018 The PHP Group

Zend Engine v3.3.2, Copyright (c) 1998-2018 Zend Technologies

    with Zend OPcache v7.3.2-3+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies

Now let’s install Composer, which is a dependency manager for PHP (like JavaScript’s npm or yarn). This time I am following this post from Digital Ocean. First we need to install a few libraries:

$ sudo apt install curl php-cli php-mbstring git unzip

Next we will download the installer’s signature and script:

$ EXPECTED_SIGNATURE= "$(wget -q -O - https://composer.github.io/installer.sig)"

$ curl -sS https://getcomposer.org/installer -o composer-setup.php

Here is how we can verify that the script’s signature matches the one we downloaded:

$ php -r "if (hash_file('SHA384', 'composer-setup.php') === '$EXPECTED_SIGNATURE') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

If the above command failed then the script’s integrity is not right and you should try downloading it again. Otherwise let’s run the installer:

$ sudo php composer-setup.php --install-dir=/usr/local/bin \
 --filename=composer

That was it. This is how we can check if Composer is ready to be used:

$ composer -v

   ______

  / ____/___  ____ ___  ____  ____  ________  _____

 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/

/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /

\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/

                    /_/

Composer version 1.8.4 2019-02-11 10:52:10

Usage:

  command [options] [arguments]

Composer can be slow when we have a long list of dependencies in the project. There is a plugin that speeds it up considerable called Prestissimo which we will install with the following command:

$ composer global require hirak/prestissimo

We now have installed the programming language that Drupal uses plus its dependency manager. Let’s move forward to the web server.

Web server

Per the official documentation: Drupal 8 works on any web server with a version of PHP that meets the PHP version requirements. We could go simple and just use the built-in web server that comes with PHP. While this is enough for a quick start, usually you will find Apache or NGINX in actual servers hosting a Drupal project. For this book I will go with Apache because I think that it is the most common one but if you prefer to use NGINX, you shouldn’t find any big challenges to do the same that we are doing here.

I found a blog post at Digital Ocean on how to install Apache at Ubuntu 18.04. First we need to execute the following command:

$ sudo apt install apache2

Next, Here is how we allow Apache to listen on port 80 through the local firewall. This is new to me because I have not seen it in previous versions than Ubuntu 18.04:

$ sudo ufw allow 'Apache'

And here is how we start the web server:

$ sudo service apache2 start

 * Starting Apache httpd web server apache2                                                                                                                                                                         AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message

While is not necessary for our case, you can fix the issue reported in the above message by following the instructions at this post from Stack Overflow.

Drupal needs Apache’s rewrite module to be activated in order to use URL aliases. Here is how can we enable it:

$ sudo a2enmod rewrite

Enabling module rewrite.

To activate the new configuration, you need to run:

  service apache2 restart

$ sudo service apache2 restart

                     

We will come back to the Apache configuration when we need to create a site alias for our Drupal site. Next in the list is the database server.

Database server

I ran a poll on Twitter asking the Drupal community what was their preferred database server for local development and discovered that 59% use MariaDB, followed by 35% who use MySQL. Based on that, even though I normally use MySQL just because it comes installed with Ubuntu, I am giving MariaDB a go in this book.

To install MariaDB in Ubuntu 18.04 I am following this blog post from Linuxsize. Here are the commands:

$ sudo apt install mariadb-server

And then we can verify it with the following command:

$mysql --version

mysql  Ver 15.1 Distrib 10.1.38-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2

That’s it! Way easier than what I expected. Before we move on, let’s set a password for the MariaDB’s root user with the following assistant which will also ask us a few useful security questions to configure the installation:

$ sudo mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB

      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current

password for the root user.  If you've just installed MariaDB, and

you haven't set the root password yet, the password will be blank,

so you should just press enter here.

Enter current password for root (enter for none):

OK, successfully used password, moving on...

Setting the root password ensures that nobody can log into the MariaDB

root user without the proper authorisation.

Set root password? [Y/n] Y

New password:

Re-enter new password:

Password updated successfully!

Reloading privilege tables..

 ... Success!

Finally, in order to allow Apache’s www-data user to connect to the Drupal database using the root user[m][n][o] (normally I do this on my local environment just for simplicity) we need to connect to the MariaDB console and run a couple statements:

$  mysql -u root -p

Enter password:

Welcome to the MariaDB monitor.  Commands end with ; or \g.

MariaDB [(none)]> use mysql;

Database changed

MariaDB [mysql]> update user set plugin='' where User='root';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [mysql]> flush privileges;

Query OK, 0 rows affected (0.00 sec)

MariaDB [mysql]> exit

Bye

$ service mysql restart

 * Stopping MariaDB database server mysqld                                                                                                                                                                  

 * Starting MariaDB database server mysqld                   

Text editor / IDE

IDE stands for Integrated Development Environment. Writing about IDEs could take a whole book so I will summarize what I have found out since I started working with Drupal 8.

Drupal 7, being function oriented, didn't require of a powerful IDE to navigate through its code. I used to use VIM + a bunch of plugins and it felt great. However, once I started contributing to Drupal 8’s core and contributed modules, I struggled to grok its Object Oriented Design. That’s when I started listening to the folks that encouraged me to give PhpStorm a try. The results surpassed my expectations. I dare to say that in order for me to work efficiently in Drupal 8 projects, I need to have PhpStorm.

You can find a list of alternative text editors and IDEs at the official documentation but my advice to you is to install PhpStorm and enable the 30 day trial. Notice that JetBrains (the company behind PhpStorm) offers free licenses to people who contribute to the Drupal project issue queue.

This concludes the local setup. We now have the basics for creating a Drupal project, which we will do in the next chapter.


3 Project creation

Create the project with Composer

The official documentation mentions a few ways to download and create the scaffolding of a Drupal 8 project. At the time of this writing the recommended one is using the Composer Template, which does additional things that save us time:

  • Downloads Drupal 8 core in its own web directory.
  • Creates a directory to store Drush commands and site configuration.
  • Sets up Composer to manage dependencies.

Here is how we create the project with Composer Template. We will build a sample project called d8workflow which we will evolve throughout the book. Usually, web projects served by Apache are placed under /var/www. This is a protected directory so we will first build the scaffolding in the temporary directory and then move the resulting directory using sudo into /var/www:

$ cd /tmp

$ composer create-project drupal-composer/drupal-project:8.x-dev d8workflow --no-interaction

Installing drupal-composer/drupal-project (8.x-dev 4229acea4bb6181f421e8dd0e72a02ccdc98df1d)

  - Installing drupal-composer/drupal-project (8.x-dev 4229ace): Cloning 4229acea4b from cache

Created project in d8workflow

> DrupalProject\composer\ScriptHandler::checkComposerVersion

Loading composer repositories with package information

Updating dependencies (including require-dev)

> DrupalProject\composer\ScriptHandler::createRequiredFiles

Create a sites/default/settings.php file with chmod 0666

Create a sites/default/files directory with chmod 0777

$ sudo mv /tmp/d8workflow /var/www/

The above command has a long output as it informs which versions of all the dependencies of Drupal 8 is installing. I added an ellipsis above to skip that bit until the last few lines which I want to highlight because they lay the groundwork for the next step: installing Drush.

Install Drush

Before installing Drupal we will install and configure Drush: Drupal’s command line interface, as it will let us manage the project via the command line. I you have never heard of Drush before, I wrote a whole book about it: Drush for developers.

Here is how we download Drush as a dependency of the project that we created in the previous section:

$ cd /var/www/d8workflow

$ composer require drush/drush

Using version ^9.5 for drush/drush

./composer.json has been updated

> DrupalProject\composer\ScriptHandler::checkComposerVersion

Loading composer repositories with package information

Updating dependencies (including require-dev)

Nothing to install or update

Writing lock file

Generating autoload files

> DrupalProject\composer\ScriptHandler::createRequiredFiles

The above command has installed Drush at /var/www/d8workflow/vendor/bin/drush. We could move on to installing Drupal but the downside would be that every time that we want to execute a Drush command we would have to call it like vendor/bin/drush some-command. The Drush team created a handy project called Drush Launcher, a wrapper for Drush that allows us executing Drush commands by just typing “drush some-command”. Here is how we install Drush Launcher:

$ wget -O drush.phar https://github.com/drush-ops/drush-launcher/releases/download/0.6.0/drush.phar

$ chmod +x drush.phar

$ sudo mv drush.phar /usr/local/bin/drush

Here is how we can verify that Drush is ready to be used:

$ drush --version

Drush Launcher Version: 0.6.0

Drush Commandline Tool 9.5.2

Drush is ready so let’s use it to install Drupal.

Install Drupal

Drush has a command for installing Drupal: site:install. This command will create and configure the database plus the file system. You may have to adjust the database URL in the following command, which follows the structure mysql://user:password/hostname/database. I am using MariaDB’s root user (whose password I have set to “root” too):

$ drush site:install --db-url=mysql://root:root@localhost/d8workflow --site-name="d8workflow"

 You are about to CREATE the 'd8workflow' database. Do you want to continue? (yes/no) [yes]:

 > yes

 [notice] Starting Drupal installation. This takes a while.

 [success] Installation complete.  User name: admin  User password: some-password

Installation completed. Here is how we can verify, using Drush, that Drupal is running:

$ drush core:status

 Drupal version   : 8.6.10                                          

 Site URI         : default                                        

 DB driver        : mysql                                          

 DB hostname      : localhost                                      

 DB port          :                                                

 DB username      : root                                            

 DB name          : d8workflow                                      

 Database         : Connected                                      

 Drupal bootstrap : Successful                                      

 Default theme    : bartik                                          

 Admin theme      : seven                                          

 PHP binary       : /usr/bin/php7.3                                

 PHP config       : /etc/php/7.3/cli/php.ini                        

 PHP OS           : Linux                                          

 Drush script     : /usr/local/bin/drush                            

 Drush version    : 9.5.2                                          

 Drush temp       : /tmp                                            

 Drush configs    : /var/www/d8workflow/vendor/drush/drush/drush.yml

                    /var/www/d8workflow/drush/drush.yml              

 Install profile  : standard                                        

 Drupal root      : /var/www/d8workflow/web                          

 Site path        : sites/default                                  

 Files, Public    : sites/default/files                            

 Files, Temp      : /tmp           

In order to browse the site in the browser, we need to configure a virtual host at the Apache web server and then adjust the hosts file so the web browser will look for http://d8workflow.local in our local environment and not out there in the Web. First we will create and enable the site alias in Apache at /etc/apache2/sites-available. Notice that this directory is owned by the root user so you will need admin permissions to write into it. I usually do this using the VIM editor but you can use anything as long as it :

sudo vim /etc/apache2/sites-available/d8workflow.local.conf

Here are the contents that we need to save into this file:

<VirtualHost *:80>

  DocumentRoot /var/www/d8workflow/web

  ServerName d8workflow.local

  RewriteEngine On

  <Directory /var/www/d8workflow/web>

    Options +FollowSymLinks +Indexes

    AllowOverride All

    Require all granted

  </Directory>

</VirtualHost>

Next, enable the site alias and reload the Apache service:

$ sudo a2ensite d8workflow.local.conf

Enabling site d8workflow.local.

To activate the new configuration, you need to run:

  service apache2 reload

$ sudo service apache2 reload

 * Reloading Apache httpd web server apache2

Finally, in order to be able to browse the site locally we need to add the following line at the end of the /etc/hosts file:

$ sudo echo "127.0.0.1 d8workflow.local" >> /etc/hosts

Finally, open http://d8workflow.local in a web browser. Here is what I got:

Drupal is running! Let’s start preparing the project so the rest of the team can set up their local environments too.

Create a repository and push changes

We will use GitHub, a hosting service for version control, to store the source code, site configuration, and documentation. Here I am creating the repository:

After creating it, the next screen explains how to push the project files to it:

Let’s initiate and configure the repository locally:

$ cd /var/www/d8workflow

$ git init

Initialized empty Git repository in /var/www/d8workflow/.git/

$ git add .

$ git commit -m "Drupal 8 barebones installation"

[master (root-commit) 405433d] Drupal 8 barebones installation

 38 files changed, 10616 insertions(+)

 create mode 100644 .editorconfig

 create mode 100644 .env.example

 create mode 100644 .gitattributes

 create mode 100644 .gitignore

 create mode 100644 .travis.yml

 create mode 100644 LICENSE

 create mode 100644 README.md

 create mode 100644 composer.json

 create mode 100644 composer.lock

 create mode 100644 config/sync/.htaccess

 create mode 100644 config/sync/README.txt

 create mode 100644 drush/Commands/PolicyCommands.php

 create mode 100644 drush/README.md

 create mode 100644 drush/drush.yml

 create mode 100644 drush/sites/self.site.yml

 create mode 100644 load.environment.php

 create mode 100644 phpunit.xml.dist

 create mode 100644 scripts/composer/ScriptHandler.php

 create mode 100644 web/.csslintrc

 create mode 100644 web/.editorconfig

 create mode 100644 web/.eslintignore

 create mode 100644 web/.eslintrc.json

 create mode 100644 web/.gitattributes

 create mode 100644 web/.ht.router.php

 create mode 100644 web/.htaccess

 create mode 100644 web/autoload.php

 create mode 100644 web/index.php

 create mode 100644 web/modules/.gitkeep

 create mode 100644 web/profiles/.gitkeep

 create mode 100644 web/robots.txt

 create mode 100755 web/sites/default/default.services.yml

 create mode 100755 web/sites/default/default.settings.php

 create mode 100644 web/sites/development.services.yml

 create mode 100644 web/sites/example.settings.local.php

 create mode 100644 web/sites/example.sites.php

 create mode 100644 web/themes/.gitkeep

 create mode 100644 web/update.php

 create mode 100644 web/web.config

Once we have initialised the git repository and committed the files, let’s connect the repository with GitHub and push our changes:

$ git remote add origin git@github.com:juampynr/d8workflow.git

$ git push -u origin master

Counting objects: 47, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (39/39), done.

Writing objects: 100% (47/47), 72.43 KiB | 0 bytes/s, done.

Total 47 (delta 1), reused 0 (delta 0)

remote: Resolving deltas: 100% (1/1), done.

To git@github.com:juampynr/d8workflow.git

 * [new branch]      master -> master

Branch master set up to track remote branch master from origin.

We are done! If we are working on a team, its members can now clone the repository to get the source code into their local environments. In order to get the site running though, they will also need the site configuration, the database, and the installation instructions. We will work on these in the following sections.

Export configuration

Drupal 8 ships with a solid configuration management system. It helps to keep track of configuration changes so they can be exported from one site such as our local environment and imported onto another such as the production environment.

In order to start this process we need to export the resulting configuration from installing Drupal locally. Since we created the project with the Composer template, we just need to run a Drush command to get all the site configuration exported into the config/sync subdirectory. Let’s do it:

$ cd /var/www/d8workflow

$ drush config-export

Differences of the active config to the export directory:

+------------------------------------------------------+-----------+

| Config                                               | Operation |

+------------------------------------------------------+-----------+

| block.block.bartik_local_actions                     | Create    |

| block.block.bartik_local_tasks                       | Create    |

| block.block.bartik_page_title                        | Create    |

| block_content.type.basic                             | Create    |

| comment.type.comment                                 | Create    |

| core.date_format.fallback                            | Create    |

| core.date_format.html_date                           | Create    |

| core.date_format.html_datetime                       | Create    |

| core.date_format.html_month                          | Create    |

| core.date_format.html_time                           | Create    |

| core.date_format.html_week                           | Create    |

| core.date_format.html_year                           | Create    |

| core.date_format.html_yearless_date                  | Create    |

| core.date_format.long                                | Create    |

| core.date_format.medium                              | Create    |

| core.date_format.short                               | Create    |

| core.entity_view_mode.block_content.full             | Create    |

| core.entity_view_mode.comment.full                   | Create    |

| core.extension                                       | Create    |

| core.menu.static_menu_link_overrides                 | Create    |

| dblog.settings                                       | Create    |

| field.settings                                       | Create    |

| file.settings                                        | Create    |

| block.block.bartik_help                              | Create    |

| image.settings                                       | Create    |

| image.style.large                                    | Create    |

| image.style.medium                                   | Create    |

| image.style.thumbnail                                | Create    |

| field.storage.node.field_image                       | Create    |

| field.storage.node.comment                           | Create    |

| core.entity_view_mode.node.teaser                    | Create    |

| core.entity_view_mode.node.search_result             | Create    |

| core.entity_view_mode.node.search_index              | Create    |

| core.entity_view_mode.node.rss                       | Create    |

| core.entity_view_mode.node.full                      | Create    |

| node.settings                                        | Create    |

| node.type.article                                    | Create    |

| field.field.node.article.field_image                 | Create    |

| field.field.node.article.comment                     | Create    |

| node.type.page                                       | Create    |

| core.base_field_override.node.page.promote           | Create    |

| rdf.mapping.comment.comment                          | Create    |

| rdf.mapping.node.article                             | Create    |

| rdf.mapping.node.page                                | Create    |

| block.block.bartik_search                            | Create    |

| search.page.node_search                              | Create    |

| search.settings                                      | Create    |

| block.block.seven_secondary_local_tasks              | Create    |

| block.block.seven_primary_local_tasks                | Create    |

| block.block.seven_page_title                         | Create    |

| block.block.seven_local_actions                      | Create    |

| block.block.seven_help                               | Create    |

| seven.settings                                       | Create    |

| block.block.seven_messages                           | Create    |

| block.block.seven_content                            | Create    |

| block.block.seven_breadcrumbs                        | Create    |

| block.block.bartik_powered                           | Create    |

| block.block.bartik_messages                          | Create    |

| block.block.bartik_content                           | Create    |

| block.block.bartik_breadcrumbs                       | Create    |

| block.block.bartik_branding                          | Create    |

| system.action.comment_delete_action                  | Create    |

| system.action.comment_publish_action                 | Create    |

| system.action.comment_save_action                    | Create    |

| system.action.comment_unpublish_action               | Create    |

| system.action.node_delete_action                     | Create    |

| system.action.node_make_sticky_action                | Create    |

| system.action.node_make_unsticky_action              | Create    |

| system.action.node_promote_action                    | Create    |

| system.action.node_publish_action                    | Create    |

| system.action.node_save_action                       | Create    |

| system.action.node_unpromote_action                  | Create    |

| system.action.node_unpublish_action                  | Create    |

| system.authorize                                     | Create    |

| system.cron                                          | Create    |

| system.date                                          | Create    |

| system.diff                                          | Create    |

| system.file                                          | Create    |

| system.image                                         | Create    |

| system.image.gd                                      | Create    |

| system.logging                                       | Create    |

| system.mail                                          | Create    |

| system.maintenance                                   | Create    |

| system.menu.account                                  | Create    |

| block.block.bartik_account_menu                      | Create    |

| system.menu.admin                                    | Create    |

| system.menu.footer                                   | Create    |

| block.block.bartik_footer                            | Create    |

| system.menu.main                                     | Create    |

| block.block.bartik_main_menu                         | Create    |

| system.menu.tools                                    | Create    |

| block.block.bartik_tools                             | Create    |

| system.performance                                   | Create    |

| system.rss                                           | Create    |

| system.site                                          | Create    |

| system.theme                                         | Create    |

| system.theme.global                                  | Create    |

| field.storage.node.field_tags                        | Create    |

| core.entity_view_mode.taxonomy_term.full             | Create    |

| taxonomy.settings                                    | Create    |

| taxonomy.vocabulary.tags                             | Create    |

| rdf.mapping.taxonomy_term.tags                       | Create    |

| field.field.node.article.field_tags                  | Create    |

| field.storage.node.body                              | Create    |

| field.storage.comment.comment_body                   | Create    |

| field.storage.block_content.body                     | Create    |

| field.field.node.page.body                           | Create    |

| field.field.node.article.body                        | Create    |

| field.field.comment.comment.comment_body             | Create    |

| field.field.block_content.basic.body                 | Create    |

| core.entity_view_display.comment.comment.default     | Create    |

| core.entity_view_display.block_content.basic.default | Create    |

| core.entity_form_display.node.page.default           | Create    |

| core.entity_form_display.node.article.default        | Create    |

| core.entity_form_display.comment.comment.default     | Create    |

| core.entity_form_display.block_content.basic.default | Create    |

| text.settings                                        | Create    |

| system.action.user_unblock_user_action               | Create    |

| system.action.user_cancel_user_action                | Create    |

| system.action.user_block_user_action                 | Create    |

| search.page.user_search                              | Create    |

| rdf.mapping.user.user                                | Create    |

| field.storage.user.user_picture                      | Create    |

| field.field.user.user.user_picture                   | Create    |

| core.entity_view_mode.user.full                      | Create    |

| core.entity_view_mode.user.compact                   | Create    |

| core.entity_view_display.user.user.default           | Create    |

| core.entity_view_display.user.user.compact           | Create    |

| core.entity_view_display.node.page.teaser            | Create    |

| core.entity_view_display.node.page.default           | Create    |

| core.entity_view_display.node.article.teaser         | Create    |

| core.entity_view_display.node.article.rss            | Create    |

| core.entity_view_display.node.article.default        | Create    |

| core.entity_form_mode.user.register                  | Create    |

| core.entity_form_display.user.user.default           | Create    |

| block.block.seven_login                              | Create    |

| user.flood                                           | Create    |

| user.mail                                            | Create    |

| user.role.administrator                              | Create    |

| system.action.user_remove_role_action.administrator  | Create    |

| system.action.user_add_role_action.administrator     | Create    |

| user.role.anonymous                                  | Create    |

| user.role.authenticated                              | Create    |

| user.settings                                        | Create    |

| views.settings                                       | Create    |

| views.view.archive                                   | Create    |

| views.view.block_content                             | Create    |

| views.view.comment                                   | Create    |

| views.view.comments_recent                           | Create    |

| views.view.content                                   | Create    |

| views.view.content_recent                            | Create    |

| views.view.files                                     | Create    |

| filter.format.basic_html                             | Create    |

| views.view.frontpage                                 | Create    |

| editor.editor.basic_html                             | Create    |

| views.view.glossary                                  | Create    |

| filter.format.full_html                              | Create    |

| views.view.taxonomy_term                             | Create    |

| editor.editor.full_html                              | Create    |

| views.view.user_admin_people                         | Create    |

| contact.form.feedback                                | Create    |

| filter.format.plain_text                             | Create    |

| views.view.watchdog                                  | Create    |

| contact.form.personal                                | Create    |

| filter.format.restricted_html                        | Create    |

| views.view.who_s_new                                 | Create    |

| automated_cron.settings                              | Create    |

| contact.settings                                     | Create    |

| field_ui.settings                                    | Create    |

| filter.settings                                      | Create    |

| menu_ui.settings                                     | Create    |

| shortcut.set.default                                 | Create    |

| tour.tour.views-ui                                   | Create    |

| update.settings                                      | Create    |

| views.view.who_s_online                              | Create    |

+------------------------------------------------------+-----------+

 The .yml files in your export directory (../config/sync) will be deleted and replaced with the active config. (yes/no) [yes]:

 > yes

 [success] Configuration successfully exported to ../config/sync.

Next, we will commit and push these changes to GitHub:

$ git add config

$ git commit -m "Add exported site configuration files"

[master 1239d78] Add exported site configuration files

 175 files changed, 10974 insertions(+)

 create mode 100644 config/sync/automated_cron.settings.yml

 create mode 100644 config/sync/block.block.bartik_account_menu.yml

 create mode 100644 config/sync/block.block.bartik_branding.yml

 create mode 100644 config/sync/block.block.bartik_breadcrumbs.yml

 ...

 create mode 100644 config/sync/views.view.user_admin_people.yml

 create mode 100644 config/sync/views.view.watchdog.yml

 create mode 100644 config/sync/views.view.who_s_new.yml

 create mode 100644 config/sync/views.view.who_s_online.yml

$ git push origin master

Counting objects: 179, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (178/178), done.

Writing objects: 100% (179/179), 70.43 KiB | 0 bytes/s, done.

Total 179 (delta 26), reused 0 (delta 0)

remote: Resolving deltas: 100% (26/26), completed with 1 local object.

To git@github.com:juampynr/d8workflow.git

   405433d..1239d78  master -> master

Now we have added the site configuration to version control. This opens the door to the team to make changes to the site configuration in a way that can be tested and then deployed to other environments. We will see examples of this further down the line in this book.

Create and share a database dump

The source code and the site configuration are now in the repository ready to be used. The remaining items needed to give the rest of the team full control of the project is creating and sharing a database dump and finally documenting the installation instructions. We will deal with the database dump in this section while the documentation will take place in the next section.

Ideally, the development team should be able to download and install a sanitized copy of the development environment database into their local environment via the drush sql:sync command. However, we haven’t set up the development environment yet and we definitely don’t want to make the team wait for that so let’s be creative and temporarily share with them a database dump somewhere safe. In the past I have used a server directory whose access was protected via SSH. Lately, I have simply created and shared a folder in Dropbox, a file hosting service. As you can restrict access just to the development team, anything else will do.

Here is how we can dump the database into a compressed file:

$ cd /var/www/d8workflow

$ drush sql:dump --gzip > d8workflow.sql.gz

Next would be, in my case, to create a Dropbox folder, upload the file there, and share it with the team. It’s up to you to go this route or use a different approach. Keep in mind that this is a temporary solution since in the next chapter we will set up a development environment so the team will be able to download a database dump directly from there.

Prepare the local settings file

By default, the web/sites/default/settings.php file, responsible for defining configuration such as the database connection details, caching, configuration directories, and such, is not under version control. This is a security measure that, while safe, it makes it harder to define a few default settings that can be used by the team and also when we are building the project via a script in later chapters.

Fortunately, the settings file has a mechanism for overriding and extending it’s settings via a file usually named settings.local.php. Below we can see the last few lines of the settings.php, which contain the documentation of how to enable the settings.local.php file, and the database connection details:

/**

 * Load local development override configuration, if available.

 *

 * Use settings.local.php to override variables on secondary (staging,

 * development, etc) installations of this site. Typically used to disable

 * caching, JavaScript/CSS compression, re-routing of outgoing emails, and

 * other things that should not happen on development and testing sites.

 *

 * Keep this code block at the end of this file to take full effect.

 */

#

# if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {

#   include $app_root . '/' . $site_path . '/settings.local.php';

# }

$config_directories['sync'] = '../config/sync';

$databases['default']['default'] = array (

  'database' => 'd8workflow',

  'username' => 'root',

  'password' => 'root',

  'prefix' => '',

  'host' => 'localhost',

  'port' => '',

  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',

  'driver' => 'mysql',

);

We will make the following changes:

  • Move the $config_directories['sync'] = '../config/sync'; statement before loading settings.local.php so it can be overridden if necessary.
  • Uncomment the three lines that check and load the settings.local.php if it is present.
  • Remove the database connection details and place them into web/sites/example.settings.local.php.

Here is how settings.php looks like after making the above changes:

$config_directories['sync'] = '../config/sync';

/**

 * Load local development override configuration, if available.

 *

 * Use settings.local.php to override variables on secondary (staging,

 * development, etc) installations of this site. Typically used to disable

 * caching, JavaScript/CSS compression, re-routing of outgoing emails, and

 * other things that should not happen on development and testing sites.

 *

 * Keep this code block at the end of this file to take full effect.

 */

#

if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {

  include $app_root . '/' . $site_path . '/settings.local.php';

}

And here are the last few lines of example.settings.local.php[p][q][r]:

/**

 * Skip file system permissions hardening.

 *

 * The system module will periodically check the permissions of your site's

 * site directory to ensure that it is not writable by the website user. For

 * sites that are managed with a version control system, this can cause problems

 * when files in that directory such as settings.php are updated, because the

 * user pulling in the changes won't have permissions to modify files in the

 * directory.

 */

$settings['skip_permissions_hardening'] = TRUE;

// Default database connection settings.

$databases['default']['default'] = array (

  'database' => 'd8workflow',

  'username' => 'root',

  'password' => 'root',

  'prefix' => '',

  'host' => 'localhost',

  'port' => '',

  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',

  'driver' => 'mysql',

);

Right, we have activated the mechanism for overriding the settings defined at web/sites/default/settings.php via a web/sites/default/settings.local.php file and we have moved the database connection details to the sample file web/example.settings.local.php. Now we can stop ignoring settings.php in the repository. To do so, we will remove the following line from the .gitignore file:

# Ignore directories generated by Composer

/drush/contrib/

/vendor/

/web/core/

/web/modules/contrib/

/web/themes/contrib/

/web/profiles/contrib/

/web/libraries/

# Ignore sensitive information

/web/sites/*/settings.php

/web/sites/*/settings.local.php

# Ignore Drupal's file directory

/web/sites/*/files/

# Ignore SimpleTest multi-site environment.

/web/sites/simpletest

# Ignore files generated by PhpStorm

/.idea/

# Ignore .env files as they are personal

/.env

With the above set up, in order to override the default settings defined at settings.php, one would have to copy web/sites/example.settings.local.php into web/sites/default/settings.local.php. This is something that everyone in the team will have to do so let’s automate it via Composer. If we look at the composer.json file, it has a section that defines which scripts should run after running certain commands:

  "scripts": {

    "pre-install-cmd": [

      "DrupalProject\\composer\\ScriptHandler::checkComposerVersion"

    ],

    "pre-update-cmd": [

      "DrupalProject\\composer\\ScriptHandler::checkComposerVersion"

    ],

    "post-install-cmd": [

      "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"

    ],

    "post-update-cmd": [

      "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"

    ]

  },

I am interested in the highlighted script, which runs after the composer install command. This command will be in the installation instructions so let’s take the chance to tweak it so it copies web/sites/example.settings.local.php into web/sites/default/settings.local.php. Here are the lines that we need to add to scripts/composer/ScriptHandler.php:

class ScriptHandler {

  public static function createRequiredFiles(Event $event) {

    // Skipped a few lines for readability.

    // Create the files directory with chmod 0777

    if (!$fs->exists($drupalRoot . '/sites/default/files')) {

      $oldmask = umask(0);

      $fs->mkdir($drupalRoot . '/sites/default/files', 0777);

      umask($oldmask);

      $event->getIO()->write("Create a sites/default/files directory with chmod 0777");

    }

    // Copy example.settings.local.php into settings.local.php.

    if (!$fs->exists($drupalRoot . '/sites/default/settings.local.php')) {

      $fs->copy($drupalRoot . '/sites/example.settings.local.php',

        $drupalRoot . '/sites/default/settings.local.php');

      $event->getIO()

        ->write('Created a web/sites/default/settings.local.php file. Review the database details in this file.');

    }

  }

That’s it! We have laid the groundwork for a seamless installation. Let’s wrap up this section by committing and pushing the changes that we made:

$ git st

On branch master

Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .gitignore

        modified:   scripts/composer/ScriptHandler.php

        modified:   web/sites/example.settings.local.php

Untracked files:

  (use "git add <file>..." to include in what will be committed)

        d8workflow.sql.gz

        web/sites/default/settings.php

no changes added to commit (use "git add" and/or "git commit -a")

$ git add .gitignore scripts web/sites

$ git commit -m "Add settings.php and create settings.local.php on composer install"

[master bc1f962] Add settings.php and create settings.local.php on composer install

 4 files changed, 792 insertions(+), 1 deletion(-)

 create mode 100755 web/sites/default/settings.php

$ git push origin master

Counting objects: 11, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (9/9), done.

Writing objects: 100% (11/11), 11.53 KiB | 0 bytes/s, done.

Total 11 (delta 6), reused 0 (delta 0)

remote: Resolving deltas: 100% (6/6), completed with 6 local objects.

To git@github.com:juampynr/d8workflow.git

   303cb62..0d99890  master -> master

In the next section we will document how to set up this project.

Document the setup in the README file

We have the basics in place. In order to make it easy for the team to get their local environment set up we will create a README.md file at the root of the repository that contains the installation instructions.

The goal of the README is to be the starting point for developers. They should find in it:

  • Where is the project documentation.
  • How to set up the project locally.

This document usually lives at the root of the repository and is written in markdown format, hence its filename is README.md. Here is the first version of our README.md:

# Drupal 8 workflow

This repository contains a Drupal 8 installation which serves as a

sample project for the book "A development workflow for Drupal 8

projects".

## Requirements

* PHP 7.3 or higher[s][t]

* Composer 1.7.1 or higher

* MySQL/MariaDB

* Apache

* Drush launcher

## Installation

1. Clone this repository and install its dependencies:

```

git clone git@github.com:juampynr/d8workflow.git

composer install

```

2. Review and adjust the database credentials at `web/sites/default/settings.local.php`.

3. Download the database dump from [Insert URL or instructions here].

4. Decompress the database dump, create the database, and import the database dump into it:

```

gunzip d8workflow.sql.gz

drush sql:create

drush sql:cli < d8workflow.sql

```

5. Verify that Drupal can bootstrap with `vendor/bin/drush core:status`.

6. Set up the web server so it points to the `web` subdirectory.

7. Execute `vendor/bin/drush user:login` to obtain an admin login URL. Adjust the hostname and open it with the web browser.

Next, we will commit and push this file to GitHub:

$ git add README.md

$ git commit -m “Add README.md with installation instructions”

[master 303cb62] Add README.md with installation instructions

 1 file changed, 45 insertions(+), 145 deletions(-)

 rewrite README.md (99%)

$ git push origin master

Counting objects: 3, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (3/3), done.

Writing objects: 100% (3/3), 889 bytes | 0 bytes/s, done.

Total 3 (delta 1), reused 0 (delta 0)

remote: Resolving deltas: 100% (1/1), completed with 1 local object.

To git@github.com:juampynr/d8workflow.git

   1239d78..303cb62  master -> master

We are done! We have all the basics in the repository so at this point we can notify the team that they can find the repository at https://github.com/juampynr/d8workflow where they will find the installation instructions by just scrolling down a bit. When they open the above URL, they will see the following:

As you can see, GitHub identifies the README.md file and renders it underneath the file browser. This wraps up the chapter. In the next one, we will set up the development environment.


4 Development environment setup[u]

There are many ways of setting up development environments. At the time or this writing, servers can be configured and maintained manually, via a virtual box, or via Docker. This book is about setting up a development workflow so the focus is on process and continuous integration and less into server architecture. I want to favor treating architecture as code so for the development environment I will use a Linode server with Ubuntu 18.04 and then use Docker Compose to define the architecture.

I start by creating an account at Linode and then creating a new Linode (that’s how they call a server):

Next, I log into the server as root:

$ ssh root@some-ip

We need to install Docker so we can build the environment:

root@localhost:~# snap install docker

2019-04-04T20:08:15Z INFO Waiting for restart...

docker 18.06.1-ce from Canonical✓ installed

Next is cloning the repository. Even though is public, I will create a Personal Access Token as it is more realistic when working for projects which are hosted in private repositories. Here is the form where I create the token:

After submitting the above form, we will get the token which we will copy and use to clone the repository within the development environment like this:

$ git clone https://[personal-access-token]@github.com/juampynr/d8workflow.git

Cloning into 'd8workflow'...

remote: Enumerating objects: 261, done.

remote: Counting objects: 100% (261/261), done.

remote: Compressing objects: 100% (208/208), done.

remote: Total 261 (delta 47), reused 254 (delta 40), pack-reused 0

Receiving objects: 100% (261/261), 149.82 KiB | 1.97 MiB/s, done.

Resolving deltas: 100% (47/47), done.

The repository has a docker-compose.yml file that defines the set of Docker images that will run the project. Here are the contents:

version: "3"

services:

  mariadb:

    image: mariadb:10.4

    container_name: mariadb

    stop_grace_period: 30s

    environment:

      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}

      - MYSQL_PASSWORD=${MYSQL_PASSWORD}

      - MYSQL_USER=${MYSQL_USER}

      - MYSQL_DATABASE=${MYSQL_DATABASE}

    volumes:

      - ./mariadb-init:/docker-entrypoint-initdb.d

      - ./var/lib/mysql:/var/lib/mysql

  php:

    image: localphp:latest

    container_name: php

    ports:

      - "80:80"

    volumes:

      - ./:/var/www/html

The above file defines a MariaDB image that hosts the database and a custom image that extends from the official Drupal image and installs a few useful tools such as Composer, Git, VIM, and Drush Launcher.

The environment section above references a few environment variables. These will be picked up automatically from an .env file at the root of the repository. Let’s create this file and enter the variables and their values:

MYSQL_ROOT_PASSWORD=root

MYSQL_PASSWORD=d8workflow

MYSQL_USER=d8workflow

MYSQL_DATABASE=d8workflow

Next, in order for the MariaDB image to install the database when it starts, we will create a database dump locally, copy it to the Linode server, and place it under mariadb-init:

$ drush sql:dump --gzip > d8workflow.sql.gz

$ scp d8workflow.sql.gz root@some-ip:/root/d8workflow/

$ ssh root@some-ip

$ cd d8workflow

$ mkdir mariadb-init

$ mv d8workflow.sql.gz mariadb-init/

We are ready to build the Docker environment:

$ docker-compose up --detach

Creating network "d8workflow_default" with the default driver

Creating php ...

Creating mariadb ...

Creating php

Creating php ... done

Next, we will jump into the php container and install the project dependencies via Composer:

$ docker exec -it php bash

root@9c6a39e8fc18:/var/www/html# composer install

Loading composer repositories with package information

Installing dependencies (including require-dev) from lock file

Generating autoload files

> DrupalProject\composer\ScriptHandler::createRequiredFiles

Create a sites/default/files directory with chmod 0777

Created a web/sites/default/settings.local.php file. Review the database details in this file.

Finally, I have registered the domain d8workflow.com and configured the dev.d8workflow.com to point to my Linode. Let’s test this by opening the site in a browser:

It works! We have set up the development environment keeping most of the server configuration in code via Docker.

Set up Drush alias

Now that we have a development environment, we can define a Drush alias alias so we can run do things like downloading the database.[v][w]

Update README

5 Introduce Continuous Integration

Pull request checks

Auto deploy on merge

Checking for updates for core and contrib

Update README

https://www.lullabot.com/articles/continuous-integration-drupal-8-circleci

https://www.lullabot.com/articles/continuous-integration-in-drupal-8-with-travis-ci

6 Define a development workflow

https://www.lullabot.com/articles/development-workflow-drupal-8-projects

Patching workflow

https://www.lullabot.com/articles/the-peer-review-howto-guide

Update README

7 Staging and Production environments setup

Configure and connect with repository and database

Set up a tag-based deployment job with… CircleCI ?

Clone Staging environment into Production

Set up Drush aliases

Update README

8 Running the whole workflow

Working on a ticket

Pull request creation

Peer review process

Preparing a release candidate for Staging

Deployment to Production

9 Conclusion

[a]I am not sure if you're aware of https://github.com/AmazeeLabs/silverback (e.g. setup documentation https://github.com/AmazeeLabs/silverback/blob/aaaedfd65d408fb7bee5e1c7ef9b429e97b4f4d6/docs/development/setup.md) - that seems to be "ground zero" for amazee labs codifying their best development practices; it is - in a good/interesting way - very opinionated; whilst going well beyond initial setup it provides lots interesting glances/ideas (even as far ahead as their concept/approaches to decoupling &  content modelling ...). I found it full of interesting "dev nuggets" - your custom installation could e.g. benefit from using `direnv` to autoload DRUSH_OPTIONS_URI.

[b]Thanks Fredrik! I did not know about this but I will keep this comment open as a remainder to do so.

[c]Could this book include topics including agile/scrum, using pm software like Jira, a Drupal Contrib-First Approach development methodology ( https://www.midcamp.org/2019/topic-proposal/adopting-drupal-contrib-first-approach), etc.? Given the target audience, these topic may be helpful.

[d]Hi Jason!

While the topic definitely interests me I am worried about it's scope because it's vast. Perhaps once I have progressed further I will see how can include some of it. I will leave this comment unresolved as a reminder.

Thanks!

[e]Indeed,  perhaps a topic for another book!

[f]Maybe it's worth adding then that "fishing by yourself" should last only within evaluation period and for the sake of consistency between LOCAL, DEV, STAGE and PROD environments and other reasons mentioned here: https://docs.devwithlando.io/#what-is-it-good-for, it's advisable to use some sort of local dev tool.

[g]_Marked as resolved_

[h]_Re-opened_

[i]Thanks Tormi! I will keep this comment open so I can come back, read that link, and see how can I reword this.

[j]Hmmm, I did some reading and the idea of keeping infrastructure configuration as code is appealing to me. I will do some reading and probably make further changes to reorientate this and other chapters.

[k]Thanks, and welcome to this century :D Cheers!

[l]After starting to tinker with a docker-compose to set up the Development environment I decided that I will use that approach to set up the local environment too.

[m]I have done this locally for ages because makes spinning up sites locally easier but now I wonder if there are any security implications.

[n]I think it's fine for local dev. There's so much more to securing a site for production, that guide lives elsewhere imo.

[o]Thanks Chris!

[p]The hash_salt variable still lives at settings.php which will be under version control. I am not sure about moving it out of it or not.

[q]I usually maintain it under version control (if it's help you!)

[r]Thanks Samu!

[s]It would be good to enforce this in composer.json (e.g. https://github.com/juampynr/d8workflow/blob/master/composer.json#L19) maybe in a step above; given the  team-setup background (introducing potentially heterogeneous local environments)  it might maybe also worth touching on https://getcomposer.org/doc/06-config.md#platform which can help to enforce consistent builds.

[t]Yep, will do. Thanks.

[u]This is an important topic so I will look at different options before choosing one and then explain why. I will be very conservative as I want something that can be useful to anyone regardless of which hosting provider they choose later. Therefore, I am keen to use something that may require some additional work but also gives more freedom rather than going for a solution that is very easy and quick to set up but it may be trickier to tweak. I am still considering going for a docker based deployment model or not.

I am starting by looking at the different options at https://www.drupal.org/hosting. I am also taking into account https://github.com/drush-ops/drush/pull/3835 so I can use Docker for this.

[v]I am figuring out how to configure the site alias so a local site using Docker Compose can run drush sql-sync against the development environment, which runs a similar Docker Compose.

[w]Thanks