Local WordPress copy with docker compose

Introduction

When you have a WordPress page and you want to test something without testing in production… what do you need?

A separate testing stage. Using docker, it is simple to start up some LAMPish (linux, apache, mysql, php) stack locally and start testing without the fear to break something.

Docker Compose

There are kind of official docker images for WordPress available on docker hub, e.g. https://hub.docker.com/_/wordpress. In order to use it, you also need a database, e.g. https://hub.docker.com/_/mysql. For easy access to the database, we’ll use another image: https://hub.docker.com/_/phpmyadmin. The complete docker compose file looks like this:

services:

  wordpress:
    image: wordpress
    restart: unless-stopped
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
      WORDPRESS_TABLE_PREFIX: prefix_
    volumes:
      - ./wordpress:/var/www/html

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - ./db:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin
    restart: unless-stopped
    ports:
      - 8081:80
    environment:
      - PMA_ARBITRARY=1

Save this as a file docker-compose.yaml.

Now you can start all three services with docker compose up. The WordPress installation will be available at http://localhost:8080 and the phpmyadmin is available at http://localhost:8081.

There are some important configuration options to know: The service wordpress uses bind mounts to access the local directory ./wordpress inside the container. When the container starts up the first time, WordPress is extracted and installed into this directory - and this directory is then served by the included apache webserver. If you start it the first time, you are presented with the usual WordPress installation wizard where you need to setup a user and password. If the container is restarted, this directory is reused. So the data is preserved. This is then also a simple way, to replace this default WordPress installation with your production copy, see below.

Similar thing is happening with the data of the mysql database: This is written down into the bind mounted local directory ./db and is preserved through restarts.

phpmyadmin doesn’t use any volume, it simply serves the application. As it is included in the same docker compose file, it can directly access the database via the service name “db”.

There are some configuration options, which are provided as environment variables. E.g. the WordPress instance needs to know the database, which must match the values for the mysql service. For phpmyadmin we use “PMA_ARBITRARY=1” which allows to use this phpmyadmin instance to connect to any reachable mysql server. You can simply enter “db” as server name and use “exampleuser/examplepass” to login.

You can stop the services with Ctrl+C, as docker compose up runs in the foreground. Start them again with docker compose up or destroy the with docker compose down. Note: Only the containers are destroyed, not the data, that is preserved in the local directories.

Copying the data

A WordPress instance consists of the PHP files itself (including any additionally installed plugin), the uploaded files and the content of the pages which is stored in the database. In order to replicate a production instance locally, you need to copy all.

  1. Stop the (docker compose) services if they are still running.

  2. Notice the userid/groupid ownership of the files. You’ll need this later. Then delete or move away all files in the local ./wordpress.

  3. Copy all files from the production instance into local ./wordpress. And adjust the file ownership accordingly, e.g. with chown -R 100032:100032 ./wordpress.

  4. Create a database dump from the production instance, e.g. via phpymadmin’s export functionality.

  5. Adjust the WordPress configuration for the local stage, that means: Edit the file ./wordpress/wp-config.php and change the following properties:

    define( 'DB_NAME', 'exampledb' );
    define( 'DB_USER', 'exampleuser' );
    define( 'DB_PASSWORD', 'examplepass' );
    define( 'DB_HOST', 'db' );
    
    // https://stackoverflow.com/questions/10578369/wordpress-wp-admin-redirects-to-https
    // allow to use wp-admin locally
    define( 'FORCE_SSL_ADMIN', false );
    
  6. Then start the services with docker compose up.

  7. Go to phpyadmin: http://localhost:8081 and import your production database dump into database “exampledb”.

  8. Go to the table “options” (it might be prefixed with some table prefix) and change the options “siteurl” and “home” to be “http://localhost:8080”.

  9. Then go to WordPress: http://localhost:8080. It should display the production page. You can also log in to wp-admin: http://localhost:8080/wp-admin using the production credentials.

  10. Update WordPress, install new plugins, test. This should all work now locally.

Note: Some themes might have stored the production URLs for images, so keep an eye on the network requests to see, if all requests really end up on localhost. E.g. I noticed that the configured background image for “Avant Portfolio” was using a absolute production url. But configuring the background image again solved the issue.

Note 2: Maybe you have some absolute links in use for some custom navigation. You’ll need to change those manually to point to “http://localhost:8080/…” (or just use relative links if possible).

Note 3: If the file permissions are messed up, WordPress updates or plugin updates / installations might not work. So be sure, to use the correct userid.

Summary

This article shows an easy way to get a production copy of a WordPress instance running locally. You can simply copy the local folders and create another WordPress instance locally for another test. And all this without directly impacting the production instance.


Comments

No comments yet.

Leave a comment

Your email address will not be published. Required fields are marked *. All comments are held for moderation to avoid spam and abuse.


Andreas Dangel | subscribe via RSS | adangel | .onion © Copyright 2025. adangel.org (29 June 2025)