VS Code, WordPress, WSL2 & Docker

This blog post is going to detail my journey with running a Node & PHP (WordPress) development environment on Windows 10 using WSl2, Docker and VS Code. I will share the bare config in a final, separate post at the end.

The Goal

I am a React developer by trade, so all I really need to be happy is VS Code and Node. As part of a previous project I entered the WordPress realm and authored several WPGraphQL integration plugins. Embarrassingly, I did all of this work with a deployed WordPress installation and vanilla VS Code. I didn’t want the hassle of installing PHP and XDebug on my personal machine. I even used FTP to deploy my code changes to the server – gross.

So, having used VS Code remoting and Docker at work I felt this was a winning solution. However I do not have the time to learn how to build a Docker container development environment from scratch. Cue the Docker for Windows, WSL2 and VS Code integration. This is perfect!

The requirements:

  • VS Code should open my working directory in a Docker container
  • PHP intellisense and XDebug should be available
  • Node should run the client app from the container
  • Everything should shut down with VS Code to free system resources for gaming

And I finally succeeded!

Step One: Get environment ready

I followed the official Microsoft guide for getting this pre-release environment installed. Most of the beta channel builds have filtered down since March 2020 so you might already be ready to go.

Step Two: Docker configuration

I use a mixture of Docker Compose and a Dockerfile to build the required system.

The Docker Compose configuration does a few things.

  1. Declares www service for running WordPress and Node
  2. Declares db service for running MySQL 5.7
  3. Uses a .env file to preload MySQL and WordPress configuration options as environmental variables
  4. Mounts a delegated volume under wp-content/plugins/my-plugin so my work is available in WordPress
  5. Defines a Docker Volume for persisting the database files and mounts it in /var/lib/mysql

The Dockerfile mainly installs WordPress and XDebug. This was mostly lifted from the XDebug for Docker image.

I reopened my folder in the remote container and watched my first Docker containers building. I navigated to localhost and saw the WordPress set up page – woo!

Step Three: WordPress dependencies

My React app relies on a few dependent WordPress plugins – mainly Meta Box and WPGraphQL (and of course my integration plugins!). I contribute to all of these, so I want to mount my local repositories in wp-content/plugins. I did not want to mount these in a Docker volume as it was important to manage the files from the host.

I created a .docker directory in my repository and ignored it in .gitignore. This is where I will put anything to share between host and container. In .docker/wp-content I cloned plugins, themes and uploads required for my WordPress site to run. These were mounted as delegated volumes for performance, but this is risky as container changes aren’t persisted on the host in real-time.

Finally, I added a ‘docker blocker’ tmpfs volume mounted over wp-content/plugins/my-plugin/.docker. This will hide the .docker folder from the container. This is important for not seeing duplicate PHP files in XDebug etc. I set nocopy and a max size of 0 so I am not tempted to put anything in there.

I really should set some environmental variables and clean this up!

Step Four: Node

At this point I had a working PHP development environment. At the time I was running Node in a second VS Code window on my host, while doing PHP in my container.

To migrate my JavaScript development into my Docker container I had to overcome a few issues:

  • Create React App cannot hot reload in a container.
  • File permissions/poor performance of node_modules. This was harder to diagnose, I went through several iterations of trying to get this to work.
    • Running VS Code on a Windows directory (C:\dev\src\my-repo) would fail to compile in Docker.
    • Running VS Code from a WSL2 folder (~/src/my-repo) would compile but perform horribly.
    • Finally used a ‘docker blocker’ node_modules volume.

I added the Node Source URI to apt-get and installed nodejs in my Dockerfile.

Step Five: Polish that dev experience

At this point I have WordPress and XDebug running with their dependencies. I also can run Node and view my React app alongside the deployed WordPress version. The only thing left is to improve the developer experience a little.

Things I want:

  • Persist my bash_history
  • VS Code extensions (ESLint, IntelliPhense etc)
  • VS Code settings
  • Install a .bashrc file with some helper functions
  • Create a share directory to allow easy dragging and dropping of files into the container and vice versa

I created the .bashrc file I wanted, mostly a duplicate of my WSL .bashrc.

I saved this in my repository as .bashrc.docker and used Dockerfile COPY to sync it on container build. I also added a .gitconfig file with some defaults and COPY that to /root too.

In my .docker directory (remember this is ignored and hidden from the Docker container) I add mounts for .bash_history and share under /root.

And finally, a .devcontainer.json file for VS Code settings.

And – done!

Recap

So, once all is said and done this configuration allows me to run a custom WordPress development Docker container (with PHP, XDebug and NodeJS). You can have a custom .bashrc deployed and your command history is persisted in .bash_history.

There is a share folder mounted under /root to allow SQL dumps or other file movements on and off the container.

WordPress dependencies are kept on-host and mounted in /var/www/html/wp-content.

WordPress is forwarded on port 80 and my React app is on 3000.

ESLint is scoped to development directory to avoid surfacing linting errors in other plugins.

PHP debugging and intellisense extensions are installed on the Docker container – not your local VS Code.

And most importantly, there are no services or frameworks installed on my host, other than WSL2 and Docker for Windows. And all of these can be shut down with VS Code freeing up system resources for play time.