Deploying SnapperStore with Digital Ocean


Overview

SnapperStore is a sample image, audio, and video upload client & server. This is a guide to deploying it to a staging server that others can get to. I’ve made it very general, with these components in mind:

  • Vue.js / Vuetify front end
  • Node.js / Express / Multer
  • database support via Mongoose and mLab (for MongoDB)
  • optional email support via SendGrid

The repo is at: https://github.com/DanielSmith/snapperstore

There are many ways to do this. I wanted to write something I can point to for how I deploy a basic Vue client + Node server + external services project.

I want to especially acknowledge the article and video “Tutorial: How to Deploy a Node.js App to DigitalOcean with SSL”, by Jason Lengstorf. I am repeating and pointing to much of his work, but adding in some stuff about configuring for MongoDB and email as services from other sites.

The Big Steps

  • Spin up a server (instance of Ubuntu, running on Digital Ocean)
  • Set up a working user account, and a basic firewall, with open ports that can hit Node.js and Vue.js dev processes
  • Clone Git repo to get SnapperStore
  • Set up mLab for MongoDB
  • (optional) Set up SendGrid for email
  • Local Config tweaks
  • Try it!

Spin Up A Server

I’m going to use Digital Ocean for hosting my client and server. I prefer running client and server as separate processes (headless CMS), and it’s easy to get a full blown Ubuntu instance going.

 You can get $10 in free Digital Ocean credit by using my referral link: https://m.do.co/c/c295876bd25b

Sign up — Digital Ocean will ask to confirm your email, and will ask for Credit Card or PayPal information. I highly recommend enabling Two Factor Authentication (see Additional Notes at the end of this article).

Let’s create a server. Once logged into Digital Ocean, click “Create” from the upper right menu:

Each server that you create is called a “droplet”. Select Droplets. This will bring up this screen:

We want to be a bit more specific than just an Ubuntu image, so click on “One-click apps”:

And choose NodeJS:

Because we will be running two processes (serving Vue client, and Node.js server), and potentially uploading large images or videos, I pick a size that’s just up from the smallest — the $10 one:

give a little headroom for large uploads

We are doing fine. Just a couple more steps!

Digital Ocean has data centers around the world. Pick the one closest to you:

Adding an SSH key allows you to securely log in from your device (desktop, laptop) without having to enter a password. See the article “https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-digitalocean-droplets” if you need some background information.

Launch the little dialog from “New SSH Key”:

From here, you will paste in the contents of an ssh key from your own machine. See https://code.lengstorf.com/deploy-nodejs-ssl-digitalocean/#add-your-ssh-key-to-the-droplet for more detail:

Once you have created your key, assure that it is selected (so, for me, I named mine “dls”)

Ok! Let’s indicate that we want one Droplet, give it a name, and press “Create”:

This kicks off a process that creates your server:

Once it is ready, you can hover over the newly allocated IP address, and click on it to copy:

Let’s see if this worked. Go to a command line on your own machine, and try to ssh in:

# use the specific address that Digital Ocean gave you
# if you did the IP address copy above, paste it here:
$ ssh root@123.123.123.123

The very first time you connect to a new machine, your local machine will prompt you to see if it is ok to trust it:

ssh root@[your digital ocean droplet ip address]
The authenticity of host ‘[...]' can’t be established.
RSA key fingerprint is [....]
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ‘[...]’ (RSA) to the list of known hosts.
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0–98-generic x86_64)
[lots of lines]
— — — — — — — — —— — — -
Thank you for using DigitalOcean’s NodeJS Application
The “ufw” firewall is enabled. All ports except for 22, 80, and 443 are BLOCKED
— — — — — — — — —— — — -
To delete this message of the day: rm -rf /etc/update-motd.d/99-one-click
root@nodejs-vue-1gb-sfo1–01:~#

Reward yourself. That might involve chocolate or coffee; or both! I don’t recommend a beer just yet.. we still have some work to do.

Set Up User And Basic Firewall

Next steps are to:

  • create a user, and set them up so they can do commands as root
  • give them a ssh login by key
  • have the backup of being able to log them in by password
  • add rules to the firewall, so that we will be able to get in on ports 8080 and 3100.
  • take away the standalone password login via ssh

New User

Since we are logged in as root, we can create a user:

root@nodejs-vue-1gb-sfo1-01:~# adduser devuser
Adding user `devuser' ...
Adding new group `devuser' (1000) ...
Adding new user `devuser' (1000) with group `devuser' ...
Creating home directory `/home/devuser' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for devuser
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
root@nodejs-vue-1gb-sfo1-01:~# usermod -aG sudo devuser
root@nodejs-vue-1gb-sfo1-01:~# id devuser
uid=1000(devuser) gid=1000(devuser) groups=1000(devuser),27(sudo)

So far, so good. Don’t forget the UNIX password you gave your user. If you ever lose your ssh keys, you can go to the Digital Ocean web site, launch a console, and log in via username and password (the console is not an ssh login).

Remember that tidbit. It will come in handy some day.

Let’s get your user set up with an ssh key login.

Go to your Local machine, and grab your ssh key:

Brooklyn:~ dls$ pbcopy < ~/.ssh/id_rsa.pub

Go back to the Remote (Digital Ocean) login, and change to your newly created user:

exec login devuser
Password:

The prompting here for a password is deliberate. It will help reinforce remembering it, in case you ever need it. You should be in the home directory. Let’s create an ssh setup for the user:

$ pwd
/home/devuser
$ mkdir .ssh ; chmod 700 .ssh
# use your favorite editor, paste from your clipboard,
# and create the new .ssh/authorized_keys file
# (the clipboard contains the contents of your
# local ~/.ssh/id_rsa.pub file)
$ vi .ssh/authorized_keys  
$ chmod 600 .ssh/authorized_keys

Go to a Separate Local terminal, and see if you can get into your new user:

ssh devuser@[your Digital Ocean IP Address]

If you get in, wonderful! If you don’t, at least you are still logged in via another window, and can backtrack to find the problem.

Firewall

We need to be root, in order to configure new rules for our firewall. You will need to recall the password you made for your devuser:

$ sudo bash
[sudo] password for devuser:
root@nodejs-vue-1gb-sfo1-01:~#

For reference, if you push a root shell, then back out, you can go back to it without having to re-enter a password (for a given login session within a window):

root@nodejs-vue-1gb-sfo1-01:~# exit
exit
devuser@nodejs-vue-1gb-sfo1-01:~$ sudo bash
root@nodejs-vue-1gb-sfo1-01:~#

Let’s check the status of the firewall, as it ships with the droplet:

root@nodejs-vue-1gb-sfo1-01:~# ufw status
Status: active

To Action From
-- ------ ----
22 LIMIT Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
22 (v6) LIMIT Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)

We want to add access to port 8080 (for getting the client), and port 3100 (for making calls into our Node.js server):

root@nodejs-vue-1gb-sfo1-01:~# ufw allow 8080
Rule added
Rule added (v6)
root@nodejs-vue-1gb-sfo1-01:~# ufw allow 3100
Rule added
Rule added (v6)
root@nodejs-vue-1gb-sfo1-01:~# ufw status
Status: active

To Action From
-- ------ ----
22 LIMIT Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
8080 ALLOW Anywhere
3100 ALLOW Anywhere
22 (v6) LIMIT Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
8080 (v6) ALLOW Anywhere (v6)
3100 (v6) ALLOW Anywhere (v6)

Great. We’ll be able to get to our app from the outside world. Let’s lock down logins a bit more, and get to setting up our client and server.

Disable Password Logins

We’re going to disable root logins via ssh. This eliminates a major attack vector. As root, edit the /etc/ssh/sshd_config file with your favorite editor:

root@nodejs-vue-1gb-sfo1-01:~# vi /etc/ssh/sshd_config
# find this:
PermitRootLogin yes
# change to:
PermitRootLogin no
# find this and make sure it is "no".  Mine was:
# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no
# save the file!

Let’s make our changes take effect:

root@nodejs-vue-1gb-sfo1-01:~# sudo systemctl reload sshd

Did it work? Go to a Local terminal and try to log in:

Brooklyn:~ dls$ ssh root@[your Digital Ocean IP Address]
Permission denied (publickey).

Good. Now let’s try the devuser:

Brooklyn:~ dls$ ssh devuser@[[your Digital Ocean IP Address]
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-98-generic x86_64)

Very good. Now let’s get our app going.

Grab The App

With the droplet I used, the git command that comes preinstalled:

$ git --version
git version 2.7.4

I’m going to create a directory for the SnapperStore project, and grab a copy of the git repo (based at https://github.com/DanielSmith/snapperstore)

$ mkdir projects
$ cd projects/
$ git clone https://github.com/DanielSmith/snapperstore.git
Cloning into 'snapperstore'...
remote: Counting objects: 232, done.
remote: Compressing objects: 100% (61/61), done.
remote: Total 232 (delta 59), reused 65 (delta 31), pack-reused 136
Receiving objects: 100% (232/232), 130.85 KiB | 0 bytes/s, done.
Resolving deltas: 100% (122/122), done.
Checking connectivity... done.
$ cd snapperstore/
$ ls
common LICENSE README.md snapstore-client snapstore-server

Great. So we have client and server directories to set up. Let’s install dependencies. I prefer to keep different terminals open for client and server. You may want to try the program “screen” for this — it is very handy for detaching a session, and picking it up again later.

Global Dependency: nodemon

Make sure that you have “nodemon” installed. It is used run the Node.js process. Run as root:

/snapstore-server# npm install -g nodemon

Client

$ cd snapstore-client
$ npm install

Server

$ cd snapstore-server
$ npm install

Set Up Database at mLab

We could set up MongoDB (and sendmail) on our local Digital Ocean droplet, but the point of this article is to show how to tie together services from multiple places. At mLab (https://mlab.com/), we can get free access to a MongoDB instance.

Go to https://mlab.com/signup and create an account for yourself:

The mLab signup form

Go ahead and sign in:

Click on “+ Create new”:

I chose Amazon Web Services, and “Sandbox”, which is the Free Tier.

When you hover over the available regions, you will see this:

It’s a little confusing. Where is the “Continue” button? It is hiding in the far lower right corner:

That will take you to this spot, to pick a region. Pick the closest one to you, and Continue:

From the next screen, name your database (I picked “snapperstore”), and Continue:

That will bring you to a confirmation screen:

This will now show up as a deployed database in your account:

Click on your “snapperstore” db:

You will see an address and port number that is specific to you.

We need to create a database user, which is used when the Node.js side reaches out to connect to the MongoDB instance on mLab. Click on Users, and then Add Database User:

I chose “ssuser”, and hit Create. That results in:

So, you can test a login to this from your Local machine:

local$ mongo [your mlab db #].mlab.com:[port]/snapperstore -u ssuser -p [your pw]
MongoDB shell version v3.4.4
connecting to: mongodb://[db #].mlab.com:[port]/snapperstore
MongoDB server version: 3.4.9
rs-[db #]:PRIMARY> exit
bye

Keep your mLab browser tab open. You will refer to it when it comes time to tweak your local configuration (so that it can find its database)

(optional) Set Up Email at SendGrid

SendGrid (https://sendgrid.com) provides a free tier of access for sending out email. This is great. I know I am not a fan of dealing with local email setup.

When signing up for SendGrid, choose the free plan:

You may need to check your local email for something from SendGrid: a confirmation link to click on.

Once you have signed up and logged in, go to https://app.sendgrid.com/guide

This can also be found from the pull-down menu under your name:

You will see this screen:

Choose “Integrate using our Web API or SMTP relay”, and “Web API”

Wonderful. We want “Node.js”:

Give your key a name, and select “Create Key”. You will be given a key that starts with “SG.”, followed by a long string of letters and numbers. Hang on to this. For future reference, you can go to “API Keys”, from the left sidebar menu. You will not be able to see the key, though. You can only delete it, or edit a few parameters.

Config: IP Address

You will need to know the IP address of your server. There are a couple of quick ways to get that:

Via your Digital Ocean dashboard — go to your list of Droplets:

Or from the terminal:

$ ifconfig eth0

It is the “inet addr:” (not inet6). It’s in the form of 123.123.123.123

Where you see “your digital ocean IP” in this document, I am referring to the IP address of the Droplet (the Remote address)

Config: Client

The local configuration for the client is kept in the src/config.js file. For the purposes of this article, the main thing that would be changed is your IP address.

const configs = {
ENV: 'Staging',
CLIENT: 'http://[your digital ocean IP]:8080',
SERVER: 'http://[your digital ocean IP]',
SERVER_PORT: ':3100',
USING_DB: 1,
USING_EMAIL: 0
}

Config: Server

The local configuration for the server needs to be created in the snapstore-server directory.

Create the file “config.json”:

{
"USING_DB": 1,
"USING_EMAIL": 0,
"SENDGRID_API_KEY": "SG.[your key]",
"SS_ADMIN_EMAIL": "receivesAdminEmail@example.com",
"SS_ADMIN_FROM": "replyAddress@example.com",
"MONGO_DB_HOST": "mongo host",
"MONGO_DB_CONNECT": "mongodb://someUser:somePassword@[DBHost #].mlab.com:[DB Port #]/snapperstore",

"SERVER_ADDRESS": "http://[your digital ocean IP]:3100",
"CLIENT_ADDRESS": "http://[your digital ocean IP]:8080"
}

So your directory will look like:

snapstore-server$ ls
api.js config.json node_modules router.js
app.js models package.json utils.js

Notes:

  • SENDGRID_API_KEY - SG.123456778 .. etc
  • SS_ADMIN_EMAIL - where the email is sent TO
  • SS_ADMIN_FROM - the email reply address
  • MONGO_DB_CONNECT - refer to your mLab account. "mongodb://user:pw@12345.mlab.com:1234" is the format you are looking for

Test Client And Server

As I mentioned earlier, I like to keep separate Remote terminals open (visually, or in a “screen” session) for client and server processes.

Client

The package.json file has targets for “dev” and “devs”. Use “devs”, as it runs without trying to pop open a browser:

.../snapstore-client$ npm run devs

> snapstore-client@1.0.0 devs /home/devuser/projects/snapperstore/snapstore-client
> cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --inline --hot

Project is running at http://0.0.0.0:8080/
webpack output is served from /dist/
404s will fallback to /index.html

Server

Now, in another Remote terminal, lets get the Node.js server process going. If USING_DB is on, it will connect to your MongoDB over on mLab:

.../snapstore-server$ npm run server

> snapstore-server@1.0.0 server /home/devuser/projects/snapperstore/snapstore-server
> nodemon --inspect ./app.js

[nodemon] 1.12.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*

Check Your Browser

Point your browser over to the Client address and port you set up in your config files, such as: http://[your digital ocean IP]:8080

This initial check assumes that you have configured things to use the database.

The initial SnapperStore page does not know about any media, so it gives a little instruction:

Try dragging an image in:

New items are bordered in green.

For now, Refresh the page after adding things. There is a bug that deals with adding tags to something that is just a preview. I will fix it soon…

For each day, there will be a button to go to the media that was uploaded for that day…

Let’s add some tags. Click on the pencil:

and press “Add Tags”:

Ok, great. Now lets take a tag away (“X”) and add a new one. Click on the pencil:

..and press “Add Tags”:

You can drag in several audio, video, and image files at once. Be careful about very large files. It really has not been tested for that — this is just meant to be an example project.

Here we have added a sound, and we will give it some tags:

So our page so far looks like:

Let’s do a simple search:

Which results in:

You can search for multiple tags. The search will be “match all of them” (AND) — not “match one” (which would be an OR)

That is a quick look at the functionality. Over a period of days, you can build up quite a collection. Tags will search everywhere, and the Day buttons just focus on a items uploaded on a specific date.

enjoy!

Additional Notes

A big hat tip to Jason Lengstorf @jlengstorf. His tutorial at https://code.lengstorf.com/deploy-nodejs-ssl-digitalocean/ provides the basis for much of this one. See his for additional information on using PM2, SSL, Certs, and Nginx. Mine is more a dev level deploy, focusing on how to bring in outside services. I aspire to write as well as he does!

You may run into configuration errors. One strategy is to turn off the use of database and email (USING_DB and USING_EMAIL) on the client and server sides, and just get a simple setup working. The basic idea is that you have a dev server driving the client (where you get the Vue.js frontend from), and that you have a Node.js api endpoint.

Let me know of any feedback. There is much more I can do to turn this into a better deploy (such as using port 80).

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.