How to install WordPress with dokku

A tutorial on how to install WordPress with Dokku on a server. Learn how to configure HTTPS, backups and databases for WordPress.

I’ve tried installing WordPress with dokku, but ended running into problems in all guides I followed, from lack of permissions, mixed content warnings on Chrome, admin not accessible, themes not being installed, etc. Building on top of those guides, I managed to get it running (you’re reading this post on a WordPress instance installed on dokku).

If you want to run a docker image as a dokku application, don’t miss our tutorial!

This guide assumes that you already have dokku installed on the host machine. If you haven’t done it, please do. I recommend the official installation guide.

Create the DNS record

Start by creating a A record pointing the domain name you bought to the server that will be running WordPress.

I recommend doing this at the beginning, so that when we get to the point of accessing the WordPress instance, the domain name servers changes have propagated.

This step depends on your registrar, but it should have documentation (examples for namecheap and cloudflare).

Set up WordPress (locally)

Now that our record is propagating through the internet, we can start working on configuring WordPress to be installed on Dokku.

Let’s start by downloading the latest version of WordPress:

ShellScript
# Download latest version
curl -LO https://wordpress.org/latest.zip
unzip latest.zip

cd wordpress

# Initialize repository and make initial commit 
git init
git add .
git commit -m "Initial commit"

Right now we have a baseline state of our WordPress blog. If we screw things up, we can always revert to this initial commit.

Configure wp-config with the database credentials (locally)

To configure WordPress to be installed on Dokku, we need to change our wp-config file.

Copy and rename wp-config-sample.php to wp-config.php. This is the file that holds the initial configuration to be loaded. We need to update this with the database connection URL that will be injected over environment variables by dokku.

Replace the lines

wp-config.php
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'database_name_here' );

/** Database username */
define( 'DB_USER', 'username_here' );

/** Database password */
define( 'DB_PASSWORD', 'password_here' );

/** Database hostname */
define( 'DB_HOST', 'localhost' );

/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );

/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );

with

wp-config.php
$url = parse_url(getenv("DATABASE_URL"));

$host = $url["host"];
$username = $url["user"];
$password = $url["pass"];
$database = substr($url["path"], 1);

/** The name of the database for WordPress */
define( 'DB_NAME', $database );

/** MySQL database username */
define( 'DB_USER', $username );

/** MySQL database password */
define( 'DB_PASSWORD', $password );

/** MySQL hostname */
define( 'DB_HOST', $host );

What this is doing:

  1. Load the environment variable DATABASE_URL . This env var will be injected by dokku into the running container once we link the database.
  2. Parse and split the different parts of the database URL into host, users, password and database variables respectively.
  3. Assign those values to the WordPress variables.

Configure authentication keys (locally)

Still on wp-config.php replace the authentication keys and salts. Head over to https://api.wordpress.org/secret-key/1.1/salt/, and replace this code in the wp-config file with the page output:

wp-config.php
define( 'AUTH_KEY',         'put your unique phrase here' );
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
define( 'NONCE_KEY',        'put your unique phrase here' );
define( 'AUTH_SALT',        'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
define( 'NONCE_SALT',       'put your unique phrase here' );

Configure HTTP redirect (locally)

Dokku has it own Reverse Proxy, Nginx that handles HTTPS. The way it works by default is that the requests will reach Nginx using HTTPS, but Nginx will forward the Request to WordPress using HTTP, since it is running on the same machine. However, WordPress expects all connections to reach it by HTTPS. This will cause a redirect loop.

To prevent this, we can use this snippet from the WordPress documentation, so that WordPress recognizes the HTTP_X_FORWARDED_PROTO header that Nginx will set if the origin request is sent over HTTPS (which it will).

Tip: on wp-config, copy this snippet to before the if ( ! defined( 'ABSPATH' ) ) {statement. I had some issues with the order inside the wp-config file

wp-config.php
define( 'WP_DEBUG', false );

/* Add any custom values between this line and the "stop editing" line. */

/* That's all, stop editing! Happy publishing. */

// in some setups HTTP_X_FORWARDED_PROTO might contain
// a comma-separated list e.g. http,https
// so check for https existence
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
    $_SERVER['HTTPS'] = 'on';
}

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	

Lastly, we can commit our changes, to make sure we do not loose any progress:

ShellScript
git add .
git commit -m "Configure wp-config.php"

Configure buildpack (locally)

On this step, we will configure the PHP environment that dokku will provide to the WordPress installation so that it will run with the correct dependencies. This was something that was frequently missing on the other tutorials, so please feel free to comment on any questions you have.

We need to nudge dokku into running our container with the correct dependencies installed, namely the PHP version. For that, we will use Composer, a dependency manager for PHP.

We need to create two files in the root directory, composer.json and composer.lock:

composer.json
{
  "require": {
    "php": "~8.2.0",
    "ext-mbstring": "*"
  }
}

Here we are setting the PHP version, selecting the latest that WordPress supports as of April 2023. We also take the opportunity to install a dependency that WordPress needs to to support UTF8 text. If you need a PHP dependency installed, this is the place to set it (don’t forget to update the composer.lock).

composer.lock
{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
        "This file is @generated automatically"
    ],
    "content-hash": "5e566c73e251588a692644ab2294cb80",
    "packages": [],
    "packages-dev": [],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": [],
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": {
        "php": "~8.2.0",
        "ext-mbstring": "*"
    },
    "platform-dev": [],
    "plugin-api-version": "2.3.0"
}

This is the corresponding composer.lock file. This file matches the compose.json file with the exact dependency versions that will be installed. Because this setup with very simple, there is nothing extraordinary about it. You can think about it like the pyproject.toml and poetry.lock in python projects using poetry.

Configure the Procfile (locally)

The Procfile instructs dokku how to start the container. We will use this opportunity to set some extra dependencies and configurations needed for WordPress. Again in the root of your directory, we will create three files:

Procfile
web: vendor/bin/heroku-php-apache2 -i custom_php.ini

You can read more about Procfiles in the dokku documentation. Basically we are creating a web server with php and apache installed that will serve requests using the mentioned custom_php.ini configuration, configured as:

custom_php.ini
upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 60
memory_limit = 512M
[ExtensionList]
extension=gd

This file expands the server’s limits to accept larger files (we will also do this to Nginx later in the tutorial), useful to upload images, themes and plugins, for example. We then take the opportunity to enable the gd extension, that WordPress uses for image manipulation.

Lastly, we also set the .htaccess file, to be used by Apache, following the WordPress own recommendations. We take the opportunity to disable xmlrpc, since it poses a security risk, and has had its functionality reduced over time.

.htaccess
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# Block WordPress xmlrpc.php requests
<Files xmlrpc.php>
order deny,allow
deny from all
# Uncomment and replace with your IP address, if you want to enable it 
# allow from xxx.xxx.xxx.xxx
</Files>

We can celebrate by committing our files:

ShellScript
git add .
git commit -m "Configure dokku"

Your folder should be looking something like this (with the files we changed highlighted):

ShellScript
wordpress git:(master) ls
composer.json
composer.lock
custom_php.ini
index.php
license.txt
Procfile
readme.html
vendor
wp-activate.php
wp-admin
wp-blog-header.php
wp-comments-post.php
wp-config.php
wp-config-sample.php
wp-content
wp-cron.php
wp-includes
wp-links-opml.php
wp-load.php
wp-login.php
wp-mail.php
wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php

We’ve finished all the local steps on how to install WordPress with Dokku. Let’s move to the server!

Creating the dokku app and domain (dokku server)

Now that everything is set up locally, we can need to work on the server so we can install WordPress with Dokku.

SSH into the server and let’s get started.

Start by creating the app (I’ll call it blog-wp) and adding the domain:

ShellScript
dokku apps:create blog-wp
dokku domains:add blog-wp example.com

Creating the database (dokku server)

WordPress requires an external database, that we will configure now. Dokku has a plugin that allows managing databases, from creating, linking to apps or backing them up. It will serve us perfectly. WordPress supports MariaDB or MySQL natively, and we’ll go for MariaDB.

ShellScript
# Install plugin
sudo dokku plugin:install https://github.com/dokku/dokku-mariadb.git mariadb

# Create database
dokku mariadb:create blog-db

# Link database to app
dokku mariadb:link blog-db blog-wp

Linking the database will inject the environment variables DATABASE_URL to the container, that will be picked up by our changes to wp-config.

Updating nginx settings, enabling SSL (dokku server)

Lastly, we need to increase the client_max_body_size parameter, so we are able to upload larger files (for example themes and plugins), just like we did in .htaccess before. We can do that without needing to open a Nginx configuration file using Dokku:

ShellScript
dokku nginx:set blog-wp client-max-body-size 50m

We can also take the opportunity to enable HTTPS in our site, after all it is 2023, and there is no good excuse to not having it. Again, Dokku gives us all we need to enable it, without much fuss. It will also configure Nginx to automatically redirect HTTP request to HTTPS, which is an added bonus:

ShellScript
# Install plugin
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

# Set the global email
dokku config:set --global [email protected]

# Setup cronjob to auto-renew certificates when they expire
dokku letsencrypt:cron-job --add

# Add https to site
dokku letsencrypt:enable blog-wp

Just a note from the official plugin documentation regarding Cloudflare:

If using this plugin with Cloudflare:

  • The domain dns should be setup in “Proxied” mode
  • SSL/TLS mode must be in “Full” mode
    • Using letsencrypt in “Flexible” mode will cause Cloudflare to detect your server as down
    • Using “Full” mode will require disabling SSL/TLS in cloudflare in order to renew the certificate.

If using “Flexible” SSL/TLS mode, avoid using this plugin.

See these two links for more details:

Deploying the WordPress site (locally)

We are running very close to install WordPress with Dokku! We are just missing the deploy. To do this, just add the dokku server as a git remote and push:

ShellScript
git remote add dokku dokku@<ip address>:blog-wp
git push dokku master

Now you can go to your domain and you should see the installation page. All is done!

Extra: persisting state between deploys (dokku server):

This is an extra after the WordPress installation is finished.

Because we are running WordPress inside a container, its state will not be persisted between restarts (e.g. if the server goes down, or we deploy a change). To prevent this, we can mount a directory to the container, so files like themes, plugings and media are kept between restarts.

The approach I followed is a bit hacky, but when I followed the other tutorials, I just ended up with a messed up wp-content folder, that did not have the themes, plugins, etc, because the host folders overrides the container files.

This is what I’m doing:

  1. Creating a storage folder for the app.
  2. Copying the existing files on the container to the storage folder.
  3. Mount the folder to the container.
  4. Restarting the container for the changes to take effect.
ShellScript
dokku storage:ensure-directory blog-wp --chown false

docker ps # find the container id
docker cp <container id>:/app/wp-content/. /var/lib/dokku/data/storage/blog-wp/

dokku storage:mount blog-wp /var/lib/dokku/data/storage/blog-wp:/app/wp-content


dokku ps:restart blog-wp

This has the added bonus that it is trivial to backup themes and plugins, one just this to zip the mount and copy it to another location (although you should consider a WordPress backup plugin).

Thank you so much for following along!

References I followed to learn how to install WordPress with Dokku



2 responses to “How to install WordPress with dokku”

  1. Thank you Paulo, you are the best!

  2. Thank you! Your tutorial explains things more clearly then others.
    I didn’t understand where do we get composer.lock though,.. but I just copied yours and it works, at least I deployed the clean wordpress.

Leave a Reply

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