Setting Up Your Own PAAS Using Dokku
These are notes about how we setup Dokku on EC2 with Vagrant, Docker and Dokku.
Note: an earlier version of this post mentioned that the maintainers of Dokku worked at Deis, however that's incorrect — previously Deis sponsored the project, but this has now finished, and none of the maintainers work at or have worked at Deis.
Also, it appears that a custom Buildpack is no longer needed for releasing a static site (though use of the plugin mentioned below won't break the deploy)
What is Dokku?
Dokku allows us to spin up our own Platform-as-a-Service (PaaS) and create a Heroku-style environment that's cheap, and that we can easily deploy to simply by performing a git push
.
Under the covers, Dokku uses Nginx to serve sites, creating a reverse proxy Nginx config for each app that we deploy. Dokku also installs a command-line tool that allows us to configure and stop / start our applications.
We can deploy apps written using a range of technologies, including Node.js, PHP, Ruby-on-rails, and even static sites, and Dokku supports a variety of mechanisms to determine how to deploy an app, including:
- the presence of a
package.json
file, letting Dokku know that it's dealing with a node.js app - Procfiles
- Docker files
- Heroku-style Buildpacks
Dokku versus Dokku-alt
Our approach described below is based on the tutorial by Alex MaCaw at ClearBit, but we'll amend this to use Dokku rather than Dokku-alt.
Dokku-alt arose because people wanted Dokku installed complete with a range of verified plugins, and because the Dokku project wasn't being maintained after the founder, Jeff Lindsay, stepped aside.
However, the situation has now reversed, with Dokku receiving active support from it's new maintainers, and Dokku-alt now a number of versions behind Dokku.
Jeff Lindsay described his experience with stepping aside from Dokku and he left the new maintainers a roadmap for future development which the maintainers have been following.
Recent Dokku features include:
- zero-downtime deployment, with Dokku waiting a configurable amount of time before routing traffic to from your old container to your newly-deployed container
- an increasing range of plugins
- the ablity to specify deploy-time checks (such as text appearing at a given URL) to ensure that
the deploy worked correctly - the ability to rename a deployed app
Amazon EC2 instance setup
We presume that you've already setup an AWS account, and have retrieved your AWS_ACCESS_KEY_ID
and your AWS_SECRET_ACCESS_KEY
.
We need to create our EC2 keypair as aws
, and save the aws.pem
to ~/.ssh/aws.pem
.
Then, we create our EC2 Security Group as production
, adding the rules to allow incoming traffic from port 22 (SSH) and port 80 (HTTP).
Next, set environment variables for our AWS_ACCESS_KEY_ID
and our AWS_SECRET_ACCESS_KEY
:
export AWS_ACCESS_KEY_ID=WHATEVER
export AWS_SECRET_ACCESS_KEY=WHICHEVER
These can be set in a .bash_profile
or similar.
Create a new t2.micro
instance — in our case we'll use the region ap-southeast-2
.
Then, in the AWS dashboard, access the instance, and check the public IP address, which we'll need later to set our DNS:
52.62.179.58
Note that, depending on the apps you'll be deploying, you may need to upgrade to a larger EC2 instance.
Now, we need to determine the AMI instance to use. Use the Amazon EC2 AMI Locator
to determine the correct ami-id — scroll down to the bottom of the locator and use the dropdowns to filter the available instances.
We'll need one that has:
- region: ap-southeast-2
- version: 14.04 LTS
- instance type: hvm:ebs
Originally, when I ran the Vagrantfile
below, I received the following message:
InvalidParameterValue => Value () for parameter groupId is invalid
The problem turned out to be that I was specifying an aws.ami
id that was not valid for the specified aws.region
— using the Amazon EC2 AMI Locator allowed me to find a suitable AMI id.
Add an entry accordingly to our /etc/hosts
file — here we presume we'll be using the domain dokku.me
, as we'll be using our hosts file to specify our DNS, but you can use a real (i.e. registered) domain if you'd like to make your dokku box accessiable via the public web.
52.62.179.58 dokku.me
Vagrant setup
Install the vagrant plugin for aws:
vagrant plugin install vagrant-aws
We'll retrieve the following Vagrantfile
:
curl https://gist.githubusercontent.com/jcdarwin/e1991320f1c4f3d1db2f/raw/d5e2796a3c632ec63e8a503501ead7af5744d6c2/Vagrantfile > Vagrantfile
Our Vagrantfile
is as follows:
Vagrant::configure('2') do |config|
config.vm.define :dokku, autostart: false do |box|
box.vm.box = 'trusty'
box.vm.box_url = 'https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box'
box.vm.hostname = 'dokku.me'
box.vm.provider :aws do |aws, override|
aws.access_key_id = ENV['AWS_ACCESS_KEY_ID']
aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
aws.keypair_name = 'aws'
# As per the Amazon EC2 AMI Locator
aws.ami = 'ami-4f32142c'
aws.region = 'ap-southeast-2'
aws.instance_type = 't2.micro'
# Customize this to the id of the security group you're using
aws.security_groups = 'production'
# To mount EBS volumes
aws.block_device_mapping = [
{
:DeviceName => "/dev/sdb",
:VirtualName => "ephemeral0"
},
{
:DeviceName => "/dev/sdc",
:VirtualName => "ephemeral1"
}
]
override.ssh.username = 'ubuntu'
# Customize this to your AWS keypair path
override.ssh.private_key_path = '~/.ssh/aws.pem'
end
# To make sure we use EBS for our tmp files
box.vm.provision "shell" do |s|
s.privileged = true
s.inline = %{
mkdir -m 1777 /mnt/tmp
echo 'export TMPDIR=/mnt/tmp' > /etc/profile.d/tmpdir.sh
}
end
# To make sure packages are up to date
box.vm.provision "shell" do |s|
s.privileged = true
s.inline = %{
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get --yes --force-yes upgrade
}
end
# Install dokku as per http://dokku.viewdocs.io/dokku/getting-started/install/debian/
box.vm.provision "shell" do |s|
s.privileged = true
s.inline = %{
export DEBIAN_FRONTEND=noninteractive
echo "install prerequisites"
sudo apt-get update -qq > /dev/null
sudo apt-get install -qq -y apt-transport-https
echo "setup unattended installation"
echo "dokku dokku/vhost_enable boolean true" | sudo debconf-set-selections
echo "dokku dokku/hostname string dokku.me" | sudo debconf-set-selections
echo "install docker"
sudo apt-get install lxc wget bsdtar curl
sudo apt-get install linux-image-extra-$(uname -r)
sudo modprobe aufs
sudo usermod -aG docker ubuntu
wget -nv -O - https://get.docker.com/ | sh
echo "install dokku"
wget -nv -O - https://packagecloud.io/gpg.key | apt-key add -
echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ trusty main" | sudo tee /etc/apt/sources.list.d/dokku.list
sudo apt-get update -qq -y > /dev/null
sudo apt-get install -qq -y dokku
sudo dokku plugin:install-dependencies --core
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
}
end
end
end
Note that our box_url
is a blank Vagrant box to coax Vagrant into working with AWS — we can add our box manually:
vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
Inspect the metadata:
cat ~/.vagrant.d/boxes/dummy/0/aws/metadata.json
We should see something like:
{
"provider": "aws"
}
Create our EC2 instance using Vagrant
Start up our VM:
vagrant up dokku --provider=aws
Our Vagrantfile
will create the EC2 instance, and run the provisioning script.
Once Vagrant finishes provisioning the box, you should be able to ssh into it using Vagrant:
vagrant ssh dokku
Note that if you experience any problems with the provisioning, you may need to check that Dokku and the core plugins installed correctly:
sudo apt-get install dokku
sudo dokku plugin:install-dependencies --core
While in our box, change /home/dokku/HOSTNAME
and /home/dokku/VHOST
from the Amazon-generated name (e.g. ip-172-31-3-31.ap-southeast-2.compute.internal
) to our domain:
dokku.me
We can now exit
from our ssh session.
Complete Dokku setup
We should now be able to finish the Dokku setup using Dokku's web interface at http://dokku.me
- change our hostname from the IP address to our domain:
dokku.me
- choose
Use virtualhost naming for apps
Setup direct ssh access
We want to be able to ssh into our box without relying on Vagrant.
First, register our normal public key with our box:
cat ~/.ssh/id_rsa.pub | vagrant ssh dokku -- sudo sshcommand acl-add dokku ${USER}
Check the ssh config for our box:
vagrant ssh-config dokku
Host dokku
HostName ec2-52-62-179-58.ap-southeast-2.compute.amazonaws.com
User ubuntu
Port 22
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /Users/jasondarwin/.ssh/aws.pem
IdentitiesOnly yes
LogLevel FATAL
We can use the reported HostName
for direct ssh access to our box:
# This will print out the Dokku help menu
ssh dokku@ec2-52-62-179-58.ap-southeast-2.compute.amazonaws.com
# We can run commands over ssh
ssh dokku@ec2-52-62-179-58.ap-southeast-2.compute.amazonaws.com version
Deploy our app
We'll use the heroku node-js-sample app to prove that we can deploy an app to our dokku box:
# Clone the repo
git clone https://github.com/heroku/node-js-sample
cd node-js-sample
# Add our Dokku box as a remote:
git remote add dokku dokku@ec2-52-62-179-58.ap-southeast-2.compute.amazonaws.com:<our app name>
# Push our repo to our box and set off the deploy
git push dokku master
Add an entry to our hosts file for our app:
52.62.179.58 node-js-sample.dokku.me
We should now be able to access our app at http://node-js-sample.dokku.me
dokku config:set --no-restart node-js-sample DOKKU_LETSENCRYPT_EMAIL=<email>
dokku letsencrypt node-js-sample
More notes about application deployments can be found in the Dokku documentation.
Further
- Dokku has support for plugins which allow
functionality such as database setup and access. - Note also that web applications, like the sample nodejs app, are expected to be exposed through port 5000 unless an environment variable is specified.
- We can use Florian Heinemann's custom
buildpack to deploy a static site. - Note that we can run docker directly on our box, e.g.
vagrant ssh dokku
docker run hello-world - Check the Dokku docs for further information