DEV Community

Cover image for Setup a Vendure E-Commerce API on a Dokku Droplet
Manuel Sommerhalder
Manuel Sommerhalder

Posted on • Edited on

Setup a Vendure E-Commerce API on a Dokku Droplet

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!

This guide assumes that you have already installed a local Vendure project via @vendure/create. 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 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 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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Install MySQL Dokku Plugin

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Install Elasticsearch Dokku Plugin

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:

# 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
Enter fullscreen mode Exit fullscreen mode

Since Dokku seems to have an issue 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create a Swapfile

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
Enter fullscreen mode Exit fullscreen mode

Install LetsEncrypt Dokku Plugin

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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 };
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 or look into the real world Vendure project on Github, which might help you too.

Top comments (0)