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:
- Zero-downtime deployment with git
- Easily manage multiple apps/domains/certificates on one server
- 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
- Set up two remote Kirby installs on the same server (dev and live)
- Set up persistent storage for the sites (necessary to keep content between deployments)
- Set up domains and SSL certificates with Let’s Encrypt
- Bonus: Deployment via GitHub actions
- 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"
- On GitHub, click on the
Settings
tab and then clickSecrets
in the left-hand column. - Click on
New repository secret
- 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-----
) - 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 examplevim-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.