Tutorials How to deploy WordPress with Docker Compose

How to deploy WordPress with Docker Compose

WordPress is one of the most popular content management software (CMS) due to its multitude of features and ease of use. However, setting up a new web host environment can be time-consuming especially if you need to do it often. Docker Compose manages to simplify the installation process to a single deployment command greatly reducing the time and effort required. Deploying WordPress with Docker Compose is fast and easy after the first time setup.

WordPress using Docker Compose

Docker is a container platform that allows simple and fast software installations on any system or OS. It wraps the piece of software in a complete file system that includes everything it needs to run such as code, runtime, system tools and libraries. This allows anyone to package an application with its dependencies into a standardized building block.

Installing Docker

Using Docker Compose requires having the Docker daemon running on your cloud server. Installing Docker itself is already easy. Firstly run the usual update command for your system to make sure you have the latest source lists.

# Debian and Ubuntu
sudo apt-get update
# CentOS
sudo yum update

Check that you have the curl command line utility.

curl -V

It comes preinstalled with most Linux distributions, but if it can not be found, install it manually with the appropriate command for your OS.

# Debian and Ubuntu
sudo apt-get install curl
# CentOS
sudo yum install curl

Use the command below to download and install Docker. The process requires root privileges so you will be asked for your sudo password on any non-root user.

curl -fsSL https://get.docker.com/ | sh

Towards the end of the installation process, you will see a suggestion to add your username to the Docker users group. Doing this allows you to run Docker commands without needing to invoke sudo every time.

sudo usermod -aG docker username

Log out and back in again after adding yourself to the Docker users group before continuing.

You can check that the installation was successful with the following test program:

docker run hello-world

You should see an output similar to the example below.

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Hello from Docker.
This message shows that your installation appears to be working correctly.
...

If the command does not work immediately, restart the Docker service with the following and try to run the hello-world app again.

sudo systemctl restart docker

Docker should now be installed and working correctly. Continue on below with rest of the guide to install Docker Compose.

Installing Docker Compose

While installing applications in containers with simple commands is already easy enough, it still requires memorizing those commands and their parameters. Docker-compose allows combining the install process for multiple containers. Install the additional components for Docker using the procedure below.

First, temporarily switch to your root account.

sudo -i

Use the install script below to download and apply the docker-compose packet.

curl -L https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

Set the execution permissions to it with:

chmod +x /usr/local/bin/docker-compose

And then exit the root user account to return to your normal username.

exit

You should now have the docker-compose application installed and available, test it by checking for the version number.

docker-compose -v
docker-compose version 1.24.0, build 0aa59064

With the Docker Compose installed you are now ready to start configuring your containerized WordPress environment.

Configuring WordPress with Compose

WordPress is officially available on Docker Hub and easy to set up, but it will not create a working website by itself, it requires a database to store the content. MariaDB is a community-developed relational database management system and a drop-in replacement for MySQL. It is also officially available on Docker and provides easy instructions with up to date images.

Setting up containers with Docker Compose works by creating a Dockerfile and docker-compose.yml in the desired working directory. Start off by creating your working directory, e.g. wordpress-compose.

mkdir  ~/wordpress-compose && cd ~/wordpress-compose

Next, create a docker-compose.yml file. This will tell docker how to configure and start the WordPress and MariaDB containers.

nano docker-compose.yml

Copy the example underneath and set the parameters in the file. Replace the database password and public_ip with values appropriate to your cloud server. Make sure the password is the same for both environment variables so that WordPress will be able to access the database.

wordpress:
    image: wordpress
    links:
     - mariadb:mysql
    environment:
     - WORDPRESS_DB_PASSWORD=password
    ports:
     - "public_ip:80:80"
    volumes:
     - ./html:/var/www/html
mariadb:
    image: mariadb
    environment:
     - MYSQL_ROOT_PASSWORD=password
     - MYSQL_DATABASE=wordpress
    volumes:
     - ./database:/var/lib/mysql

When you are done editing the compose file, save it and exit the editor.

Now create the new containers with the command below. This starts both containers in the background and leaves them running. If you wish to see the output from the containers just leave out the -d to deploy the applications in the foreground.

docker-compose up -d

Wait for the installation to finish. You will see something like the example output below once the process is complete.

Creating wordpress-compose_mariadb_1 ... done
Creating wordpress-compose_wordpress_1 ... done

You can then open the public IP or domain of your WordPress server in your web browser to test the installation. You should be redirected to the WordPress initial setup page like the image shown below.

WordPress initial setup page

If you want to make changes to the configuration, simply update the files and run the docker-compose command again. If docker-compose detects the configuration or the image has changed since the container was created, it applies the changes by stopping and recreating the containers while preserving mounted volumes.

For example, you can check for updates on the WordPress and MariaDB images and push changes to your containers using the commands below.

docker-compose pull
docker-compose up -d

Other useful commands for docker-compose are start/stop, config, ps and down.

# Starts all stopped containers in the work directory
docker-compose start
# Stops all currently running containers in the work directory
docker-compose stop
# Validates and shows the configuration
docker-compose config
# Lists all running containers in the work directory
docker-compose ps
# Stops and removes all containers in the work directory
docker-compose down

Docker-compose also has its own command-line reference guide at the documentation page.

Conclusions

Congratulations, you should now have a docker-compose set up with WordPress and MariaDB running in containers and an easy way to update the services when needed. While manually running Docker commands to create containers already simplifies application management, docker-compose takes it a step further and allows you to bundle multiple containers within a single working directory.

Before continuing on building your new WordPress site, make sure to pay attention to the security on your server. To find out more, check out our article for how to secure your Linux cloud server.

Editor-in-chief and Technical writer at UpCloud since 2015. Cloud enthusiast writing about server technology and software.

31 thoughts on “How to deploy WordPress with Docker Compose

    1. Hi Erri, thanks for the question. The code directory was part of an earlier example and no longer needed. We’ve updated the guide to make it faster and easier to follow.

  1. Above example is NOT working with Windows 10 OS. Even docker-compose.yml file shows error. However that I have corrected it. But it is not working. It shows following message

    Starting wp_mariadb_1 … done Creating wp_wordpress_1 … error
    ERROR: for wp_wordpress_1 Cannot create container for service wordpress: invalid volume specification: ‘E:\docker\wp\html:/var/www/html:rw’: invalid mount config for type “bind”: bind source path does not exist: e:\docker\wp\html

    ERROR: for wordpress Cannot create container for service wordpress: invalid volume specification: ‘E:\docker\wp\html:/var/www/html:rw’: invalid mount config for type “bind”: bind source path does not exist: e:\docker\wp\html
    ERROR: Encountered errors while bringing up the project.

    1. Hi there, thanks for the question. The guide is intended for cloud servers running Linux, and since you are using Windows, you need to set COMPOSE_CONVERT_WINDOWS_PATHS=1 to your environmental variables e.g. $Env:COMPOSE_CONVERT_WINDOWS_PATHS=1

      1. Thank.

        However I have ongoing wordpress site and it has Data of 180 MB and theme is also different as our designer has modified many things in CSS.

        With above example it will add fresh copy of WordPress. Asked for installation of WordPress data etc. While I want to upload source from my localhost to Docker Container.

        1. The Docker volumes allow you to include content from outside the container. In your case, place a copy of your existing WP HTML content in your E:\docker\wp\html directory. Then run docker-compose up and check that your site data is accessible within the container. Your site should now load when opening the server IP on a web browser.

          1. Thank you for these 2 WordPress write ups, they are great!

            I am struggling with two additional steps and it seems hard to find documentation at a beginner level. If you ever do a tutorial (or know of a good source) I would appreciate it

            1) Commands to use if your dev and web servers are separate servers.

            2) More information about actually making changes to the WordPress site. Like the question above about adding in existing content and how that is done when running inside a docker image

          2. Hi there, thanks for the question. One option for deploying sites from development to production is using a repository. With docker, you could run your own private registry or use their ready-made solution at hub.docker.com. Alternatively, if you are looking to import an old WordPress site, you may find this guide useful.

  2. i am trying to follow the above docker compose file when i am getting the error is saying as below.
    ERROR: The Compose file ‘./docker-compose.yml’ is invalid because:
    wordpress.ports is invalid: Invalid port “public_ip:80:80”, should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol]

    1. Hi Sagar, thanks for the question. As mentioned in the instructions, you should replace the “public_ip” part in the docker-compose.yml file with the IP address of your server or remove it entirely if you wish docker to listen on all network interfaces.

    1. Hi there, thanks for the question. You’ll need to connect to the running database container using docker exec. Once connected, you can use mysqldump db_name > backup-file.sql to export data. MariaDB has some good documentation on the database export and import options.

  3. In the above compose .yml, there are 2 different statements regarding volumes.
    1. In wordpress, there is this mounting statement: ./html:/var/www/html
    2. In mariadb, another mounting statement: ./database:/var/lib/mysql

    In both of these statements, where are ./html & ./database directories located?
    Are they in the host OS?
    I am using Win10, based on your yml, does it mean I have to creat 2 subfolders: html and database in order for this to work?
    What if my project is located in c:/User/thisapp folder. Where thisapp folder has the yml file. How should I modify your volume statement in such Windows host environment?

    1. Hi there, thanks for the question. The "./" at the beginning of each volume path indicates the working directory which in your case is then your C:\User\thisapp where the .yml file is located. Compose should create the volume directories by itself on the first run without any modification to the volume paths. However, you’ll likely need to tell Docker to convert the paths to Windows format by setting the environmental variable COMPOSE_CONVERT_WINDOWS_PATHS=1, for example, by creating a .env file in the same directory as your .yml file.

  4. I got this all set up no problem but I’m having a problem figuring out how to install an SSL certificate. All the tutorials I’ve found refer to folders and directories that don’t seem to exist. Any help you can give me would be appreciated!

    1. Hi there, thanks for the question. You can use the Certbot Let’s Encrypt docker image to obtain certificates for your WordPress site and configure SSL with a separate reverse proxy container e.g. using nginx. Though this sounds complicated, it’s really quite simple. We have a tutorial on another topic which shows how this can be done. The only thing you’ll need to change in your docker-compose.yml file is the port it listens to by using the following instead:

      ports:
       - "8080:80"
      1. I tried following the tutorial but I’m not sure how to adapt it to my particular configuration since I’m not using “Rancher”.

        1. The only part you need to follow is the section for Configuring SSL. Apologies for not being clear on that earlier.

  5. Yes but when I get to the “Nginx” part, the config file has this at the top:

    “upstream rancher {
    server rancher-server;
    }”

    And the two commands after that contain references to Rancher. What would I substitute in my case?

    1. The mentions of Rancher in the nginx config are just names and can be replaced. To get the containers talking with each other, do the following.

      First, make a small change to your compose config by adding the following two lines to the top and set the ports to 8080:80.

      ~/wordpress-compose/docker-compose.yml
      version: "3"
      services:
        wordpress:
          image: wordpress
          links:
           - mariadb:mysql
          environment:
           - WORDPRESS_DB_PASSWORD=password
          ports:
           - "8080:80"
          volumes:
           - ./html:/var/www/html
        mariadb:
          image: mariadb
          environment:
           - MYSQL_ROOT_PASSWORD=password
           - MYSQL_DATABASE=wordpress
          volumes:
           - ./database:/var/lib/mysql
      

      Then restart the compose containers.

      docker-compose down && docker-compose up -d

      In addition to the WordPress and database containers, compose will also create a network called “wordpress-compose_default”.
      Next, create the reverse proxy config. You can name the upstream block as you wish, e.g. wordpress as below and point it to the wordpress container. Change the “server wordpress” if you’ve named your WordPress container something else. Then replace all occurrences of “example.com” with your own domain.

      ~/docker/nginx/default.conf
      upstream wordpress {
          server wordpress;
      }
      
      map $http_upgrade $connection_upgrade {
          default Upgrade;
          ''      close;
      }
      
      server {
          listen 443 ssl http2;
          server_name example.com;
          ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
      
          location / {
              proxy_set_header Host $host;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_set_header X-Forwarded-Port $server_port;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_pass http://wordpress;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection $connection_upgrade;
              proxy_read_timeout 900s;
          }
      }
      
      server {
          listen 80;
          server_name example.com;
          return 301 https://$server_name$request_uri;
      }

      Then start the proxy container that will use the SSL certificates.

      sudo docker run -d -p 443:443 -p 80:80 \
      -v ~/docker/letsencrypt:/etc/letsencrypt \
      -v ~/docker/nginx:/etc/nginx/conf.d \
      --restart=unless-stopped \
      --network=wordpress-compose_default \
      --name=nginx-proxy nginx
      

      Once running, the container will connect to the “wordpress-compose_default” network and you should be able to reach your WordPress website now secured using Let’s Encrypt certificates.

      1. That worked perfectly! Thanks so much for your help! One more question, would I just run the same Certbot command again to renew the SSL certificate once it expires or would I use a different command? If I could set it up to auto-renew, that would be even better.

        1. Glad to hear. The Let’s Encrypt certificates are valid for 90 days and can be renewed when they have less than 30 days left before expiry. You can renew your certificates by using the same docker image but with the renew command instead. E.g.

          docker run -it --rm -p 80:80 -v ~/docker/letsencrypt:/etc/letsencrypt certbot/certbot renew

          You can test this even before your certificates are about to expire by adding --dry-run at the end of the above command. If you want to automate the renewal, you could set up a cronjob to run the renew command periodically. More information about this in our Let’s Encrypt tutorial. The only difference is that your certbot is running inside a container and requires the docker related parameters in addition to the certbot renew command.

          1. Thanks! I tried running it but got this error:

            “Bind for 0.0.0.0:80 failed: port is already allocated.”

            I tried stopping wordpress-compose first but still got the same error.

          2. Nevermind! I forgot I needed to stop nginx as well. It worked after I did that. Problem is that I don’t know how to start it up again now. What command would I use for that?

          3. Just wanted to update to say that I figured it out. Before renewing, you need to stop nginx-proxy by using this command:

            docker stop nginx-proxy

            and then start it up again after with:

            docker start nginx-proxy

          4. Yeah, you’re right, the proxy will bind to the ports 80 and 443 while running and needs to be stopped to be able to renew the certificates.

  6. Sorry to bother you again. Haha. I was just wondering if it would be possible to host two sites on the same server? I assume I can probably do that with the nginx-proxy? I just want to host a basic non-SSL HTML site in addition to the WordPress one I have now. I know typically you would do this with the virtual hosts file but I don’t have access to it since it’s in the docker container. If you could help me with this I would really appreciate it!

  7. So I am banging my head against a wall, I get access denied for user @ root even though both passwords are set the same in the compose file

    wordpress_1 | MySQL Connection Error: (1045) Access denied for user ‘root’@’172.17.0.3’ (using password: YES)
    mariadb_1 | 2020-10-13 4:13:27 12 [Warning] Access denied for user ‘root’@’172.17.0.3’ (using password: YES)

    anyone have any ideas?

    1. Hi Phil, thanks for the question. MariaDB is commonly configured to only allow connections to the localhost address 127.0.0.1. If you are running the database and WordPress containers of separate servers, the bind-address needs to be set accordingly.

Leave a Reply

Your email address will not be published. Required fields are marked *

Locations

Helsinki (HQ)

In the capital city of Finland, you will find our headquarters, and our first data centre. This is where we handle most of our development and innovation.

London

London was our second office to open, and a important step in introducing UpCloud to the world. Here our amazing staff can help you with both sales and support, in addition to host tons of interesting meetups.

Singapore

Singapore was our 3rd office to be opened, and enjoys one of most engaged and fastest growing user bases we have ever seen.

Seattle

Seattle is our 4th and latest office to be opened, and our way to reach out across the pond to our many users in the Americas.