Skip to content

Kirby 3.9.8

Git-based deployment with Dokku

Introduction

In the vein of the Kirby Meets Docker Recipe, Dokku allows you to containerize and run multiple apps/domains on a single server, manage deployments with git and easily manage domains and certificates with Let’s Encrypt. Dokku can be very helpful for the following reasons:

  1. Zero-downtime deployment with git
  2. Easily manage multiple apps/domains/certificates on one server
  3. Can be simpler to setup/manage than Docker itself

What is Dokku?

According to the Dokku documentation:

Dokku is an extensible, open source Platform as a Service that runs on a single server of your choice.

Behind the scenes, Dokku makes use of Docker containerization to run any number of applications on your server. It allows you to essentially create your own PaaS or something like Heroku where you can deploy and manage your apps. In fact, Dokku will make use of the official Heroku PHP Buildpack to run your Kirby site.

Prerequisites and Requirements

  • Access to cloud servers such as Hetzner or Digital Ocean
  • A fresh installation of Ubuntu 18.04/20.04 x64, Debian 9+ x64 or CentOS 7 x64 (experimental) with the FQDN set
  • At least 1 GB of system memory. Workaround for less than 1GB
  • Basic familiarity with Linux and the command line
  • Git and composer installed locally
  • An SSH key
  • A domain name, the ability and knowledge to configure DNS

Goals of this recipe

  1. Set up two remote Kirby installs on the same server (dev and live)
  2. Set up persistent storage for the sites (necessary to keep content between deployments)
  3. Set up domains and SSL certificates with Let’s Encrypt
  4. Bonus: Deployment via GitHub actions
  5. Tips: How to enter a running app, how to add packages to the container

Install Dokku

Start a fresh cloud server instance (in my case, I will be using Ubuntu 20.04 x64 on Hetzner). You may also follow the Dokku install instructions to ensure you are installing the latest version.

# ON SERVER
# for debian systems, installs Dokku via apt-get
wget https://raw.githubusercontent.com/dokku/dokku/v0.26.6/bootstrap.sh;
sudo DOKKU_TAG=v0.26.6 bash bootstrap.sh

Then connect your SSH key (i.e. the public key on your local machine, or wherever you plan to deploy from) and set up the domain accordingly.

# ON SERVER
# usually your key is already available under the current user's `~/.ssh/authorized_keys` file
cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin

# you can use any domain you already have access to
dokku domains:set-global replace.with.your.domain

Pay attention to the domain name on the last line. For example if your domain is example.domain, you would want to run dokku domains:set-global example.domain

Create Dokku apps

You can create as many apps as you want. In the case of our tutorial, we will create two apps named live and dev to show how we are able to run multiple apps/sites on one server and deploy to them independently.

Dokku commands typically follow a syntax such as

dokku primary_command:secondary_command app_name variables

If you are unsure of commands, you can simply run dokku to see a list and then use the command without arguments for more specific help. For example, to learn more about app management, type dokku apps.

You will see we create apps with apps:create <app>, so we will go ahead and create our apps:

# ON SERVER
dokku apps:create live
# -----> Creating live...
dokku apps:create dev
# -----> Creating dev...

Clone the Kirby Starterkit to your local machine

Open your local machine, open a terminal and type

# LOCAL
git clone https://github.com/getkirby/starterkit.git
cd starterkit

Generate composer.lock and adjust .gitignore

I prefer taking vendor items out of the repository and relying on composer to install our packages for us on deploy. Open the .gitignore file in the starterkit and add to the top of the file. I also prefer removing content from version control which would typically be necessary if you are running multiple instances anyhow:

# Vendor Items
# ---------------
/kirby
/vendor

# Content
# ---------------
/content/*

Then commit your changes with

git add .gitignore
git commit -m 'Update .gitignore'`

In the terminal, run composer update to generate a composer.lock file. Commit the new composer.lock file.

# LOCAL
composer update
git add .
git commit -m 'Add composer.lock, clear vendor files'

In order to run the panel install remotely, you will want to edit /site/config/config.php accordingly:

return [
  'panel' =>[
    'install' => true
  ]
];

Add and commit your changes to the git repository:

# LOCAL
git add site/config/config.php
git commit -m 'Allow remote panel install'

After things are up and running, you should remove this or set up domain specific config files.

Set up direct git deployment to dokku

To deploy directly to our new apps, you will want to add corresponding git remotes for each app.

# LOCAL
# git remote add remote_name dokku@IP_OF_SERVER:app_name
git remote add live dokku@IP_OF_SERVER:live

Amazingly, Dokku autodetects our app and knows to use the Heroku PHP Buildpack automatically, so we can already go ahead and deploy our app. If you need more control, you will want to create a Procfile in the root of your repository and have a look at the documentation.

Let's start by deploying our live app:

# LOCAL
git push live

You will see a fair bit of output which should end with a successful deployment:

=====> Application deployed:
       http://live.example.domain

To x.x.x.x:live
 * [new branch]      main -> main

Set up a dev branch and corresponding remote

Say we also want a dev branch to be able to show updates without affecting our live site.

# LOCAL
git checkout -b dev
# Switched to a new branch 'dev'
git remote add dev dokku@IP_OF_SERVER:dev

Then we can also deploy accordingly:

# LOCAL
git push dev

Set up persistent storage

When a dokku app is deployed or rebuilt, everything within is destroyed. Thus it is critical to set up persistent storage so that your content, accounts etc remain between deployments.

Typically, I would create a separate storage for live and for dev:

# SERVER
dokku storage:ensure-directory live
dokku storage:ensure-directory dev

This creates the two following directories and sets their permissions correctly (dokku apps make use of the 32767:3267 user:group):

/var/lib/dokku/data/storage/live
/var/lib/dokku/data/storage/dev

The folders I would typically persist across deployments are the content, media and site/accounts folders. Thus we mount the storage to our apps accordingly:

# SERVER
# storage:mount <app> <host-dir:container-dir>
#
# LIVE app mounts
dokku storage:mount live '/var/lib/dokku/data/storage/live/content':'/app/content'
dokku storage:mount live '/var/lib/dokku/data/storage/live/media':'/app/media'
dokku storage:mount live '/var/lib/dokku/data/storage/live/site/accounts':'/app/site/accounts'
#
# DEV app mounts
dokku storage:mount dev '/var/lib/dokku/data/storage/dev/content':'/app/content'
dokku storage:mount dev '/var/lib/dokku/data/storage/dev/media':'/app/media'
dokku storage:mount dev '/var/lib/dokku/data/storage/dev/site/accounts':'/app/site/accounts'

When complete, you can confirm the set up with dokku storage:report. Then you will want to restart your apps to set up the persistent storage accordingly:

# SERVER
dokku ps:restart live
dokku ps:restart dev

You should now see the new folders created:

# SERVER
ls /var/lib/dokku/data/storage/live/
# content media site
ls /var/lib/dokku/data/storage/dev/
# content media site

Load the starterkit content and test

As the content is no longer part of the repository, you will need to manually copy the starterkit content. The easiest way would be to do so on the server directly:

# SERVER (as root)
cd ~
wget -O starterkit.zip https://github.com/getkirby/starterkit/archive/refs/heads/main.zip
unzip starterkit.zip
cp -a starterkit-main/content/* /var/lib/dokku/data/storage/live/content/
cp -a starterkit-main/content/* /var/lib/dokku/data/storage/dev/content/
chown -R 32767:32767 /var/lib/dokku/data/storage/live/content
chown -R 32767:32767 /var/lib/dokku/data/storage/dev/content
rm -r starterkit-main starterkit.zip

Test things out by setting up the panel and an account on the live app: https://www.example.domain/panel

You should see a working panel and all the starterkit content. Try adding a new Note in the panel called 'Test Note'.

Confirm that test-note was created in the storage folder:

# SERVER
ls /var/lib/dokku/data/storage/live/content/2_notes/_drafts
# in-the-jungle-of-sumatra  test-note

Set up domains and DNS

You can see which domains are assigned to our apps:

# SERVER
dokku domains:report

Let's set the www subdomain to our live app:

# SERVER
# dokku domains:set app_name domain
dokku domains:set live www.example.domain

If you would like to add other domains you can do so with the domain:add command:

# SERVER
# dokku domains:add app_name domain
dokku domains:add live another.example.domain

Confirm your settings are correct:

# SERVER
dokku domains:report

When you are satisfied with your domains, set up A records through your DNS management to point to the server's IP address. If you don't have a domain name, you could try a service like FreeDNS.

In the worst case, you could also edit your hosts file so you can test without using a domain/DNS. This would allow you to visit the page without SSL, but will prevent you from using Let’s Encrypt or making the site available to others.

Once complete, you should be able to visit your sites without encryption. Open your browser and try visiting the pages.

Setup SSL and Let’s Encrypt

The dokku-letsencrypt plugin allows you to configure and manage Let’s Encrypt very easily. Get started by installing the plugin:

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

Once installed, you will have many commands available, but first you will need to configure an email address for your apps so that Let’s Encrypt can notify you of issues (upcoming expirations, issues with renewal etc):

# SERVER
#
# A single email used for all apps:
# dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=your@email.tld
#
# Or you can configure each app individually:
# dokku config:set --no-restart app_name DOKKU_LETSENCRYPT_EMAIL=your@email.tld
dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=your@email.tld

Then you can go ahead and enable Let’s Encrypt on your apps:

# SERVER
dokku letsencrypt:enable live
dokku letsencrypt:enable dev

If your domains/DNS are setup correctly you should see a fair bit of output, and in particular:

# -----> Certificate retrieved successfully.
# -----> Installing let's encrypt certificates

If you are having trouble, it is possible your DNS settings have not yet propagated or are not setup correctly.

You should now be able to visit your sites with valid encryption certificates: https://www.example.domain, https://dev.example.domain

The certificates should automatically renew, but should you ever have to do so manually you can run dokku letsencrypt:auto-renew

Bonus: Deployment with GitHub Actions

If you are using GitHub, you can connect this to your deployment via GitHub Actions. With this, you are able to deploy to GitHub, which will in turn deploy to your dokku instance.

Go ahead and create a new GitHub repository and add a remote accordingly. For the purposes of this tutorial, I will create a repository called kirby-dokku-example and push everything up to GitHub.

# LOCAL
# In starterkit folder
git remote add origin git@github.com:username/kirby-dokku-example.git

In your repository, create the folder/file:

# /.github/workflows/deploy-live.yml
name: Deploy LIVE app

on:
  push:
    branches:
      - main

jobs:
  build:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Deploy Live app
      uses: idoberko2/dokku-deploy-github-action@v1
      with:
        ssh-private-key: ${{ secrets.PRIVATE_KEY }}
        # MAKE SURE TO ADJUST THE DOKKU-HOST TO YOUR SERVER IP OR APP DOMAIN
        dokku-host: <ip of server>
        app-name: live

On your local machine, generate a private key for the deployment which will be used by the GitHub action via the ${{ secrets.PRIVATE_KEY }} variable.

# LOCAL
ssh-keygen -t ed25519 -C "deploy-live@github"
  1. On GitHub, click on the Settings tab and then click Secrets in the left-hand column.
  2. Click on New repository secret
  3. Name the secret PRIVATE_KEY and paste in the contents of the key you just generated. (The private key file without the .pub extension, it should begin with -----BEGIN OPENSSH PRIVATE KEY-----)
  4. Add the public key to your dokku server:
# LOCAL
cat path_to_key.pub | ssh root@example.domain dokku ssh-keys:add GH_DEPLOY`

Commit the new workflow/action and GitHub should automatically deploy your app:

# LOCAL
git add .github/workflows
git commit -m 'Add GitHub action and deploy'

If you navigate to the Actions tab on GitHub, you should already see the workflow in progress. Clicking on it, you should see the Deploy action underway.

By setting up different workflows, you could choose to set up certain branches to deploy to different apps accordingly. For example, you could also create a deploy-live and deploy-dev branch, and thus keep the running apps separate from your work in progress.

Tips

  • If you need to open a shell inside one of your apps, try dokku enter <appname>. For example, dokku enter live. Sometimes, you will need to also include the process: dokku enter live web.
  • The buildpacks are very minimal, if you want a text editor included inside your container, create a file named apt-packages at the root of the repository and add the necessary packages, for example vim-tiny. You will also need to install the dokku-apt plugin.
  • You can view the nginx/server logs on an app with dokku logs <appname>
  • If you are having issues, try turning trace mode on with dokku trace:on. The output will be significantly more verbose, but may help you pinpoint issues that are otherwise hidden.