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 ofexample.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:
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
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
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
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
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
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
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:
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 or look into the real world Vendure project on Github, which might help you too.
Top comments (0)