A guide to deploying Any App(s) to your own "Platform-as-a-Service" using Dokku.
Like having your own (_self-managed/hosted_) Heroku platform with full VM access/control at a _fraction_ of the cost.
We need a way of deploying multiple apps to the same Digital Ocean VPS/instance.
Digital Ocean is a great alternative to the "main" cloud providers (Amazon, Google & Microsoft); it has a much more intuitive (UX-focussed) "control panel" which means you can get your "DevOps" learning and work done a lot faster!
Managing your own infrastructure (or "Platform") is a "rabbit hole";
it might be easy to get started, but if (when) things go "wrong",
it can take a while to understand the issue (lots of googling!).
This is "OK" if you are the type of person who enjoys debugging Docker/Linux,
but if you prefer focus on the features of your App, let someone else
handle the infra/PaaS until you achieve "critical mass" and can afford
to hire a professional DevOps person.
Deploy "unlimited"1 apps to a Virtual Private Server (VPS) instance with great service quality and minimal cost.
In this guide we will be using the following:
- Digital Ocean Droplet (Virtual Private Server "VPS")
- CentOS (Operating System) - though any "mainstream linux" will work, and Ubuntu/Debian is the most popular.
- Dokku "Platform as a Service" ("PaaS") based on Docker.
- LetsEncrypt Free SSL Certificates.
If you do not already have a Digital Ocean account, please use the following link to register: https://m.do.co/c/29379863a4f8 and get $10 in Credit.
Note: we are launching a Digital Ocean instance in this tutorial. unless you used the "referral link" above, you will incur a small cost. If you use the 1GB instance and go through the tutorial in 1 hour it will cost you 0.007 cents (less than one cent). If you chose to use this method for running your apps it will cost you $5/month which is cheaper than the cheapest paid tier on Heroku, and since we will cover how to run multiple apps, it will cost you less than $1 per month (per app) if you run 5+ apps. Crucially, the service quality/speed will be much better than the "free apps" on Heroku!
1"unlimited" apps is not strictly true; we are limited by the RAM resources of the instance.
Anyone that needs to deploy one or more App(s) to DigitalOcean and needs a step-by-step guide.
It's only worth investing the time to create your own "Mini Platform-as-a-Service" once you have used (and "out-grown") Heroku.
Heroku offers significant advantages including logging/alerting and "teams", which are not covered here.
If you have never used Heroku, or this is your first time
deploying a Node.js app, we highly recommend following the
https://github.com/dwyl/learn-heroku
guide first.
Bookmark (Star) this repo/tutorial now so you can return to it when you are spending more than $10/month on Heroku; it does not make sense to run your own "PaaS" before then!
These are step-by-step instructions, follow them in order and don't skip steps!
Before we start, please ensure you have the following:
- Digital Ocean Account
(New to "DO"? Please use this link: https://m.do.co/c/29379863a4f8
to register)
- Public SSH Key uploaded to https://cloud.digitalocean.com/settings/security (so that you can login to the instance we are about to launch!) see: https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-digitalocean-droplets
- Basic Node.js knowledge (but this guide works for Any app e.g: Ruby, Python, Elixir, Go, Java, Scala, etc!)
- 30 mins of time.
Go to: https://cloud.digitalocean.com/droplets/new and Create a Droplet!
Note: We are using a "blank" instance as opposed to a "One-click app", because this will show us how to setup "from scratch" and will thus be applicable to any cloud provider.
The instance we are creating is a CentOS 7.5 Droplet with 1GB RAM.
Select your desired region (datacenter); (pick the nearest to your users or dev team) e.g:
The default hostname
for the instance (based on the selected options)
is: centos-s-1vcpu-1gb-lon1-01
let's change that to: centos-dokku-paas
so that we know what the instance does from reading it's hostname.
This also means that if/when we "scale" the instance up we don't need to rename it when the CPU/RAM changes.
Click on the "Create" button.
You will see a "loading" progress bar for a few seconds while the instance is being created:
and then something like this:
There are two ways to access your Digital Ocean instance: the first is via the Web-based console:
We only tend to use the web-based console when on a device that does not have a "native" terminal app. (e.g: an iPad)
get the instance's IP (v4) Address, e.g: 138.68.163.126
and run the following command into your terminal
to login to the instance via SSH:
ssh root@138.68.163.126
While logged in as root
run the update command:
yum update
root
is the default
user for Digital Ocean instances,
we prefer to minimise the activity of "root" or sudo
users
on our instances for security.
So our next step we will create a new user called: dokku
with reduced privileges.
Create a dokku
user on the server (so that we can avoid running as root
)""
cat ~/.ssh/id_rsa.pub | ssh root@<ip4> "sudo sshcommand acl-add dokku root"
e.g:
cat ~/.ssh/id_rsa.pub | ssh root@138.68.163.126 "sudo sshcommand acl-add dokku root"
If you are already logged into the server, run the following command:
cat ~/.ssh/id_rsa.pub | sudo sshcommand acl-add dokku root
If you ever need to remove the dokku
user on the instance, run:
sshcommand acl-remove <USER> <NAME>
e.g:
sshcommand acl-remove dokku root
In this section we'll walk through:
- Finding and registering a domain name
- Point the domain's DNS record to DigitalOcean so the "droplet" is configured to receive all traffic for all subdomains.
Note: If you already have a domain name you can use for this, then skip the registration step.
You will still need to "point" the domain's DNS to DigitalOcean for the rest to work.
We registered a custom domain name as we intend to use
this server to host multiple "demo" apps,
ademo.app
seemed like a logical name for the domain.
We use https://domainr.com to lookup if the domain is available:
And then use https://iwantmyname.com to register the domain.
In the settings (where you registered the domain), point DNS servers at the DigitalOcean's name servers:
- ns1.digitalocean.com
- ns2.digitalocean.com
- ns3.digitalocean.com
This indicates the settings update is in progress ...
Verify that the new domain servers are listed by running the whois
command:
$ whois <domain.com> | grep "Name Server"
e.g:
whois ademo.app | grep "Name Server"
You should see something like this:
https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-dns
Now that we know the domain (DNS) is configured to "point" to DigitalOcean we can move on to creating the SSL Certificate for the domain!
In order to have multiple subdomains on the same server,
e.g: hello.ademo.app
and awesome-word-game.ademo.app
you will need to have a Wildcard SSL Certificate!
Thankfully you can get one for free with about 10 mins work. We wrote a separate (self-contained) tutorial for that:
letsencrypt-wildcard-certificate.md
once you have finished setting it up, return here and continue.
Given that there is no "package" for CentOS we need to
install dokku
manually using the "advanced" instructions:
http://dokku.viewdocs.io/dokku/getting-started/advanced-installation
Run the following commands on your DO instance
to install Extra Packages for Enterprise Linux ("EPEL")
https://fedoraproject.org/wiki/EPEL to get nginx
:
sudo yum install -y epel-release
Install Docker
curl -fsSL https://get.docker.com/ | sudo sh
Once Docker is installed, proceed to installing Dokku using the following script:
curl -s https://packagecloud.io/install/repositories/dokku/dokku/script.rpm.sh | sudo bash
sudo yum install -y herokuish dokku
sudo dokku plugin:install-dependencies --core
That will install quite a few packages, so go for a walk!
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
You should see the following output:
-----> Cloning plugin repo https://github.com/dokku/dokku-letsencrypt.git to /var/lib/dokku/plugins/available/letsencrypt
Cloning into 'letsencrypt'...
remote: Counting objects: 454, done.
remote: Total 454 (delta 0), reused 0 (delta 0), pack-reused 454
Receiving objects: 100% (454/454), 94.57 KiB | 0 bytes/s, done.
Resolving deltas: 100% (276/276), done.
-----> Plugin letsencrypt enabled
Removed symlink /etc/systemd/system/docker.service.wants/dokku-redeploy.service.
Created symlink from /etc/systemd/system/docker.service.wants/dokku-redeploy.service to /etc/systemd/system/dokku-redeploy.service.
-----> Migrating zero downtime env variables to 0.5.x. The following variables have been deprecated
=====> DOKKU_SKIP_ALL_CHECKS DOKKU_SKIP_DEFAULT_CHECKS
=====> Please use dokku checks:[disable|enable] <app> to control zero downtime functionality
=====> Migration complete
=====>
-----> Migrating zero downtime env variables to 0.6.x. The following variables will be migrated
=====> DOKKU_CHECKS_ENABLED -> DOKKU_CHECKS_SKIPPED
=====> Migration complete
=====>
Adding user dokku to group adm
-----> Migrating DOKKU_NGINX env variables. The following variables will be migrated
=====> DOKKU_NGINX_PORT -> DOKKU_PROXY_PORT
=====> DOKKU_NGINX_SSL_PORT -> DOKKU_PROXY_SSL_PORT
=====> Migration complete
-----> Priming bash-completion cache
Add the desired domain to dokku so that it knows we want to deploy our app(s) to that.
dokku domains:add-global <domain.com>
e.g:
dokku domains:add-global ademo.app
via: http://dokku.viewdocs.io/dokku/configuration/domains/#customizing-hostnames
Run the following commands (on the instance) to add your ssh (public) key
to the dokku
user:
sudo cp /root/.ssh/authorized_keys /home/dokku/.ssh/dokku.pub
sudo chown dokku:dokku /home/dokku/.ssh/dokku.pub
sudo dokku ssh-keys:add dokku /home/dokku/.ssh/dokku.pub
vi /home/dokku/VHOST
paste the following:
A *.ademo.app 138.68.163.126
Find the file:
find / -name nginx.conf.sigil
On CentOS the file is located at:
/var/lib/dokku/core-plugins/available/nginx-vhosts/templates/nginx.conf.sigil
For reference, this is the git diff
("before and after") the change:
https://github.com/dwyl/learn-devops/compare/82919299ceaa2d0eb308c7501b8aa95b6be2b848...2da1a06365cce145d98e293a263a8ae747fb2f01
For details on nginx configuration in Dokku, see: https://github.com/dokku/dokku/blob/master/docs/configuration/nginx.md
Run this command while logged in (via SSH) to the DO instance:
dokku apps:create hello
You should see:
-----> Creating hello... done
dokku certs:add yourapp < /etc/letsencrypt/live/ademo.app/certs.tar
e.g:
dokku certs:add hello < /etc/letsencrypt/live/ademo.app/certs.tar
using https://github.com/nelsonic/hello-world-node-http-server as my "hello world" app.
git remote add dokku dokku@138.68.163.126:hello
Now push the app to the Dokku server:
git push dokku master
You should see output similar to the following:
In our case we deployed our hello
app to: https://hello.ademo.app
Create a new
Dokku app (on the instance):
dokku apps:create hello-world-node
Output (you should see):
-----> Creating hello-world-node... done
git remote add dokku dokku@138.68.163.126:hello-world-node
Now push the app to the Dokku server:
git push dokku master
In my case I am working on a branch so I did:
git push dokku dokku-paas-deployment-issue#24:master
This pushes the branch but tells Dokku to treat it as master
So it will be deployed.
The "new" app hello-world-node
was successfully deployed to:
https://hello-world-node.ademo.app
You have successfully deployed your first App to A Dokku PaaS!
Dokku uses nginx as its server for routing requests to specific applications.
If you want to start serving your own pages or application through Nginx, you will want to know the locations of the Nginx configuration files and default server root directory.
The default server root directory is /usr/share/nginx/html
.
Files that are placed in there will be served on your web server.
This location is specified in the default server block
configuration file that ships with Nginx,
which is located at /etc/nginx/nginx.conf
Any additional server blocks, known as Virtual Hosts in Apache,
can be added by creating new configuration files in
/etc/nginx/conf.d
Files that end with .conf
in that directory will be loaded when Nginx is started.
The main Nginx configuration file is located at /etc/nginx/nginx.conf. This is where you can change settings like the user that runs the Nginx daemon processes, and the number of worker processes that get spawned when Nginx is running, among other things.
By default, access and error logs are written for each app to /var/log/nginx/${APP}-access.log and /var/log/nginx/${APP}-error.log respectively
/home/dokku/*/nginx.conf
e.g:
/home/dokku/hello/nginx.conf
nginx -t -c /etc/nginx/conf.d/dokku.conf
nginx -t
via: http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/
nginx -s reload
via: https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-centos-7
To check if nginx
is running on the CentOS instance run:
ps waux | grep nginx
e.g:
root 14575 0.0 0.0 112704 968 pts/0 S+ 20:39 0:00 grep --color=auto nginx
root 19119 0.0 0.2 141272 2100 ? Ss 18:31 0:00 nginx: master process nginx
nginx 19120 0.0 0.3 141660 3572 ? S 18:31 0:00 nginx: worker process
kill $(ps aux | grep '[n]ginx' | awk '{print $2}')
or the cleaner version:
pkill nginx
dokku apps:list
Sample response:
=====> My Apps
hello
via: https://github.com/dokku/dokku/blob/master/docs/deployment/process-management.md
dokku proxy:report hello
Sample output:
[root@centos-dokku-paas hello]# dokku proxy:report hello
=====> hello proxy information
Proxy enabled: true
Proxy type: nginx
Proxy port map: http:80:5000
via: http://dokku.viewdocs.io/dokku/networking/proxy-management/
dokku ps:restart <app>
e.g:
dokku ps:restart hello
To delete or "destro" an app run:
dokku apps:destroy <app>
e.g:
dokku apps:destroy hello
via: dokku/dokku#36
docker -D info
- https://github.com/dokku/dokku
- https://github.com/gliderlabs/herokuish
- https://www.upcloud.com/support/get-started-dokku-centos
- Nginx beginners guide: https://nginx.org/en/docs/beginners_guide.html
- How to install nginx on CentOS: https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-centos-7
- https://stackoverflow.com/questions/14434120/nginx-set-multiple-server-name-with-ssl-support
- See git commit hash of running Dokku app? https://stackoverflow.com/questions/29801570/see-git-commit-hash-of-running-dokku-app
- How To Set Up Nginx Server Blocks (Virtual Hosts)
https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04 - Nginx Reverse Proxy (good docs): https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy
- How to set up TravisCI for projects that push back to github https://gist.github.com/willprice/e07efd73fb7f13f917ea
- Deploy With Travis CI and Git https://jorin.me/deploy-with-travis-and-git/ (v. "high level"...)
- Deploying from Travis CI to dokku: http://blog.abarbanell.de/linux/2017/09/09/deploy-from-travis-to-dokku
- push a specific commit to a remote: https://stackoverflow.com/questions/3230074/how-can-i-push-a-specific-commit-to-a-remote-and-not-previous-commits
This tutorial stands on the shoulders of several giants.
The particular "guide" we found the most useful
was written by Gleb Bahmutov @bahmutov
https://glebbahmutov.com/blog/running-multiple-applications-in-dokku
PDF snapshot: running-multiple-applications-in-dokku.pdf
His post is 2 years old, uses a much older version Dokku,
and is focussed on Ubuntu, so we had to fill-in quite a few "gaps".
But on the whole, it's a superb post!
To 138.68.163.126:hello-dokku
! [remote rejected] dokku-paas-deployment-issue#24 -> master (pre-receive hook declined)
error: failed to push some refs to 'dokku@138.68.163.126:hello-dokku'
I ended up having to "kill" the nginx
server before
pushing the app to the server.
By default Dokku sets the App's TCP Port to 5000
.
Unless you have a very good reason to change it, leave it as the default.
If you see the following error message:
-----> Configuring ademo.app...(using built-in template)
-----> Configuring hello-dokku....(using built-in template)
-----> Configuring hello-dokku.ademo.app...(using built-in template)
-----> Creating https nginx.conf
-----> Running nginx-pre-reload
Reloading nginx
Job for nginx.service invalid.
-----> Configuring ademo.app...(using built-in template)
-----> Configuring hello-dokku....(using built-in template)
-----> Configuring hello-dokku.ademo.app...(using built-in template)
-----> Creating https nginx.conf
-----> Running nginx-pre-reload
Reloading nginx
Job for nginx.service invalid.
It's because you are attempting to override the PORT
environment variable.
(we learned this he hard way ... don't make the same mistake!)