Back to Blog

How to Install WordPress with Docker Compose

How to install WordPress with Docker Compose: MariaDB and WordPress containers, passwords in .env, Nginx and SSL, upload limits, backups and updates.

Nodesty Team5 min readServers
The words WordPress on Docker with Compose, MariaDB and Volumes on a dark Nodesty background

Installing WordPress with Docker Compose means running WordPress and its database as two containers defined in a single file. You don't need to install PHP, a web server or a database on the server; everything comes from official images. You can run several sites on the same server, isolated from each other, and move a site to a new server by copying its folder and data.

This guide covers installing WordPress and MariaDB with Docker Compose, storing passwords securely, putting Nginx and SSL in front, upload limits, backups and updates. If Docker isn't installed, follow our Docker installation guide first.

Folder structure

Create a folder for the site:

mkdir -p ~/sites/yourdomain && cd ~/sites/yourdomain

This folder will hold three files: compose.yaml, .env and uploads.ini.

Step 1: Put the passwords in the .env file

Instead of writing passwords directly into the compose file, keep them in a separate .env file:

DB_PASSWORD=a-long-random-password
DB_ROOT_PASSWORD=another-long-random-password

You can use the openssl rand -base64 24 command to generate random passwords. Protect the file so that only you can read it: chmod 600 .env. If you keep this folder in a Git repository, add the .env file to .gitignore.

Step 2: compose.yaml

services:
    db:
        image: mariadb:11
        restart: unless-stopped
        environment:
            MARIADB_DATABASE: wordpress
            MARIADB_USER: wordpress
            MARIADB_PASSWORD: ${DB_PASSWORD}
            MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
        volumes:
            - db:/var/lib/mysql

    wordpress:
        image: wordpress:php8.3-apache
        restart: unless-stopped
        depends_on:
            - db
        environment:
            WORDPRESS_DB_HOST: db
            WORDPRESS_DB_NAME: wordpress
            WORDPRESS_DB_USER: wordpress
            WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
            WORDPRESS_CONFIG_EXTRA: |
                if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
                    $$_SERVER['HTTPS'] = 'on';
                }
        ports:
            - '127.0.0.1:8080:80'
        volumes:
            - wp:/var/www/html
            - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro

volumes:
    db:
    wp:
  • db: The MariaDB database. The data is kept in the db volume; it isn't lost even if the container is deleted.
  • wordpress: The official WordPress image (PHP and Apache included). WordPress files, uploads and plugins are kept in the wp volume.
  • 127.0.0.1:8080:80: Exposes WordPress only to the server itself. Outside access will go through Nginx in front of it. We explain in our Docker installation guide that ports published by Docker can bypass the firewall.
  • WORDPRESS_CONFIG_EXTRA: Lets WordPress correctly detect when the Nginx in front of it uses HTTPS; otherwise you can run into redirect loops. In the compose file, the $ character is written as $$.
  • The database port isn't published. WordPress reaches the database by the name db over Docker's internal network; the database is never exposed to the outside.

Step 3: Upload limits

The uploads.ini file sets PHP's upload and memory limits:

upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120

We explain what these settings do in our How to change the PHP version guide.

Step 4: Start it

docker compose up -d
docker compose ps

Both containers should be in the running state. On the first start, preparing the database takes a few seconds.

Step 5: Nginx and SSL

Put Nginx on the server in front of the WordPress container as a reverse proxy. /etc/nginx/sites-available/yourdomain.com:

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;
    client_max_body_size 64M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site and get SSL with Certbot:

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

client_max_body_size should match the upload limit in the uploads.ini file; otherwise large uploads stop at Nginx with a 413 error. Then open https://yourdomain.com and complete the WordPress setup wizard. We cover the details of Nginx and SSL in our Nginx installation guide.

Backups

A backup of a WordPress site has two parts: the database and the files.

Database:

docker compose exec -T db sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" wordpress' > db-$(date +%F).sql

Files (the wp volume):

docker run --rm -v yourdomain_wp:/data -v "$(pwd)":/backup alpine \
  tar -czf /backup/wp-$(date +%F).tar.gz -C /data .

Volume names start with your folder name (like yourdomain_wp); you can see the full name with docker volume ls. We cover automating backups with cron and keeping them somewhere independent of the server in our Server backups guide.

Updates

  • WordPress, plugins and themes: Because the files are kept in a volume, they're updated from the admin panel as usual.
  • Images (PHP, Apache, MariaDB): Update the images regularly to get system packages and security fixes:
    docker compose pull
    docker compose up -d
    

    Updating the image doesn't overwrite your WordPress files. Always take a database backup before changing MariaDB's major version.

Multiple sites

Create a separate compose.yaml in a separate folder for each site and give each one a different local port (127.0.0.1:8081:80, 127.0.0.1:8082:80 ...). Nginx in front of them routes each domain name to the right port. Giving each site its own database container keeps a problem on one site from affecting the others.

WordPress with Docker at Nodesty

Nodesty AMD Ryzen 9 9950X VPS plans render PHP pages quickly thanks to their high single-core speed; with full root access, you can run Docker, Nginx and several WordPress sites on the same server. NVMe storage, nShield DDoS protection and free daily backups kept for 7 days are included in the plan. If you only want to publish a WordPress site without managing a server, our web hosting plans with LiteSpeed are a more practical choice; we cover the setup in our WordPress installation guide.

Frequently Asked Questions

Docker or web hosting for WordPress?

If you only want to publish a WordPress site, web hosting is easier; server management, updates and security are the provider's job. If you want to run several sites or other services on the same server, control the environment completely and move sites easily, Docker gives you flexibility.

Do I need to replace the container when I update WordPress?

Because the WordPress files are kept in a volume, you update the core, plugins and themes from the admin panel as usual. Updating the image refreshes the PHP version and system packages; it doesn't overwrite your WordPress files.

Can I run several WordPress sites on the same server?

Yes. Create a separate folder and compose.yaml file for each site and give each one a different local port. Nginx in front of them routes each request to the right container based on the domain name. Giving each site its own database container keeps the sites isolated from each other.

I get the Error establishing a database connection message. Why?

The WordPress container can't connect to the database. Check that the database container is running with docker compose ps, make sure the passwords in the .env file are the same for both containers, and on the first setup wait a few seconds for the database to be ready.

I changed the password in the .env file but it doesn't work. Why?

The MariaDB image sets the user and password only when the database is first created. If the database volume already exists, a change in the .env file doesn't affect the existing user. You need to change the password from inside the database, or delete the volume on a fresh installation.

Where are my uploaded files and plugins stored?

All of WordPress's files, including uploads and plugins, are stored in the wp volume defined in the compose file. Even if you delete and recreate the container, your files aren't lost as long as the volume stays in place.

Nodesty

About the author

Nodesty Team · Infrastructure & Support

The Nodesty team runs our servers, network and DDoS protection day to day and answers customer support requests around the clock. These guides come from that work.

Related articles

Launch your server now.

Build, scale, and conquer without limits on Nodesty's premium infrastructure.

                                                                                                                                                                                                                            
                                                                                                                                                                                                                            
                                                                                                                                                                                                                            
                                                                                                                             ············································                                                   
                                                ··································································································..........···························                                     
                                       ················..........................................................................................................................··············                             
                                  ·············.................::::::::::::::::::::::::::::::::::::::::::::::....................:::::::::::::::::::::::::::::::::::::::................···········                        
                             ············................:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::............·········                     
                         ············.................:::::::::::::::::::::::::::::::::::::::::::::::::::------------::::::::::::::::::::::::::::::----------------:::::::::::::::::::::..........········                  
                    ············......................::::::::::::::::::::::::::::::::::::------------------------------------------------------------------------------------:::::::::::::.........········                
                ··········.............................................::::::::::::::::::-------------==========================================-----------------------------------:::::::::::........·······               
             ········..................:.....................................::::::::::--------=======+++++++++++++++++++++++++++++++++++++=========================------------------::::::::::.......·······              
           ······.........::::::::::::::::::::................................:::::::-----=====++++xxxxxx**************************xxxxxxxxx++++++++++++++===================------------::::::::.......·······             
          ·····.......:::::::::::::::::::::::::::.............................:::::----===+++xxx****%%%%&&&&&&#############&&&&&&&%%%%%*******xxxxxxxx++++++++++++++==============----------:::::::......······             
         ····......::::::::-------------:::::::::::..........................::::---===+++xx***%%%&&&####@@@@@@@@@@@@@@@@@@@@@@@@####&&&&&%%%%*******xxxxxxxxx+++++++++++++===========--------::::::......·····             
        ····.....:::::::--------------------:::::::::.......................::::---===++xxx**%%&&&###@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@###&&&&%%%%%********xxxxxxxxxxx++++++++++++========------::::::.....······            
        ····.....:::::--------------------------::::::::....................::::---===++xx***%%&&&####@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@###&&&&&%%%%%**********xxxxxxxxxxxxx+++++++++=======------:::::.....·····            
        ····....::::::-------=============---------:::::::::................:::::---===+++xx***%%%&&&#####@@@@@@@@@@@@@@@@@@@@@@@@@#####&&&&&%%%%%%*************xxxxxxxxxxxxxx+++++++++======-----:::::....·····            
         ····....:::::-------================---------:::::::::...............:::::----===++++xxx***%%%%&&&&&&#################&&&&&&%%%%%%%*******************xxxxxxxxxxxxxxxxx++++++++======----:::::....·····            
         ·····....:::::--------=================---------::::::::::..............:::::::-----====+++++xxxxxx**************************xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx++++++++=====-----::::....·····            
          ·····.....:::::--------=================----------:::::::::::.................:::::::::-------=============++++++++++++++++++++++++++++++++++++xxxxxxxxxxxxxxxxxxxxxxx++++++++======-----::::....·····            
            ·····.....:::::::----------===========-------------::::::::::::.........................::::::::::::::::::------------------=============+++++++++++++++++++++++++++++++++=======-----::::.....·····            
              ······......:::::::---------------------------------:::::::::::::........................................:::::::::::::::-----------============+++++++++++++++++++++========------:::::.....·····             
                 ······........:::::::::---------------------------:::::::::::::::::...........................................:::::::::::::------------=============================--------::::::.....······              
                    ·········.........:::::::::::::::-------------::::::::::::::::::::::::.......................................::::::::::::::::--------------------------------------::::::::......·······                
                          ··········............::::::::::::::::::::::::::::::::::::::::::::::::::................................::::::::::::::::::::::::::-------------:::::::::::::::.........········                   
                                  ············..................:::::::::::::::::::::::::::::::::::::::::::.........................:::::::::::::::::::::::::::::::::::::................···········                        
                                             ················.............................................................................................................·················                                 
                                                           ······················................................................................···························                                                
                                                                               ·········································································