Deploying a CraftCMS website with Dokku

Mitch

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:

For more information and for installation see the Dokku guides. 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.

 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.

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

	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.

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

dokku apps:create <yourdomainname.com>

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

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.

# 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 or Sequel Pro 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!

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.


'*' => [
  // ...
  '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.

// 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!

Spread the word

Share this article

Like this content?

Check out some of the apps that I've built!

Snipline

Command-line snippet manager for power users

View

Pinshard

Third party Pinboard.in app for iOS.

View

Rsyncinator

GUI for the rsync command

View

Comments