Setup a Vendure E-Commerce API on a Dokku Droplet
Tags: vendure, headless, graphql, node
Published: 2020-06-28 12:10:44

---
title: Setup a Vendure E-Commerce API on a Dokku Droplet
published: true
description: How to Setup a Vendure E-Commerce API on a Dokku 
tags: vendure, headless, graphql, nodejs
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/raxcrwkimneq3lbon70a.png
---

If you haven't heard of Vendure, it is a "modern, headless GraphQL-based e-commerce framework built with TypeScript & Nodejs". It's still in beta but already used in production and a major release is planned until end of this year. Check it out on [vendure.io](https://www.vendure.io/)!

This guide assumes that you have already installed a local Vendure project via [@vendure/create](https://www.vendure.io/docs/getting-started/). It will lead you through the setup of Vendure on a Dokku droplet, hosted by DigitalOcean with some tips for your production system. If you have no DigitalOcean account yet, you can use this [referral link](https://m.do.co/c/6f1dc5cbfd78) if you want, to get $100 over 60 days. 

#Create Droplet

First you can create the Dokku droplet with the one-click installer here: https://marketplace.digitalocean.com/apps/dokku

When creating the droplet, you will see three setting fields: 
- **Public Key**: For adding an SSH key to login into your server.
- **Hostname**: For setting the hostname (e.g. `example.com`). You an also just use the IP address of your droplet.
- **Use virtualhost naming for apps**: Enable this if you want the app URLs by default to be `APP_NAME.example.com` instead of `example.com:APP_PORT_NUMBER`.

Make sure your droplet has:
- **Enough disk space**: I'm currently using 9.5GB including OS, Docker containers and around 200 product images.
- **Enough memory**: Especially if you are going use the [ElasticsearchPlugin](https://www.vendure.io/docs/typescript-api/elasticsearch-plugin/) to search through products. I would recommend **at least 3GB of memory and a swapfile of 3GB** (we will create one later). This should be enough in the beginning and the swapfile can cover possible memory peaks.
- **A firewall**: To secure your droplet, make sure you restrict inbound rules to **only HTTP(S)** and also SSH if you want to login on your server via SSH. This will prevent outsiders from accessing your Elasticsearch instance on port 9200/9300. On your droplet overview click on `Secure your Droplets` and add a new firewall. Set the inbound rules to HTTPS and SSH and save. You firewall should look like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gx4gq1oly0nkc680tonw.png)

It might also make sense for you to enable backups for weekly snapshots, after the shop is up and running.

#Setup Dokku Environment

When the droplet is ready and you are able to connect with your previously added SSH key (`ssh -i SSH_KEY_NAME root@IP_OF_YOUR_DROPLET`), we can start setting up Dokku and its services. First we will create the app:

```
dokku apps:create myshopapi
```

So our API is later going to be available on `myshopapi.example.com/shop-api` and the admin area on `myshopapi.example.com/admin`. Dokku will provide the ENV variable `PORT`, which we will use later in our config file.

#Create storage folder

Then we will create a persistent storage folder that will get mounted to the `/storage` folder of the app when the application starts. It stores product assets, mail templates and test mails. On your droplet run the following:

```
# create folder and set correct ownership
mkdir -p  /var/lib/dokku/data/storage/myshopapi
chown -R dokku:dokku /var/lib/dokku/data/storage/myshopapi

# mount it to your app container to /storage
dokku storage:mount myshopapi /var/lib/dokku/data/storage/myshopapi:/app/storage
```

Then zip and upload the contents of the `/static` folder from your local computer:

```
# create zip file
cd ~/YOURLOCALPROJECTFOLDER/static
zip -r ../storage.zip . *

# upload it to your droplet
scp ~/YOURLOCALPROJECTFOLDER/storage.zip root@IP_OF_YOUR_DROPLET:/var/lib/dokku/data/storage/myshopapi
```

Back at your droplet unzip it:

```
# unzip folders
unzip /var/lib/dokku/data/storage/myshopapi/storage.zip
# remove the zip
rm /var/lib/dokku/data/storage/myshopapi/storage.zip
```

Now you should have your `assets` and `email` folders inside the `/var/lib/dokku/data/storage/myshopapi` folder.

#Install [MySQL Dokku Plugin](https://github.com/dokku/dokku-mysql)

I choose MySQL but you can also use Postgres, MariaDB or SQLite if you like. Let's call the service `myshopapi-mysql` and link it to the app:

```
sudo dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql
dokku mysql:create myshopapi-mysql
dokku mysql:link myshopapi-mysql myshopapi
```

After the installation is complete you should get some data/config directories and the ENV variable `DATABASE_URL`. The value should look like this: `mysql://mysql:YOUR_MYSQL_PASSWORT@dokku-mysql-myshopapi-mysql:3306/myshopapi_mysql`

For easier usage of the login data in our config file later, we set our own custom ENV variables:

```
dokku config:set --no-restart myshopapi MYSQL_PORT=3306
dokku config:set --no-restart myshopapi MYSQL_USER=mysql
dokku config:set --no-restart myshopapi MYSQL_PASSWORD=YOUR_MYSQL_PASSWORD
dokku config:set --no-restart myshopapi MYSQL_HOST=dokku-mysql-myshopapi-mysql
dokku config:set --no-restart myshopapi MYSQL_DB=myshopapi_mysql
```

#Install [Elasticsearch Dokku Plugin](https://github.com/dokku/dokku-elasticsearch)

First we install the plugin and create the service. Vendure should work with v7.0 or higher. I'm currently using v7.5.2. Then we increase the `max_map_count` option of the virtual machine to [prevent out of memory exceptions](https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html):

```
# install plugin
sudo dokku plugin:install https://github.com/dokku/dokku-elasticsearch.git elasticsearch
# set version you want to use
export ELASTICSEARCH_IMAGE_VERSION="7.5.2"
# create service
dokku elasticsearch:create myshopapi-elasticsearch
# expose the service to ports
dokku elasticsearch:expose myshopapi-elasticsearch 9200 9300
# link the service to your app
dokku elasticsearch:link myshopapi-elasticsearch myshopapi
# increase max_map_count 
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf; sudo sysctl -p
```

Since Dokku seems to have [an issue](https://github.com/dokku/dokku-elasticsearch/issues/72#issuecomment-510771763) connecting with Elasticsearch **v7.***, you will get an `unable to connect` error after creating the service. We also have to paste in following into the `/var/lib/dokku/services/elasticsearch/myshopapi-elasticsearch/config/elasticsearch.yml` file, to be able to connect to the instance:

```
node.name: node-1
cluster.name: docker-cluster
network.host: 0.0.0.0
cluster.initial_master_nodes:
  - node-1
```

We also get an ENV variable during this process named `ELASTICSEARCH_URL` which looks like this: `http://dokku-elasticsearch-myshopapi-elasticsearch:9200`

We will also split it in our own variables, to use it later in our config file:
```
dokku config:set --no-restart myshopapi ELASTICSEARCH_HOST=http://dokku-elasticsearch-myshopapi-elasticsearch
dokku config:set --no-restart myshopapi ELASTICSEARCH_PORT=9200
```

#Create a [Swapfile](https://linuxize.com/post/create-a-linux-swap-file/)

I still experienced memory overflows sometimes on production when Elasticsearch was busy. We can create a 3GB swapfile to help cover those like previously mentioned. You can also create a larger one, the recommendations vary. Changing it or adding another file later is possible too.

Further, we will set the `swappiness` variable to `10`, so the virtual machine is less likely going to use the swapfile instead of the memory.

```
# create 3GB swapfile
fallocate -l 3G /swapfile
# set correct permissions
chmod 600 /swapfile
# set up swap area
mkswap /swapfile
# turn swap one
swapon /swapfile
# save swap file in config to use after restart
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# check if the swap is on
swapon --show
# set the swappiness
sysctl vm.swappiness=10
# save config to use after restart
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
```

#Install [LetsEncrypt Dokku Plugin](https://github.com/dokku/dokku-letsencrypt)

What would be a good shop without SSL? So now we add the domain for the vendure instance and install the LetsEncrypt plugin and add a cronjob to renew the certificate automatically:

```
# add domain to dokku app
dokku domains:set myshopapi API.YOURDOMAIN.COM

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

# set your email
dokku config:set --no-restart myshopapi DOKKU_LETSENCRYPT_EMAIL=YOUREMAIL@example.com

# install certificate for domain and add renewal cron job
dokku letsencrypt:enable myshopapi
dokku letsencrypt:cron-job --add
```

# Set further Environment Variables

Since sensitive data should not be in your source code when checking into git, we will add some more ENV variables for the SMTP connection, which will be used to send emails and also one for the session secret.

```
dokku config:set --no-restart myshopapi SESSION_SECRET=YOUR_SESSION_SECRET_KEY

dokku config:set --no-restart myshopapi SMTP_HOST=YOUR_SMTP_HOST
dokku config:set --no-restart myshopapi SMTP_PORT=YOUR_SMTP_PORT
dokku config:set --no-restart myshopapi SMTP_USER=YOUR_SMTP_USER
dokku config:set --no-restart myshopapi SMTP_PASSWORD=YOUR_SMTP_PASSWORD
```

#Change your `vendure-config.ts` File

Now that we have everything ready, we can update our config file with all the ENV variables. We will also add `cors.origin` setting to be able to query the API `myshopapi.example.com` from `example.com` and set the correct `assetUrlPrefix`. Here's how your config file could look:

```ts
import path from 'path';

import {
  VendureConfig,
  DefaultJobQueuePlugin,
  examplePaymentHandler
} from '@vendure/core'

import { Transport } from '@nestjs/microservices'

import { AssetServerPlugin } from '@vendure/asset-server-plugin';
import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
import { ElasticsearchPlugin } from '@vendure/elasticsearch-plugin';
import { EmailPlugin, defaultEmailHandlers } from '@vendure/email-plugin'

export const config: VendureConfig = {
  workerOptions: {
    transport: Transport.TCP,
    options: {
      host: 'localhost',
      port: 3020
    }
  },
  apiOptions: {
    port: Number(process.env.PORT) || 3000,
    adminApiPath: 'admin-api',
    shopApiPath: 'shop-api',
    cors: {
      origin: /example\.com$/
    }
  },
  authOptions: {
    sessionSecret: process.env.SESSION_SECRET
  },
  dbConnectionOptions: {
    type: 'mysql',
    synchronize: false,
    logging: false,
    port: Number(process.env.MYSQL_PORT) || 3306,
    database: process.env.MYSQL_DB,
    host: process.env.MYSQL_HOST,
    username: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    migrations: [path.join(__dirname, '../migrations/*.ts')]
  },
  paymentOptions: {
    paymentMethodHandlers: [examplePaymentHandler]
  },
  plugins: [
    DefaultJobQueuePlugin,
    AssetServerPlugin.init({
      port: 3001,
      route: 'assets',
      assetUploadDir: '/storage/assets',
      assetUrlPrefix: 'https://myshopapi.example.com/assets/'
    }),
    ElasticsearchPlugin.init({
      host: process.env.ELASTICSEARCH_HOST,
      port: Number(process.env.ELASTICSEARCH_PORT) || 9200
    }),
    EmailPlugin.init({
      handlers: defaultEmailHandlers,
      templatePath: '/storage/email/templates',
      transport: {
        type: 'smtp',
        host: process.env.SMTP_HOST || '',
        port: Number(process.env.SMTP_PORT) || 587,
        auth: {
          user: process.env.SMTP_USER || '',
          pass: process.env.SMTP_PASSWORD || ''
        }
      },
      globalTemplateVars: {
        fromAddress: '"Example" <info@example.ch>',
        verifyEmailAddressUrl: 'https://example.com/verify',
        passwordResetUrl: 'https://example.com/password-reset',
        changeEmailAddressUrl: 'https://example.com/verify-email-address-change'
      }
    }),
    AdminUiPlugin.init({
      port: 3002
    })
  ]
}

module.exports = { config };
```

#Setup Git

Finally we can add the droplet as remote in our git repostory and push our code to it:

```
git remote add dokku dokku@IP_OF_YOUR_DROPLET:myshopapi
git push dokku master
```

#Some useful Dokku commands
```
# output app logs
dokku logs myshopapi
# output Elasticsearch logs
dokku elasticsearch:logs myshopapi-elasticsearch
# restart the app
dokku ps:restart myshopapi
# connect to MySQL database
dokku mysql:connect myshopapi-mysql
USE myshopapi_mysql;
# export/import an SQL file from/into database
dokku mysql:export myshopapi-mysql > backup.sql
dokku mysql:import myshopapi-mysql < backup.sql
```

I hope this guide will help you setting up your shop API. Please comment if something is not working or if you have other tips, that you would like to share. You can also join the [Slack channel](https://join.slack.com/t/vendure-ecommerce/shared_invite/enQtNzA1NTcyMDY3NTg0LTMzZGQzNDczOWJiMTU2YjAyNWJlMzdmZGE3ZDY5Y2RjMGYxZWNlYTI4NmU4Y2Q1MDNlYzE4MzQ5ODcyYTdmMGU) or look into the [real world Vendure project](https://github.com/vendure-ecommerce/real-world-vendure) on Github, which might help you too.