How I use Docker and WordPress for website development
I enjoy building websites with WordPress as it is very quick and easy to install and modify. In order to make my development work easier, I prefer to utilize Docker containers as an even quicker and easier way to spool up a WordPress site locally and then deploy to an external hosting service when completed.
The tools
The four main portions of my development environment are the web server, the database server, database tools and my preferred editor.
I use the following prebuilt Docker images:
- WordPress PHP server: https://hub.docker.com/_/wordpress
- Database server: https://hub.docker.com/_/mysql
- MySQL tools: https://hub.docker.com/r/phpmyadmin/phpmyadmin
For editing of the PHP files, my preferred editor is VS Code from Microsoft. There are many editors out there, but I have found VS Code is very simple to use and organize your workspaces in. There are also many third party extensions to help with many types of coding. But we are going to keep it simple.
I choose to use Docker, as I can quickly spool up the various containers I need and keep everything sandboxed to those containers. This way I do not need to install all the different software applications on my host system. For instance, I can start up a MySQL server and when finished I can completely remove any trace of the server from my host computer. This way there are no lasting effects on my computer.
Installation of Docker is beyond the scope of this article. Please visit Docker Getting Started article for their own very well written guides to install and use Docker. Depending on your system, you may need to install Docker Compose separately. Compose will be required for this tutorial.
The setup
First, you will want to create a working folder that will store the important WordPress files, database files and the Docker Compose instruction file. This directory will be empty when we begin, but after creating the instruction file and the first run of the WordPress installation there will be two new folders with all the relevant user generated data of a WordPress installation.
Name this working directory anything that makes sense to you. For this tutorial I will use: mysite
Using your preferred editor, create a file called: docker-compose.yml
This file will contain all the instructions for Docker to download the required images (only done once), and then startup the containers using those images to host our WordPress website. Copy and Paste the following code into the docker-compose.yml file and then save to your working directory.
version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
volumes:
- ./wp-content:/var/www/html/wp-content
ports:
- 8888:80
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
phpmyadmin:
image: phpmyadmin/phpmyadmin
restart: always
ports:
- 3333:80
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORT: somewordpress
The explanation
Let’s breakdown the portions of the compose file. The first line identifies the version of Compose api that we are using. I have been using version 3.3
in all of my scripts since at least April 2018 without issue. As of November 2019 the latest version is 3.7
Next, we identify three services that Docker should startup. The names used for the services are arbitrary, you can name them anything you like as long as you do not use any reserved words that Compose would attempt to process
db
– defines the database settingswordpress
– defines the WordPress settingsphpmyadmin
– defines the settings to run phpMyAdmin
db – MySQL database
db:
image: mysql:5.7
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
The image I use is MySQL 5.7 from Oracle. This database has been tested and is commonly used for WordPress database backend. There are other SQL databases that can be used, one of which is even more popular called MariaDb. If you use a different database image, you may need to adjust your environment variables to work properly. Note: if you wish to use MySQL 8.0 there is an additional requirement to use the 5.7 version of password hashing. You will need to research how to accomplish this on your own. Following my suggestion of 5.7 above is easy and already tested.
I like to create the volume within my working folder so it is easier to zip up and transport as one working unit. The db_data volume stored on the host will be mapped to the /var/lib/mysql directory within the container. This way, if the container is stopped and destroyed you will retain the database the next time Compose restarts.
The environment variables are used by the image to setup custom settings to allow the other services to connect easier. You can supply the values you feel are appropriate for your use, as long as you ensure that the wordpress and phymyadmin services have matching values.
I could have setup a ports section to allow access to the database from outside of the container, but since I am including phpMyAdmin it was no longer necessary. I did open a port for the phpmyadmin service in order to get access to the database using this tool. If you want to use your own tool like MySQL Workbench, then supply a ports option where the first number is how you access from outside the container, and the second number is the port within the container that it maps to.
ports:
- 3306:3306
The above example will map from the host using localhost:3306 to allow access to the port 3306 within the container. The first number, choose any available port number you want (nothing below 1024), but the second number 3306 is used as default setup for MySQL server.
wordpress – WordPress files and PHP server
wordpress:
depends_on:
- db
image: wordpress:latest
volumes:
- ./wp-content:/var/www/html/wp-content
ports:
- 8888:80
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
The wordpress service depends upon the db service having been already started up. Adding the depends_on entry will tell Docker to wait for the database service to startup before creating the WordPress container. Thankfully MySQL starts up very quickly, otherwise we would need to add some extra commands to tell Docker to wait until a certain condition is reached in the database. ( How to tell Docker to wait I have never tried before, but noticed an option in some of the Compose tutorials. )
Again, we are mapping a volume to our working directory so we can safely destroy the Docker container and not loose the information we created. Note that we are only mapping the wp-content folder. This is because the other files and folders for WordPress are system files that are pertinent to the version of WordPress. The wp-content folder is where the user generated files are kept, like plugins, themes, and image uploads. We must keep a matching wp-content folder with a matching database folder, as the posts and pages are stored in the database, and any reference to which plugins and themes are installed. If we did not create these volumes, then when we restarted Docker we would need to run the WordPress Install every time.
I have mapped the host port of 8888 to the internal container port 80. The WordPress image uses the default port 80 within the container, so in order to access that port outside the container we need to map a number to that port. I chose to use port 8888 as that port will typically be open on my host computer and the 8000 range is typical for websites. This way I can type localhost:8888 in my web browser on my computer and see the WordPress website inside the container.
For the environment variables, we need to use the values according to our settings for the database service.
- WORDPRESS_DB_HOST: db:3306
- points to the service db and the port inside the container of 3306
- WORDPRESS_DB_USER: wordpress
- This is the same user as the MYSQL_USER in the database service
- WORDPRESS_DB_PASSWORD: wordpress
- This is the same password as the MYSQL_PASSWORD in the database service
phpmyadmin – phpMyAdmin web service
phpmyadmin:
image: phpmyadmin/phpmyadmin
restart: always
ports:
- 3333:80
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: somewordpress
If you have been following along thus far, then this portion should almost be self explanatory. The important sections are the ports and environment variables.
I chose to use external port 3333 to map to the internal port 80 web service. This way I can access the web service but not confuse the ports with the WordPress web service above. I could have used a number like 8000 or 8080 or other typical web service port, but seeing that MySQL tends to use 3306 it was easier for me to remember the purpose of the external port by using a number close to what MySQL uses, so port 3333 seemed to make sense to me.
As for the environment variables:
- PMA_HOST: db
- db is the name of the database service that I used, so match that value here
- MYSQL_ROOT_PASSWORD: somewordpress
- This is the same password as the MYSQL_ROOT_PASSWORD password in the database service
The usage
To startup the Docker containers, open up a Terminal (Command Prompt) window and enter the directory where the docker-compose.yml file is stored. Then enter the following:
docker-compose up -d
Compose will read the instruction file, download the required images if not already on your system, startup the containers Docker Volumes and Networks.
Give Docker about a minute to fully startup the containers (usually takes less than a minute) and startup the WordPress services. Then open a web browser and enter http://localhost:8888/ for the URL.
The very first run, you will need to go through the WordPress 5-minute install (which really only takes about a minute). Then every other time you start Docker, you will already have your content ready for you, assuming you do not delete the db_data and wp-content folders in your working directory.
You may now edit any of the files in the wp-content folder of your working directory and see the changes affect your WordPress plugins and themes live. This is how I create my own themes and plugins. I will create a new subfolder under the themes or plugins folder in my working directory and edit the PHP files directly.
If you want to use phpMyAdmin to look at the database, then point your web browser to http://localhost:3333/ and then use the values from WORDPRESS_DB_USER and WORDPRESS_DB_PASSWORD from above in the WordPress service section. This is where you can export your database to a .sql file to be used for importing into a clean database on your new hosting server.
Using the exported .sql file and the matching wp-content folder, you can setup a fresh MySQL server, import the .sql file, and install the WordPress files and replace wp-content with your wp-content and you will have your development website now on a new hosting service. Note, that you may need to make some edits to your database for all the urls to work correctly, and that is beyond the scope of this tutorial.
If you are planning to migrate your work from your development server to a live host, you may be better of using one of the migration or backup plugins that are available for WordPress.
Troubleshooting
I see the following error:
ERROR: yaml.scanner.ScannerError: while scanning a simple key
in "./docker-compose.yml", line 6, column 1
could not find expected ':'
in "./docker-compose.yml", line 7, column 1
Docker Compose is very particular about spaces and indents. Sometimes, copying code and pasting into your editor will remove the indentation. Please be sure to indent your code per Docker Compose requirements.
I see the following error:
Traceback (most recent call last):
File "docker-compose", line 6, in <module>
File "compose/cli/main.py", line 71, in main
File "compose/cli/main.py", line 124, in perform_command
File "compose/cli/command.py", line 42, in project_from_options
File "compose/cli/command.py", line 115, in get_project
File "compose/config/config.py", line 387, in load
File "compose/config/config.py", line 387, in <listcomp>
File "compose/config/config.py", line 520, in process_config_file
File "compose/config/config.py", line 229, in get_service_dicts
File "distutils/version.py", line 46, in __eq__
File "distutils/version.py", line 337, in _cmp
TypeError: '<' not supported between instances of 'str' and 'int'
[12417] Failed to execute script docker-compose
Sometimes when copy/paste, the incorrect quotes are pasted. Make sure using the simple single or double quotation marks in your document.
I see the following error:
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.db.volumes contains an invalid type, it should be an array
services.wordpress.volumes contains an invalid type, it should be an array
services.wordpress.ports contains an invalid type, it should be an array
services.wordpress.depends_on contains an invalid type, it should be an array
Sometimes when copy/paste, the incorrect hyphen used in the volumes and ports descriptors. Make sure using the single dash and not the wider double-dash character. It is almost difficult to tell the difference between them, except the double dash is slightly wider. If you are using VS Code, the correct dash will be white and not red.
I see the following error:
ERROR: Couldn't connect to Docker daemon. You might need to start Docker for Mac.
Don’t forget to start the Docker application.
This website was… how do I say it? Relevant!!
Finally I’ve found something that helped me. Cheers!
This is more or less exactly what I am looking to do. Thanks. Did you have success copying the db data to another location, spinning up WordPress in Docker and have the data you wanted in your WordPress site?
This is a very good question, and I have not exactly tried what you are asking yet. I will take a look at the idea this week and hopefully write a proper post outlining the process. Just from my earlier observations with Docker and WordPress, let me suggest an additional directory to keep. Since there is data stored in the database that relates to plugins and themes that are installed, I would suggest keeping the wp-contents folder as well. Looking at my sample above, I would suggest modifying the docker-compose.yml file by removing the volumes in the wordpress section and adding a new volume to point to the wp-content folder:
- ./wp-content:/var/www/html/wp-content
This way, you can keep the important plugins and themes folders that match the existing database entries. Usually, the “wp-content” folder and the database is all you need to create a backup of your WordPress site. (see Elegant Themes description of wp-content)
Then you can take your folder containing the docker-compose.yml file plus the two folders, one for database and one for content, and then use them over and over and keep the content you already created (as long as you continue to use Docker).
Note: you will not be able to directly import these two folders into any existing WordPress installation as you will need to export the database in a way that you can import into a new MySQL server. This is beyond the scope of this article, but if you have the site running in Docker, you can use WordPress backup plugins that are freely available. The other option (and worth a new post for me to write), if you add a ports section to the database portion of the docker-compose file, you can access the running MySQL database with other SQL tools. Of which, I just realized I have not yet written a post about a phpmyadmin docker image that I use often for work. Guess I better get busy 🙂 writing content again (and updating this current post)
I create a video using the phpmyadmin docker image, check that out if you are interested.
Thanks for your comment and interest!