Deploying a CraftCMS website with Dokku
Tags: craftcms, php, dokku, devops
Published: 2019-09-06 10:31:46

I recently set up my personal blog with Craft CMS and wanted to host it on my own server. To make the most of my own servers, I tend to run web applications using Dokku. This blog post goes through the details of the process of doing so for your own projects.

## What is Dokku?

Dokku is a small PaaS service powered by Docker that you can run on your own servers. Think of it as a self-hosted command-line Heroku.

It's great when you want to host multiple small projects on one server for cheap. Although it's unlikely you'll beat Heroku's free tier on price, it definitely beats the $9 tier for small projects. Your website won't go to sleep when idle, you can set up SSL (quite easily, too), and as mentioned above you can you can host multiple websites all on the same server.

In general, the advantage of Dokku over a standard server is that if you're projects use different versions of the same dependences, e.g. one project is on PHP 7.3 and another on 7.2, Dokku (and Docker) makes this super easy to do.

The other great benefits of Dokku include:

* Heroku-like git push deployment.
* Compatible with Heroku buildpacks.
* Easy to setup and link databases, storage, cache, etc to your app.
* Requires very little to get started - a $5 DO instance will work fine for multiple small applications.

For more information and for installation see the [Dokku guides](dokku.viewdocs.io/dokku/). The rest of this tutorial assumes you have Dokku installed on a server and are able to SSH into the server to run commands. Also, that you have a CraftCMS application in a Git repo ready to deploy.

## What is CraftCMS?

If you're reading this theres a good chance you already know what CraftCMS is. If you've come accross this article by chance and don't know: CraftCMS is a fantastic Content Management System. It's built with modern tools including PHP 7, Twig, and the Yii framework. It's easy to make plugins, and it's ridiculously effective for scaffolding admin areas quickly. 

All you really need to concentrate on is integrating your design into the front end. And even then, with Twig, it's a really enjoyable developer experience. 

## Making your CraftCMS project Dokku-ready

Update your `composer.json` file and add the following.

```diff
 require: {
+    "php": "~7.2.0",
+    ext-gd: *,
    // ...
 },
  extra: {
+    heroku: {
+      "document-root": "web",
+      compile: [
+        chmod 755 storage
+      ]
+    }
  }
```

We need to specify the PHP version so Dokku knows which one to use. CraftCMS also requires the GD library or Imagick for image editing.

Set the document root to `web` and also set the correct permissions for any directories that deal with uploads.

Add a new file called `Procfile` in the root of your application.

```procfile
web: vendor/bin/heroku-php-nginx -C nginx.conf.d/nginx.conf web
```

This lets us add our own custom nginx configuration.

Add the nginx configuration file to `nginx.conf.d/nginx.conf`

```nginx
	index index.html index.htm index.php;

	location ^~ /admin {
		try_files $uri $uri/ /index.php?$query_string;
	}
	location ^~ /cpresources {
		try_files $uri $uri/ /index.php?$query_string;
	}
  location / {

    gzip on;
    gzip_min_length  1100;
    gzip_buffers  4 32k;
    gzip_types    text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml  application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml;
    gzip_vary on;
    gzip_comp_level  6;

		try_files $uri $uri/ /index.php?$query_string;
  }

  error_page 400 401 402 403 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 /400-error.html;
  location /400-error.html {
    root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
    internal;
  }

  error_page 404 /404-error.html;
  location /404-error.html {
    root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
    internal;
  }

  error_page 500 501 502 503 504 505 506 507 508 509 510 511 /500-error.html;
  location /500-error.html {
    root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
    internal;
```

Now to tell Dokku which buildpacks to use, create a `.buildpacks` file in the project root as well with the following content.

```buildpack
https://github.com/heroku/heroku-buildpack-php
```

That's pretty much it for the CraftCMS side of things. I'd also advise setting up either S3 or Google Cloud Storage for media uploads, this takes out the pain of worrying about persistent storage.

## Configuring Dokku

On your server, create the app with the domain name of your choice

```bash
dokku apps:create <yourdomainname.com>
```

set up a Postgres Database (Or MySQL if you prefer) and link it to the app

```bash
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
dokku postgres:create <databasename>
dokku postgres:link <databasename> <yourdomainname.com>
```

Run `dokku postgres:info databasename` to get the database credentials. We'll need to parse the URL in the following format, and add environment variables for each.

```
dokku postgres:info <databasename>
# Dsn: postgres://<USERNAME>:<PASSWORD>@<SERVER>:5432/<DATABASE>
dokku config:set <yourdomainname.com> ENVIRONMENT=production
dokku config:set <yourdomainname.com> SECURITY_KEY=YOUR_CRAFT_SECURITY_KEY
dokku config:set <yourdomainname.com> DB_DRIVER=pgsql
dokku config:set <yourdomainname.com> DB_SERVER=<databasename>
dokku config:set <yourdomainname.com> DB_USER=<USERNAME>
dokku config:set <yourdomainname.com> DB_PASSWORD=<PASSWORD>
dokku config:set <yourdomainname.com> DB_DATABASE=<DATABASE>
dokku config:set <yourdomainname.com> DB_PORT=5432
dokku config:set <yourdomainname.com> DB_SCHEMA=public
dokku config:set <yourdomainname.com> DB_TABLE_PREFIX=
```

Note that, you should double check that these env variables are called from within your Craft config files.

If you require permanent storage on the server (e.g. No S3 or GCS) then you can set that up using `storage:mount`.

```bash
# optional permanent storage set up
dokku storage:mount <yourdomainname.com>/var/lib/dokku/data/storage/<anyuniqueidentifier>:/app/storage
```

## First deploy

If you have content and users already set up on your local which you wish to migrate to production. You're best bet is to use a tool such as [TablePlus](https://tableplus.com) or [Sequel Pro](https://sequelpro.com/) to connect directly to the database (Use the Postgres Credentials you have found from the above commands and use SSH to connect directly to it). Dump your local tables and content and import the database to the remote database. Double check your `sites` table and if your domain is hardcoded to the `baseUrl`. If it is, then update it to the live domain.

If you don't want to do this and would rather start afresh, you can instead SSH into the app and run `./craft setup` from within the app after your first deploy. You can do this with `dokku run <yourdomainname.com> bash` and then `cd` into the `app` directory.

In your local project you need to add a new git remote location with the credentials for your server. After that you can push!

```bash
git remote add dokku dokku@serverip:domainname
git push dokku master
```

Hopefully if all went well you should see the set up page for Craft (Or your website if you've imported the database) when you try to access the website. This is assuming you've pointed your domain to the server.

## Syncing updates

If, like me, you prefer to restrict settings changes on the live site so that they are kept in sync with your git repo, make sure your `config/app.php` has the following settings.

```php

'*' => [
  // ...
  'useProjectConfigFile' => true,
  // ...
],
'production' => [
  // ...
  'allowAdminChanges' => false,
  // ...
]
```

You'll also want to add this Dokku hook so that the Project YAML file is synced automatically. This prevents your site from going down everytime you push an update while it waits for you to sync settings from the admin.

```json
// app.json
{
  "scripts": {
    "dokku": {
      "postdeploy": "./craft project-config/sync"
    }
  }
}
```

And that's it! Save, Commit, Push and updates should just work!

## SSL

If you're using CloudFlare then SSL should just work out of the box. If not, Dokku has commands for setting up AutoSSL which you can find here: http://dokku.viewdocs.io/dokku/configuration/ssl/

## Closing thoughts

It took quite some time for me to set this up this correctly. Now that I have the configuration for it deploying updates with Dokku is incredibly easy. I've been running this set up with Google Cloud Storage for media for a few weeks now with no issues. I hope this guide will help others who also want a similar set up!