A development environment is an essential part of any web development project. It allows the website to be run outside of the production environment so that features or bugs can be worked on without disruption to the live website.
I've used local development environments to build websites for a number of years and I have striven to adhere to a set of rules that make life easier. These rules aren't a rigid set of requirements, but following them can make life easier for everyone on the development team. I have seen developers spend many hours with broken local development environments that could have been better spent actually working on the project.
I started to put together these rules after I inherited a website project from another company. This website came with the requirement of using a certain local development stack, which was required for the hosting provider. The problem was that every time we added another developer to the project it would take us at least two days to perform the onboarding just due to the complexity of the local environment. The project itself wasn't that complex, but lots of time was spent trying to get the project running due to the local development system in use.
The documentation we had to follow was 10 pages long and consisted of many steps to check for particular versions and to install lots of packages locally. It had very stringent requirements around the setup of the developers machine and any deviation from that would mean hours trying to get just the right version of software installed.
I found it absolutely stunning that anyone could think that amount of effort just to get up and running was in anyway acceptable. One of the goal I set in motion was to migrate that project away from that setup, after which the project was much easier to work on going forward. Plus, we didn't have to waste hours of time up front trying to onboard new developers to the project.
The recent publication of the 2023 Drupal Local Development Survey Results got me thinking about these rules again, so I thought I would collate it into an article. I will largely be talking about Vagrant or Docker or cloud setups here. I haven't used a locally installed LAMP stack as a local development environment for a good number of years now so I won't be covering that. In fact, if you are still using local LAMP stacks then I highly encourage you to start looking into docker based setups as it makes life so much easier.
Here are the rules as a list in case you want to jump around.
- It should be easy to install the packages required
- It should have a minimal footprint on the project
- It should be easy to onboard new developers
- It should be easy to make modifications to the set up
- It should closely mimic the setup of the production environment
- Developers shouldn't need the database and all the site user files to work on the project
- It should have sensible overrides
- It should wrap and/or document common tasks
The perfect local development environment should be installed onto new machines with just a few instructions. It's fine if there are a couple of things to install first, or if there a few instructions to run, along as there isn't pages and pages of intricate instructions and very stringent version requirements.
This is not necessarily about time taken, but about the complexity of the installation process. If you kick off the install process and need to wait for 10 minutes for containers to download then that's time that can be spent reading project documentation.
The project gets bonus points for not having many third party dependencies. Most modern development environments will require Docker or Vagrant to get everything setup, and these packages are well supported and in general use. Some obscure package that needs 20 steps to install or needs to be compiled by hand is not the best approach here.
You should also consider the operating systems supported by the development solution. You don't want to be in the position of having to buy new equipment or turn away contractors just because their system isn't compatible with your development solution
The ideal situation is that everything needed by the environment should be kept in a single directory, or event a single file.
Local LAMP stacks tick this box because they have no footprint on the project, but this means that every developer might have a slightly different setup. This can lead to bugs and errors being introduced due to incompatible versions or software.
The bottom line is that if you want to move away from that development environment it should't take you weeks to unpick everything from the system.
This also means that you shouldn't have to alter your project just so that you can get it to work with the environment. When you start having to alter code to fit the development system you're going to have a bad time once that code is pushed upstream as it will inevitably have bugs that you can easily miss.
If your setup guide consists of a massive document with lots of steps that need to be followed in precise order then you are doing something wrong. Taking a full working day to get just the local development environment running is a an expensive waste of time. Time that the developer could have spent making a useful contribution to the project.
The ideal situation should be that the developer needs to clone the repository and then run a single command to get everything running. New developers should be able to read a one page document that shows how to get up and running, or how to make any tweaks and changes to the project.
Even with an established project it can be easy to be complacent about the ease of setup. A developer starting from scratch might encounter something that isn't spotted in the day to day project work. As a result, you should always make sure new developers have some support when they start on the project to counter these kinds of problems.
The setup should make some sensible defaults for the system needed, including the latest versions of Nginx and PHP. Having custom settings for a project is inevitable so these settings must be configurable in some way.
For example, you might start a project using PHP 7.4 and then need to upgrade to PHP 8.1 at some point in the future. The ideal situation is that you add or edit a setting to a configuration file and rebuild the project (or just that container if it's docker based) to make the change.
Adding the modifications should be simple to do and should involve a minimal number of configuration files. The documentation for what values these files can have should be easy to find and be well maintained.
Also important is communicating these changes to other members of the project team. If you change a value in a configuration file then this can be committed to the source code and used by other members of the team to easily alter their configurations so that they are the same.
It doesn't need to be exact, but your local setup should (at the very least) have the same packages used to host the production environment. This should also include the different versions and configuration options for each of these packages.
There are many things that can cause problems between different versions or configuration options. I have encountered issues with different versions of PHP, MySQL, or even Node running on different systems that cause critical bugs once the code reaches production.
It might be more subtle than that though. For example, if you are running MySQL/MariaDB then you should ensure that you have the same configuration as the production environment as these options can change how queries work. I have seen sites throw errors because a developer had created a query that worked locally, but caused a problem when deployed due to a difference in configuration settings.
After checking out the files for the project the developers should only have to run a few commands to start work on the project. This means that they shouldn't absolutely need to grab the latest copy of the database to start work.
Of course, this sometimes just isn't possible, especially when investigating user specific problems or when writing migration scripts. For day to day development processes it shouldn't be needed.
Drupal has a number of good solutions for this sort of thing. Modules like Default Content Deploy can be used to populate the database with content out of the box, which can be used in conjunction with install profiles and configuration to allow everything to be set up to mimic the production site structure.
The Drupal Stage File Proxy module can allow any files that are present on the production site to be downloaded locally as and when they are required. Meaning that you don't need to download the entire file system to get a local site running correctly.
If the database is required then tools like Drush sanitize should be used to clear out personal information from your website. This gives you peace of mind as it means that your developers won't have access to data they don't need, or accidentally email real users from their local setups. Sanitising data should ideally be done automatically before the developer gets their hands on it.
It's also possible to include a small stub database in your development system, which can allow developers to get up and running quickly or even for unit testing the system. This does need maintaining correctly in order to make it effective and so how to update it should be well documented.
As this is a development environment it should have sensible overrides to prevent developers from missing problems or making mistakes.
The simplest thing that should be enabled is sensible error reporting levels. For example, if the project is using PHP then the site should display all types of errors. This allows developers to detect when they have made mistakes, which they might otherwise miss if the errors are hidden, as they would be on production.
One of the risks of running development environments (especially if it contains production data) is that it may leak information. The biggest example of this is when the site uses emails and your development environment doesn't suppress the sending of emails. There's nothing more embarrassing than apologising to your clients because a developer sent test emails to real users and your local setup can easily override this.
Your development environment should always be setup in a developer friendly way to prevent information leakage and common mistakes.
Finally, to make life easier for developers, the environment should wrap tasks that are commonly run. This is in order to save time when setting things up, or when performing other common tasks like syntax checking or unit testing. It's also a good idea to include a README.md file in your project with some helpful commands that can be run.
This isn't necessarily part of the local environment system, but should be considered a part of your project as it feeds into the experience developers have with the system.
An example of this is if your environment has a feature that allows you to install the project, but that command takes a few parameters. If this is the case then you should wrap that command in runner of some kind. Bash scripts, make files, composer files or even or command runners are all viable, as long as your developers don't have to remember those commands.
Many tools are also self documenting. For example, you can create a Makefile in your project that will print out all the commands available within it by running 'make' on its own (although this does take a bit of configuration). This means that the developer doesn't need to look at the Makefile or pull out the documentation when they need to run a command and can't remember it.
Key to this is for the task runner to be easily integrated with your development tool. If the command you want to run needs to be run within the docker container then you should be able to easily address the container in the make file.
This technique is really useful when it comes to testing or running code analysis. Allowing developers to easily run the unit testing suite on your site is really useful and wrapping that in a single command makes it easy to do so. Even better, when you come to create a continuous development environment for your project you can just run the same commands, there's no need to start from scratch.
I usually find that if you follow these rules then it makes life easier for you and the developers in your team. This is in terms of getting setup, the day-to-day working practices, debugging issues. and deploying your code upstream. Your local development environment should aid you towards your work, rather than be a hindrance or barrier to getting things done.
There's also the important consideration of the ongoing maintenance of the system within the project. I have seen companies spending lots of time setting up a custom development environment that uses a programming language or technology that no one on the development team knows about. This creates a pretty serious bottleneck and can cause problems when things break in interesting ways as the development team then need to lean on the original author to fix things. Just as important in developing these solutions is ensuring that your team knows about it.
The DDEV package gets a lot of things in this list right by being easy to install, well documented, highly configurable, and having a small footprint on the project itself. The system was originally created for Drupal, but can now be used on any number of different PHP projects, including WordPress and Typo3. DDEV has the ability to add it to new projects using a single command, which means that onboarding and setup is simple. This is done using the ddev config command, which allows the ddev package to be added to any project just by answering a few questions.
Packages like DDEV and Lando are really popular tools for Drupal developers, and I think it's because they fit many of the requirements detailed here. Systems like docker4drupal are less popular, and I think that's because they are a simple Docker stacks that do require some knowledge of Docker to make them powerful.
Systems like GitPod (and extensions like DrupalPod for Drupal sites) can solve many of these problems without having to install anything on your local machine. These are cloud based development environments, which means all you really need to work on all aspects of a site is a browser and an internet connection. The only downside is that you need a stable internet connection, which isn't always the case in some offices. I have been in offices where the internet connection has been unstable and this has only caused minimal disruption to the team as everything was running locally. It does, however, get around problems when developing on locked down systems as you don't need to install anything to get up and running.
Picking a development environment should be a serious part of your project development life cycle. This is often called "sprint zero" as is happens before any real work is performed on the project but is an essential part of it. I have seen projects struggling to get things done due to the poor choice of development environment, which has a bigger impact on the project than you might think. If the wrong environment is selected then this can cause problems later on in the project life cycle and can lead to developer fatigue and burnout just because the project is difficult to work on.
Do you think I've missed anything here? How close does your team follow these rules? Have you encountered difficulties the a project due to the development environment not adhering to these rules. Please leave a comment and let me know your thoughts.