Aug 21st 2018Tutorial: How to Deploy Apps and Websites with Dokku

If you're a web developer with your own side projects, one of the tricky hurdles that you will come across sooner or later is how to actually get your project online and into production. Server administration is scary and a skill of its own and platforms like Heroku are great but get expensive quickly.

What if there was a middle ground? Luckily there is, with an awesome project called Dokku! It's basically a Heroku to host yourself. Pair that up with DigitalOcean and you can host a ton of projects for just $5 per month. (Use my referral link to sign up to receive $10 credit for free)

In this article I'll walk you through everything you need to know to get your apps and sites online. Together and step-by-step we will:

  1. Create a DigitalOcean Droplet with Dokku installed
  2. Set up a Domain for Dokku
  3. Complete the Dokku Installation
  4. Deploy a Static Site with Dokku
  5. Deploy a Simple Node App to Dokku
  6. Deploy a Simple PHP App to Dokku
  7. Deploy a Node / MongoDB App to Dokku
  8. Add SSL (HTTPS) to Your Dokku Apps and Sites

It is a little bit of work to get Dokku configured initially but once you get through that, you can be creating and deploying an app within the same minute. It's worth it, so keep reading!

1. Creating a DigitalOcean Droplet with Dokku installed

First thing we need, is an actual VM (virtual machine) to host Dokku for us. Luckily we don't even need to bother with installing Dokku because DigitalOcean offers so-called one-click apps.

After you've logged into DigitalOcean, click the green button at the top right to create a new droplet.

Now follow along with the video below to configure your DigitalOcean droplet to have Dokku installed.

If you can't see the video, here is what happens:

  1. Click the "One-click apps" apps tab and select Dokku
  2. The cheapest $5 droplet will be enough for the start
  3. Choose a datacenter that is close to your and/or your target audience. I live in Germany, so I selected Frankfurt.
  4. Select the SSH key you want to use to authenticate with your droplet. If you haven't added one yet, checkout the official DigitalOcean Documentation on How to Add SSH Keys to Droplets
  5. Change the hostname of your droplet to something you will recognize. This could be the main domain of that droplet or another identifier. I chose dokku-tutorial.
  6. Click "Create" and wait for your droplet to be created and provisioned. This can take several minutes.

2. Setting up a Domain for Dokku

By default, Dokku is setup to be accessed via an IP address. Let's say you created and deployed two apps – MeTube and Twotter. (Wow, such original examples!)

MeTube and Twotter would both have two different ports assigned by Dokku so you can access them via the same IP. So maybe 159.89.21.76:3000 for MeTube and 159.89.21.76:4000 for Twotter.

That kind of sucks though because nobody (including yourself) will remember the IP of your droplet. Instead, we want to use one or more domains. That's how I setup this page by the way!

maximilianschmitt.me is one app on my Dokku droplet, while pozzle.maximilianschmitt.me is another one.

Let's get you a domain connected to your Dokku instance. For this tutorial, I have registered paulgilbertisawesome.com.

Note

As far as I know, DigitalOcean does not sell domains, so you will need to buy them from somewhere else. To enable DigitalOcean to manage your domain's DNS configuration, you need to point that domain to DigitalOcean's nameservers.

How to do this varies from domain shop to domain shop but they will for sure help you out or you can find something from a few simple Google queries:

So before you proceed with the steps below:

  1. Make sure you have purchased a domain
  2. Make sure to have it pointing to DigitalOcean's nameservers
  1. Go to the Networking section on DigitalOcean.
  2. Enter your domain (paulgilbertisawesome.com in my case) and click "Add Domain"
  3. Add an A Record with @ as the hostname and select the droplet we just created to redirect to: dokku-tutorial
  4. Add a CNAME with * as the hostname and set it up as an alias of your domain (paulgilbertisawesome.com in my case). This enables Dokku to host apps as a subdomain of your hostname. So something like my-app.paulgilbertisawesome.com

It might take a bit for your DNS change to propagate. The changes will usually be visible to you somewhere between 10 minutes and 1 hour. Depending on where you are in the world, this could also take up to 24 hours, so be patient.

To check if it works for you, run:

Terminal
$ host -a paulgilbertisawesome.com
Trying "paulgilbertisawesome.com"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35949
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;paulgilbertisawesome.com.      IN      ANY

;; ANSWER SECTION:
paulgilbertisawesome.com. 1530  IN      NS      ns1.digitalocean.com.
paulgilbertisawesome.com. 3330  IN      A       159.89.21.76paulgilbertisawesome.com. 1530  IN      NS      ns3.digitalocean.com.
paulgilbertisawesome.com. 1530  IN      NS      ns2.digitalocean.com.
paulgilbertisawesome.com. 1530  IN      SOA     ns1.digitalocean.com. hostmaster.paulgilbertisawesome.com. 1534711797 10800 3600 604800 1800

Received 172 bytes from 192.168.178.1#53 in 2 ms

If you see the A record popping up and pointing to your droplet, the DNS change is now visible to you. That means we can keep going!

3. Completing the Dokku Installation

If you've made it this far, you already did the most complicated things. It gets easier from here on out with every step. We will now complete your Dokku installation and make a few more minor tweaks.

3.1. Configuring your Domain with Dokku

  1. Visit the IP or domain of your droplet in the browser. In my case: 159.89.21.76 or paulgilbertisawesome.com
  2. You will see your SSH key was already added
  3. Enter your domain as the Hostname
  4. Hit the checkbox to use virtualhost naming for apps. This allows dokku to make your apps available as subdomains instead of as port numbers of your droplet IP.

3.2. Updating Dokku to its Latest Version

It's super convenient that DigitalOcean sets Dokku up for us, but it's not always running the latest version. It's no problem though, we can update Dokku by running just two commands (after SSH-ing in ;)):

$ ssh root@159.89.21.76
root@dokku-tutorial:~# sudo apt-get update
root@dokku-tutorial:~# sudo apt-get install -qq -y dokku herokuish sshcommand plugn

This can take a few minutes.

3.3. Making it Convenient to Interact with Dokku

Currently, you would need to SSH into your droplet as root and then run dokku to interact with Dokku:

Terminal
$ ssh root@159.89.21.76
root@dokku-tutorial:~# dokku
Usage: dokku [--quiet|--trace|--rm-container|--rm|--force] COMMAND  [command-specific-options]

Primary help options, type "dokku COMMAND:help" for more details, or dokku help --all to see all commands.

Commands:

    apps             Manage Dokku apps
    certs            Manage Dokku apps SSL (TLS) certs
    checks           Manage zero-downtime settings
    config           Pretty-print an app or global environment
    docker-options   Pass options to Docker the various stages of an app
    domains          Manage vhost domains used by the Dokku proxy
    enter            Connect to a specific app container
    events           Show the last events (-t follows)
    help             Print the list of commands
    logs             Show the last logs for an application
    ls               Pretty listing of deployed applications and containers
    nginx            Interact with Dokku's Nginx proxy
    proxy            Manage the proxy used by dokku on a per app
    ps               List processes running in app container(s)
    run              Run a command in the environment of an application
    shell            Spawn dokku shell
    ssh-keys         Manage public ssh keys that are allowed to connect to Dokku
    storage          Mount local volume / directories inside containers
    tags             List all app image tags
    tar              Deploy applications via tarball instead of git
    trace            Enable dokku tracing
    url              Show the first URL for an application (compatibility)
    urls             Show all URLs for an application
    version          Print dokku's version

Alternatively, you can run ssh dokku@159.89.21.76 <dokku-command>. This is a bit better, but who can remember that IP address?

What we're going for is that we can run ssh dokku <dokku-command>. To get this working, you can add a little entry to your SSH config:

~/.ssh/config
host dokku-tutorial
  HostName 159.89.21.76
  User dokku

(I already have a host called dokku, so I'm using dokku-tutorial instead.)

Try it out:

Terminal
$ ssh dokku-tutorial
Usage: dokku [--quiet|--trace|--rm-container|--rm|--force] COMMAND  [command-specific-options]

Primary help options, type "dokku COMMAND:help" for more details, or dokku help --all to see all commands.

Commands:

    apps             Manage Dokku apps
    certs            Manage Dokku apps SSL (TLS) certs
    checks           Manage zero-downtime settings
    config           Pretty-print an app or global environment
    docker-options   Pass options to Docker the various stages of an app
    domains          Manage vhost domains used by the Dokku proxy
    enter            Connect to a specific app container
    events           Show the last events (-t follows)
    help             Print the list of commands
    logs             Show the last logs for an application
    ls               Pretty listing of deployed applications and containers
    nginx            Interact with Dokku's Nginx proxy
    proxy            Manage the proxy used by dokku on a per app
    ps               List processes running in app container(s)
    run              Run a command in the environment of an application
    shell            Spawn dokku shell
    ssh-keys         Manage public ssh keys that are allowed to connect to Dokku
    storage          Mount local volume / directories inside containers
    tags             List all app image tags
    tar              Deploy applications via tarball instead of git
    trace            Enable dokku tracing
    url              Show the first URL for an application (compatibility)
    urls             Show all URLs for an application
    version          Print dokku's version
Connection to 159.89.21.76 closed.

Awesome! With that, all our prep-work is done and we can move on to creating and deploying our first site to Dokku!

4. Deploying a Static Site with Dokku

For this tutorial I chose to register the domain paulgilbertisawesome.com which we will use to build sites and apps to honor the amazing guitarist that is Paul Gilbert.

Terminal
$ mkdir paulgilbertisawesome.com
$ cd paulgilbertisawesome.com

We'll create a simple HTML page that embeds a cool video where Paul Gilbert shows off his Airplane Flanger:

src/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Paul Gilbert is AWESOME</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <iframe
    width="560"
    height="315"
    src="https://www.youtube.com/embed/bkUfPUKilWI"
    frameborder="0"
    allow="autoplay; encrypted-media"
    allowfullscreen
  ></iframe>
</body>

</html>

Most static sites these days need some sort of build step to compile JavaScript or CSS. We don't really need that because Paul Gilbert doesn't need a fancy page to be amazing, but we'll simulate this step anyways:

package.json
{
  "scripts": {
    "build": "rm -rf dist; mkdir dist; cp src/index.html dist/"
  }
}

So our build script simply copies our HTML file over to a dist directory. This dist directory is what we want to deploy to Dokku.

Let's get that working!

Dokku deployments happen via git. Let's get that setup:

Terminal
$ git init
Initialized empty Git repository in /Users/max/code/tutorials/paulgilbertisawesome.com/.git/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) b7bfaca] initial commit
 3 files changed, 49 insertions(+)
 create mode 100644 dist/index.html
 create mode 100644 package.json
 create mode 100644 src/index.html
$ git remote add dokku dokku-tutorial:paulgilbertisawesome.com

To explain that last line:

git remote add dokku dokku-tutorial:paulgilbertisawesome.com

We're adding a git remote called "dokku" (another common remote is "origin")

git remote add dokku dokku-tutorial:paulgilbertisawesome.com

The address of the remote is dokku-tutorial:paulgilbertisawesome.com. dokku-tutorial is substituted to dokku@159.89.21.76 thanks to our little entry in ~/.ssh/config and paulgilbertisawesome.com is the name of the Dokku app that should be created and deployed to.

Ok, we initialized a new git repository, created a commit and added our Dokku droplet as a remote. Now we need some way of telling Dokku that this repository is a static site.

What we need to do, is tell Dokku which buildpack to use. We will choose the official NGINX buildpack by Dokku, which (as per the README of dokku/buildpack-nginx) also needs us to create an empty file called .static:

Terminal
$ touch .static
$ echo BUILDPACK_URL=https://github.com/dokku/buildpack-nginx > .env
$ git add .
$ git commit -m "add .static and .env with BUILDPACK_URL"
Note

Usually Dokku would detect a static site by the .static file alone. However, since our repository contains a package.json, Dokku would think that it's a node app, so it would try to run npm start (which would fail) and abort the deployment. That's why we need to explicitly set the BUILDPACK_URL.

Now, let's push our code to Dokku to get this thing deployed:

Terminal
$ git push dokku master
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (11/11), 1.21 KiB | 1.21 MiB/s, done.
Total 11 (delta 2), reused 0 (delta 0)
-----> Cleaning up...
-----> Building paulgilbertisawesome.com from herokuish...
-----> Adding BUILD_ENV to build environment...
-----> Fetching custom buildpack
-----> .static app detected
-----> Copy static files to www
-----> Download and unzip nginx
-----> Download and unzip pcre
-----> Download and unzip zlib
-----> Download and unzip sigil
-----> Compiling static nginx binary
       ar: `u' modifier ignored since `D' is the default (see `U')
       objs/src/core/nginx.o: In function `ngx_load_module':
       /cache/nginx-1.10.1/src/core/nginx.c:1475: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
       objs/src/os/unix/ngx_process_cycle.o: In function `ngx_worker_process_init':
       /cache/nginx-1.10.1/src/os/unix/ngx_process_cycle.c:838: warning: Using 'initgroups' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
       objs/src/core/nginx.o: In function `ngx_core_module_init_conf':
       /cache/nginx-1.10.1/src/core/nginx.c:1070: warning: Using 'getgrnam' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
       /cache/nginx-1.10.1/src/core/nginx.c:1059: warning: Using 'getpwnam' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
       objs/src/core/ngx_inet.o: In function `ngx_inet_resolve_host':
       /cache/nginx-1.10.1/src/core/ngx_inet.c:1120: warning: Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
-----> Using default app-nginx.conf.sigil
-----> Using default mime.types
-----> Discovering process types
       Default types for  -> web
-----> Releasing paulgilbertisawesome.com (dokku/paulgilbertisawesome.com:latest)...
-----> Deploying paulgilbertisawesome.com (dokku/paulgilbertisawesome.com:latest)...
-----> Attempting to run scripts.dokku.predeploy from app.json (if defined)
-----> App Procfile file found (/home/dokku/paulgilbertisawesome.com/DOKKU_PROCFILE)
-----> DOKKU_SCALE file found (/home/dokku/paulgilbertisawesome.com/DOKKU_SCALE)
=====> web=1
-----> Attempting pre-flight checks
       For more efficient zero downtime deployments, create a file CHECKS.
       See http://dokku.viewdocs.io/dokku/deployment/zero-downtime-deploys/ for examples
       CHECKS file not found in container: Running simple container check...
-----> Waiting for 10 seconds ...
-----> Default container check successful!
-----> Running post-deploy
-----> Creating new /home/dokku/paulgilbertisawesome.com/VHOST...
-----> Setting config vars
       DOKKU_PROXY_PORT:  80
-----> Setting config vars
       DOKKU_PROXY_PORT_MAP:  http:80:5000
-----> Configuring paulgilbertisawesome.com...(using built-in template)
-----> Creating http nginx.conf
-----> Running nginx-pre-reload
       Reloading nginx
-----> Setting config vars
       DOKKU_APP_RESTORE:  1
=====> Renaming container (fc63646eb581) dreamy_pare to paulgilbertisawesome.com.web.1
-----> Attempting to run scripts.dokku.postdeploy from app.json (if defined)
=====> Application deployed:
       http://paulgilbertisawesome.com

To dokku-tutorial:paulgilbertisawesome.com
 * [new branch]      master -> master

That looked good! However, if we go to paulgilbertisawesome.com, we will get a 403 Forbidden from NGINX. This is because with the buildpack we chose, NGINX will attempt to serve the root of the repository (which doesn't contain an index.html file - just a package.json, .static, dist/ and src/).

We need to tell Dokku that it should serve from the dist folder. Like this:

Terminal
$ ssh dokku-tutorial config:set paulgilbertisawesome.com NGINX_ROOT=dist

Dokku will give you quite a bit of output and essentially rebuild your app. Now if you go paulgilbertisawesome.com you will get to witness an awesome video:

Let's quickly summarize how we got a static app deployed to Dokku:

  1. Create the app (wow, really?)
  2. Make sure to have a .env file with BUILDPACK_URL=https://github.com/dokku/buildpack-nginx
  3. Make sure to have an empty .static file at the root of your project
  4. Add <dokku-hostname>:<dokku-app-name> as a git remote
  5. git push origin dokku master
  6. Tell NGINX/Dokku which directory to serve: ssh dokku-tutorial config:set <app-name> NGINX_ROOT=<site-dir>

Now if you want to update your site at a later point, it's as easy as making the changes, committing them and pushing them to Dokku.

5. Deploying a Simple Node App to Dokku

Hosting static sites with Dokku works well, but what about a real application?

Traditionally, hosting node apps hasn't always been the easiest thing. If you set up a VM yourself to do this, you have to find solutions to quite a few questions that will inevitably pop up:

  1. How do I restart my node process automatically if it crashes?
  2. How do I restart my node process if my server restarts?
  3. How do I deploy my app?
  4. As which user should my app run?
  5. How do I configure environment variables for my app?
  6. How do I deploy my app without any downtime?
  7. How do I periodically backup my database automatically?

Dokku has a solution to all these problems, and it's as easy as a git push.

What will we build?

praise.paulgilbertisawesome.com - a node app that outputs a random praise for Paul Gilbert when accessed.

Let's start by creating a new folder and getting git setup:

Terminal
$ mkdir praise.paulgilbertisawesome.com
$ cd praise.paulgilbertisawesome.com
$ git init
$ git remote add dokku dokku-tutorial:praise

Note how we are adding our remote. It's our Dokku's hostname (dokku-tutorial followed by the name of the app (praise). Once our app is deployed, it will be available at praise.paulgilbertisawesome.com.

The actual app code is very simple and consists of just a single file:

src/main.js
const http = require('http')

const PORT = process.env.PORT || 3000
const PRAISES = [
  'Paul Gilbert is awesome',
  'Paul Gilbert is sexy',
  'Paul Gilbert is amazing',
  'Paul Gilbert is handsome',
  'Paul Gilbert is godlike',
  'Paul Gilbert is pretty',
  'Paul Gilbert is cute',
  'Paul Gilbert is funny',
  'Paul Gilbert is kind',
  'Paul Gilbert is gentle',
  'Paul Gilbert is ROCK AND ROLL',
  'Paul Gilbert is badass'
]

http
  .createServer(function (req, res) {
    if (req.url !== '/') {
      res.statusCode = 404
      res.end()

      return 
    }

    const randomPraise = PRAISES[Math.floor(Math.random() * PRAISES.length)]

    res.end(randomPraise)
  })
  .listen(PORT, (err) => {
    if (err) {
      throw err
    }

    console.log(`server is listening on ${PORT}`)
  })

It's important to listen on a port provided by the PORT environment variable. This is what Dokku uses to tell us which port to listen on. In the code above we fallback to a default port (3000) for convenience during development.

The only other thing we need to do (besides listening on PORT) is to add a start script to our package.json:

package.json
{
  "scripts": {
    "start": "node src/main.js"
  }
}

Now, let's deploy this!

Terminal
$ git add .
$ git commit -m "paul gilbert is in many ways awesome"
$ git push dokku master

And that's it! The app is deployed:

That really was easier than deploying that static site, ey? No .static file. No BUILDPACK_URL. No NGINX_ROOT. Nice!

6. Deploying a Simple PHP App to Dokku

OK, node apps are easy with Dokku. What about apps written in a different programming language? Like PHP?

Let's try it!

Terminal
$ mkdir php-praise.paulgilbertisawesome.com
$ cd php-praise.paulgilbertisawesome.com
$ git init
$ git remote add dokku dokku-tutorial:php-praise
index.php
<?php

$PRAISES = [
  'Paul Gilbert is awesome',
  'Paul Gilbert is sexy',
  'Paul Gilbert is amazing',
  'Paul Gilbert is handsome',
  'Paul Gilbert is godlike',
  'Paul Gilbert is pretty',
  'Paul Gilbert is cute',
  'Paul Gilbert is funny',
  'Paul Gilbert is kind',
  'Paul Gilbert is gentle',
  'Paul Gilbert is ROCK AND ROLL',
  'Paul Gilbert is badass'
];

echo $PRAISES[rand(0, sizeof($PRAISES) - 1)];

(The ease of PHP is pretty awesome, you gotta admit!)

Terminal
$ git add .
$ git commit -m "praising paul gilbert with php"
$ git push dokku master

Man, that was easy!

Note

Notice how the font is a bit different than the node app? Weird, because we're just sending down text, right?

If you have a look at the dev tools, It looks like PHP sends a Content-Type: text/html; charset=UTF-8 header while node does not do this by default. That is why Chrome renders the output from our node app in a monospaced font, while it's applying some default website styling to our PHP app.

It almost feels like Dokku is getting easier the more we use it! Now let's look at some more things you will want to know for most web apps. First: How can we deploy an app that uses a database?

7. Deploying a Node / MongoDB App to Dokku

Simple stateless apps are awesome but in most cases you will need some sort of database for your project. We will use MongoDB in this example to keep things simple but obviously Dokku supports many other databases as well.

If you want to run a database inside Dokku, you can always do a Google for "dokku <name-of-database>" and you can be sure to find a plugin!

9.1. Installing the Official Dokku Mongo Plugin

You can find the official Dokku mongo plugin on GitHub at dokku/dokku-mongo. Let's follow the installation steps. Note that you have to be root to install plugins:

Terminal
$ ssh root@dokku-tutorial
root@dokku-tutorial:~# dokku plugin:install https://github.com/dokku/dokku-mongo.git mongo

Awesome how easy it is to install plugins. Creating the database and making it available to our app is just as easy.

9.2. Creating a MongoDB Database with Dokku

To create a database and expose it to our app, we need to create a so-called mongo service for our app. What should we call it? Well, what kind of app will we build?

We will build witnesses.paulgilbertisawesome.com which counts the number of people who have witnessed Paul Gilbert's awesomeness on our site. So let's create a MongoDB service called witnesses:

Terminal
$ ssh dokku-tutorial mongo:create witnesses
       Waiting for container to be ready
=====> MongoDB container created: witnesses
=====> Container Information
       Config dir:          /var/lib/dokku/services/mongo/witnesses/config
       Data dir:            /var/lib/dokku/services/mongo/witnesses/data
       Dsn:                 mongodb://witnesses:4ad9ceae708444c62403cde5323f6d63@dokku-mongo-witnesses:27017/witnesses
       Exposed ports:       -
       Id:                  5aa687ce2a62e537f934e5e183bb1762f7fe4e94012b184216824fd61b3e60df
       Internal ip:         172.17.0.5
       Links:               -
       Service root:        /var/lib/dokku/services/mongo/witnesses
       Status:              running
       Version:             mongo:3.4.9

Done! Now let's create our witnesses app on Dokku directly (before writing any code) and link our database to it. Linking our witnesses database to our witnesses app will enable the witnesses app (and only that app) to connect to the witnesses database.

But first, create the app:

Terminal
$ ssh dokku-tutorial apps:create witnesses
-----> Creating witnesses... done

To link our database to the app, we can run mongo:link <mongo-service> <app>:

Terminal
$ ssh dokku-tutorial mongo:link witnesses witnesses
-----> Setting config vars
       MONGO_URL:  mongodb://witnesses:4ad9ceae708444c62403cde5323f6d63@dokku-mongo-witnesses:27017/witnesses
-----> Restarting app witnesses
 !     App witnesses has not been deployed

As you can see, by linking the database and the app, Dokku now exposes an environment variable MONGO_URL to our app that we can use to connect to the database. With all that setup, we can start coding:

9.3. Writing the App

We'll begin again by creating a new folder:

Terminal
$ mkdir witnesses.paulgilbertisawesome.com
$ cd witnesses.paulgilbertisawesome.com
$ git init
$ git remote add dokku dokku-tutorial:witnesses

We'll also add a package to connect to MongoDB:

Terminal
$ yarn add mongodb

Now we're ready to code. When reading the code, make sure to take note that we are now dependent on two environment variables: PORT and MONGO_URL:

src/main.js
const http = require('http')
const { MongoClient } = require('mongodb')

const PORT = process.env.PORT || 3000
const MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/witnesses'

main()

async function main() {
  const mongoClient = await MongoClient.connect(
    MONGO_URL,
    { useNewUrlParser: true }
  )

  const db = mongoClient.db()

  http
    .createServer(async function(req, res) {
      if (req.url !== '/') {
        res.statusCode = 404
        res.end()

        return 
      }

      await db.collection('witnesses').insertOne({ witnessedAt: new Date() })

      const numWitnesses = await db.collection('witnesses').count({})

      res.end(`${numWitnesses} witnesses of Paul Gilbert's greatness`)
    })
    .listen(PORT, err => {
      if (err) {
        throw err
      }

      console.log(`server is listening on ${PORT}`)
    })
}

What we have is a simple node app that connects to a mongo database declared in the MONGO_URL environment variable and starts a web server on the declared PORT. When accessing the site (at /) we insert a document into our database. We then read the number of documents in the database and send them as a response.

Before we can deploy this, we (because this is a node app) need a package.json with a start script:

package.json
{
  "scripts": {    "start": "node src/main.js"  },  "dependencies": {
    "mongodb": "^3.1.3"
  }
}

And because this app has dependencies, we'll add a .gitignore to exclude node_modules from our repository:

Terminal
$ echo node_modules > .gitignore

Now let's deploy!

9.4. Deploying the App

Terminal
$ git add .
$ git commit -m 'initial commit'
$ git push dokku master

I've never had such an easy time deploying a database-driven app as I have had with Dokku. That was it!

You know what's cool? Most database plugins for Dokku support automated scheduled backups to Amazon S3!

8. Adding SSL (HTTPS) to Your Dokku Apps and Sites

Nowadays your site MUST support HTTPS. A few years ago this was still a challenge for many developers. It cost money and was pretty complicated to set up. You also had to renew your certificates every year or so and when that time came, you had already forgotten how to do everything again. It was a real pain!

But thanks to Dokku and Let's Encrypt, adding HTTPS support to your site is now the easiest thing in the world.

We will add SSL support to all our sites. I lost count a little, so here is a quick list:

Ok, first we need to install the Let's Encrypt plugin:

Terminal
$ ssh root@dokku-tutorial
$ dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

To add HTTPS to any app, we simple run two commands:

  1. dokku config:set --no-restart <app-name> DOKKU_LETSENCRYPT_EMAIL=<your-email>
  2. dokku letsencrypt <app-name>

Let's do it:

Terminal
$ ssh dokku-tutorial config:set --no-restart paulgilbertisawesome.com DOKKU_LETSENCRYPT_EMAIL=maximilian.schmitt@googlemail.com
$ ssh dokku-tutorial letsencrypt paulgilbertisawesome.com

$ ssh dokku-tutorial config:set --no-restart php-praise DOKKU_LETSENCRYPT_EMAIL=maximilian.schmitt@googlemail.com
$ ssh dokku-tutorial letsencrypt php-praise

$ ssh dokku-tutorial config:set --no-restart praise DOKKU_LETSENCRYPT_EMAIL=maximilian.schmitt@googlemail.com
$ ssh dokku-tutorial letsencrypt praise

$ ssh dokku-tutorial config:set --no-restart witnesses DOKKU_LETSENCRYPT_EMAIL=maximilian.schmitt@googlemail.com
$ ssh dokku-tutorial letsencrypt witnesses

Done! :)

We'll add one more thing. Let's get Dokku to automatically renew our certificates for us:

Terminal
$ ssh dokku-tutorial letsencrypt:cron-job --add

That's it! Now enjoy your apps running on production with SSL. :)

9. The End

Wow, that was it. This was a pretty short article but I think we covered 99% of what you would want to do with Dokku. You learned how to set it up from scratch basically. We deployed a few apps together. We were able to use a database. We got SSL working.

One of the most awesome experiences I had with Dokku, was when I recently upgraded my installation that was over 3 years (!) old. I ran two commands and everything worked flawlessly. No hickups. No compatability issues. It just worked. That kind of an upgrade experience is pretty rare and says a lot about the quality of Dokku in my opinion.

Shout-out to the devs of Dokku for making such an awesome project. You can find out more about Dokku on their GitHub: dokku/dokku

I hope you were able to learn something from this little tutorial. If you get your project deployed, let me know! Send me a tweet to @Mackseraner – I would love to check it out!

Happy coding and deploying!