Strapi + SSL + MariaDB = Awesome Sauce (Part 1)

What is this "Strapi" you speak of?

For those of you whom have heard of Strapi, why are you reading this intro? You already know how awesome it is, get on with it go read the tutorial below!

For the rest of you who might might be living under a rock, let me give you a little info on Strapi the nodejs based headless CMS.
Strapi is a framework that allows you to quickly spin up a front-end and database agnostic Rest / GraphQL API, that also ships with its own React based Admin Panel. The core code and many of the current plugins are open-source (you can visit the github here).
I’ve spent the past few months using it to build out a project for community of gamers to track a lot of the data we get from a video game, and during this time I have spent some time talking to the developers behind Strapi.
As many of you know, testing a project on your local machine and doing so in a staging or a production environment can introduce some unique problems. I will be going over how to use Strapi with HTTPs (secured by Let’s Encrypt on a linux virtual private server (VPS for short).

Guides in this series

At first I created a simple Medium guide back when Strapi was in alpha (you can see it here) but as time has gone on and Strapi has gone into the beta phase of development much has changed. Thus I will be doing a series of guides to try and cover most of the usual install cases. A footnote to the above though I will not be touching Heroku or any of the "serverless" options as in my mind, I am not a fan. If "serverless" is what you are looking for, I'm sorry to say but you are in the wrong place.

The guides

Enough fluff lets get on with it!

What you are going to need

  1. A VPS of course
    a. Vultr <-- What we will be using
    b. Digital Ocean
    c. AWS
    d. GCloud
    e. Some other random VPS provider
  2. A Domain with a DNS Provider (We will be using Cloudflare for our DNS)
  3. A little linux terminal experience
  4. (Optional) A Strapi project already hosted somewhere like Github or Gitlab

What we will be using

  • Strapi (Of course)
  • Ubuntu 18.04 LTS (Easily swappable to CentOS/RHEL 7)
  • Node 12 / NPM 6
    • PM2 (Node Process Manager)
    • Yarn (Better than NPM!)
  • Nginx
  • MariaDB (MySQL drop in replacement)
  • ACME.sh (Let's Encrypt)

Building the VPS on Vultr

If you haven't already you are going to need a Vultr account, you are more than weclome to use my referral code: https://www.vultr.com/?ref=7404107 as this helps me keep the costs down on running VPSes for these guides ;)

After you have created the count we need to create the VPS! Once you are logged in and are sitting at the dashboard click on the big 'ol plus sign in the upper right:

Selection_340

Now we need to pick some settings such as the server type, location, specs, and metadata. In our case we will be using:

  • Cloud Compute (Cheapest option, typical VPS)
  • Los Angeles Location
  • Ubuntu 18.04 LTS (64-bit)
  • $10/mo 1 CPU, 2GB Memory, 20TB Bandwidth, 55GB SSD
    • Strapi needs at least 2GB of memory to build the AdminUI
  • My laptop's SSH Key (Guide to creating them here
  • And a hostname: dm-mariadb-tutorial1

Selection_341

Once you've selected all the options required let's deploy! It may take a few minutes while Vultr spins up the machine, you can always click on it and hit the console button in the upper right:

Selection_342

Now we need to SSH into the server, if you setup the SSH keys this makes it easy! Else if you did not you can find the password to the root user in the VPSes dashboard:

Selection_343

If you are on Linux or Mac you can just pop open a terminal and use the following to SSH into the new VPS!

ssh [email protected] here

If you setup your keys properly it won't prompt you for a password, else if it does you can just copy the one from the Vultr dashboard and you're in!

Selection_344

VPS Setup complete!

Some basics before we jump in

Before we move on to start installing software, configuring stuff, and just generally being awesome I do need to explain a few concepts here.

Always, always, always update your server first

Security is important and patches are the lifeblood of keeping your server secure. On Ubuntu this simple one-liner will do just that:

apt update && apt upgrade -y && apt dist-upgrade -y && apt autoremove -y

What this does is update the repos, apply software patches, apply kernel updates, and remove any old packages we don't need.

Create a Service user without sudo privileges!

I know, I know trust me no one likes swapping around users willy-nilly and working on a user that can't even sudo! I'm not crazy I swear! Service users serve the purpose of serving applications (say that 10 times fast). Their purpose is to do one thing in life, be the sole owner of an application running on a server. You will see this in many different forms (www-data anyone?) but our goal here is to put up a small barrier in case your application has a security issue. We certainly don't want someone randomly running elevated commands on our server!

On most linux distros this is an easy one adduser strapi now you can set this up with a password, ssh keys, the whole shabang. Personally I prefer to lock this account out so that it can only be accessed after you login with another account. For simplicity here, I leave the additonal server hardening to you (and maybe some awesome guide links like this one from Digital Ocean)

Selection_346

Don't use the /home directory for services!

For some reason, people think that the cd command is a tad too far for them and want to use their users home directory to run services from, why? I honestly have no idea, lazyiness maybe?

Let me introduce you to a linux directory called /srv, what is this magic directory you rarely see anywhere? It quite literally means "service"! Still don't believe me? Take a look here the description from TLDP

Naturally of course we are going to use this lovely directory to run Strapi from!

In our case we will create the following directory and give our new service account access to it:

mkdir /srv/strapi && chown strapi:strapi /srv/strapi && chmod 755 /srv/strapi

Selection_347

Onto... Installing the software! (finally)

Nginx

Nginx is needed in Strapi case to add our SSL salt guy "flavor" (and secure it of course)

Ubuntu already includes nginx in it's base repos so doing a simple:

apt install nginx -y

Is all we need! After you have installed it you can check it's status with:

systemctl status nginx

Selection_349

MariaDB

Now MariaDB is a bit tricker, we want to make sure we are on the latest stable version (10.4 as of writing this) and while Canonical does have MariaDB in it's base repos, it's a tad out of date:

Selection_350

So we are instead going to install MariaDB right from the source! The MariaDB Foundation has a great little tool to feed you all the commands you need here, but I've done the heavy lifting for you so this is what we need:

# Making sure we have software-props
apt install -y software-properties-common

# Adding the keys
apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8

# Adding the repo
add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://sfo1.mirrors.digitalocean.com/mariadb/repo/10.4/ubuntu bionic main'

# Updating our repos and installing MariaDB!
apt update && apt install mariadb-server-10.4 -y

Selection_351

Once we have it installed we can check it's status with systemctl status mariadb

Selection_352

Now that we have MariaDB installed we want to go ahead and create our database, user, and grant permissions while we are in our root account since MariaDB no longer has a default 'root' password. To do this we just run mysql -u root and it will take us to a mysql shell. From here we can create our database, create the user, and grant that user permissions on the database.

I will be using the following:

  • User - 'strapi;
  • Password - 'strapiisawesome1234'
  • Database - 'mystrapiapp'

So lets run the commands!

create database mystrapiapp;
create user [email protected] identified by 'strapiisawesome1234';
grant all privileges on mystrapiapp.* to [email protected];
exit

Selection_353

And we are all done here! Our database is now ready to setup Strapi with!

Node.js

There are many ways to install node, you have normal repos, nvm, npx, and probably 100 other ways I don't even know about but in our case we are going to do this with nvm.

Normally this isn't something I use myself, mainly because I find it annoying and painful but for many users they like the simplicity of it. So first off we need nvm (for those that don't know nvm stands for "Node Version Manager")

In this case however nvm is typically user based, meaning we finally need to switch over to our Strapi user. If you gave your user it's own password, ssh keys, and what not it would probably be easier to open up a 2nd terminal window and keep your root shell in case you need to install something or reconfigure something (like we will with nginx)

Footnote for Linux users: If you have never heard of the awesome terminal called Tilix I highly suggest you check it out!

From your Strapi user we now need to install nvm, and naturally from their Github we have another simple one-liner!

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

Selection_354

Now if you get confused as to why nvm isn't a command, linux is odd in that it doesn't just reload your $PATH automatically to reload this you can either logout and back in (boring, slow, blah) or you can just use source ~/.profile to reload that file!

Selection_355

Now we need to install node, note that we are not actually installing this globally on the server, this is useful especially if you plan to run multiple applications on this server that may need different node version! In our case we need either Node v10 or Node v12, since Strapi just added Node v12 support let's give it a shot!

nvm install 12

And just like that we have node and npm!

Selection_357

Now lets install some Node global packages we are going to need:

  • PM2 - For running Strapi as a service
  • Yarn - Better and faster than NPM but does all the same things, plus little bonus feature

npm i -g pm2 yarn

Selection_358

You will notice that the location for these packages is in our Strapi users home directory, this means that only the Strapi user will be able to run these. Again useful for if you need multiple node versions on the server for other applications ;)

We are now all set to get our Strapi project started!

Creating our Strapi Project (or cloning from git)

Now that we have Nginx, MariaDB, and Node installed and setup we need to create our Strapi project. If you already have one you built locally and pushed into git you can easily skip this step and just simply clone it into the directory we created.

Remember that nice little feature I mentioned about yarn? We don't even need to install the strapi package on our server! Lets move over to the directory we created while logged in as our strapi user and create our first project:

cd /srv/strapi && yarn create strapi-app mystrapiapp

Plugin your database settings and boom! Strapi app created!

Selection_359

Now if we want to just simply run this and check to make sure all is working (note the build step might take a little time, so go grab a coffee):

cd mystrapiapp && yarn build && yarn develop

Selection_360

Boom!, our AdminUI is up and ready to create our first admin user!

Selection_361

Setting up PM2 to run our app as a service

Now that we have Strapi ready to go, we want to keep this running so we can close our SSH session or go do other things (like configure nginx ;) ) thus we need to run this as a service. PM2 is also helpful in that you can also set things to startup at boot.

Now depending on how you plan to run Strapi either as production, staging, or development (say if you cloned a github project you are developing locally) you may want more than one Strapi instance running here on different ports. In our case we are going to do all 3 but they will all be using the same database. This isn't most optimum solution, and I wouldn't recommend it, but we are doing this simply to show the different commands to create the pm2 service.

Development

This one is by far the easiest, we will keep the default port and just simply create the service with no NODE_ENV variable. However we do want to tweak our settings a bit so that when we want to push our project into git we aren't committing our database details ;)

First lets modify our config/environments/development/database.json file:

{
  "defaultConnection": "default",
  "connections": {
    "default": {
      "connector": "strapi-hook-bookshelf",
      "settings": {
        "client": "mysql",
        "database": "${process.env.DATABASE_NAME}",
        "host": "${process.env.DATABASE_HOST}",
        "port": "${process.env.DATABASE_PORT}",
        "username": "${process.env.DATABASE_USER}",
        "password": "${process.env.DATABASE_PASS}"
      },
      "options": {}
    }
  }
}

Simply setting these dotenv options allows us to declare them when we start the service (or in a future tutorial that will be linked here using a .env file)

Remember these are our database settings:

DATABASE_NAME=mystrapiapp
DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_USER=strapi
DATABASE_PASS=strapiisawesome1234

So now we just make sure we are in the root of our Strapi project

cd/srv/strapi/mystrapiapp

And we want to create the pm2 service

DATABASE_NAME=mystrapiapp \
DATABASE_HOST=127.0.0.1 \
DATABASE_PORT=3306 \
DATABASE_USER=strapi \
DATABASE_PASS=strapiisawesome1234 \
pm2 start npm --name="mystrapiapp-dev" -- run develop

Selection_362

And now we have our Strapi App running in a development mode as a service! From here we can now use the following commands to control this service:

# Start it
pm2 start mystrapiapp-dev

# Stop it
pm2 stop mystrapiapp-dev

# Reload (Soft restart)
pm2 reload mystrapiapp-dev

# Restart
pm2 restart mystrapiapp-dev

# View the logs
pm2 logs mystrapiapp-dev

# Delete it (only the service not the project)
pm2 delete mystrapiapp-dev

After we finish up the staging and production services we will move on to setting up the startup script and making sure we save these processes so they will start up on boot!

Staging & Production

Since we are using the same database for all our environments we can take the modified database.json file and just copy it to the other environment settings directory:

cp config/environments/development/database.json config/environments/staging/

cp config/environments/development/database.json config/environments/production/

And now we need to tweak the port each of these will run on, remember our development server is already running on port 1337 so lets do the following:

  • Development - Port 1337
  • Staging - Port 1338
  • Production - Port 1339

By default strapi already has the option to use a environment variable for this, so we just add that to our pm2 service command. Let's start with the Staging one:

NODE_ENV=staging \
PORT=1338 \
DATABASE_NAME=mystrapiapp \
DATABASE_HOST=127.0.0.1 \
DATABASE_PORT=3306 \
DATABASE_USER=strapi \
DATABASE_PASS=strapiisawesome1234 \
pm2 start npm --name="mystrapiapp-staging" -- run start

Notice I added the NODE_ENV and PORT variables while also changing the app name to mystrapiapp-staging and modified the startup command to use start. This way our staging server won't auto-reboot on changes. Now we can mirror that to our production:

NODE_ENV=production \
PORT=1339 \
DATABASE_NAME=mystrapiapp \
DATABASE_HOST=127.0.0.1 \
DATABASE_PORT=3306 \
DATABASE_USER=strapi \
DATABASE_PASS=strapiisawesome1234 \
pm2 start npm --name="mystrapiapp-production" -- run start

Selection_363

And tada! We have all 3 environments running! We can check all 3 at once by using pm2 logs with no app name after it and you see the last few lines of each!

Selection_364

Startup command and saving our process list

PM2 has a handy little command that will create our startup script for us just by doing pm2 startup it detects which init system you are using and give you a command to copy paste into the terminal note this commands needs sudo privileges, remember how I said it was worth it to just open a new ssh session and keep our root one active? This is why ;)

Selection_365

So I simply paste that command into my root session:

Selection_366

And the startup script is active! The final step on your strapi service user is to run pm2 save to dump the services config to a file that pm2 uses when it starts up to respawn the 3 services we made!

Selection_367

Part 2

Stay tuned for part 2 where we will configure our Nginx virtual host file, get our SSL Cert from Let's Encrypt using the ACME.sh script (with auto renewals) and setup our DNS on Cloudflare!