- Create an SSH key, which you'll use for a secure connection to your server.
- Sign up for a droplet on Digital Ocean.
- Install and configure Node, PostgreSQL, and other necessary software on your droplet.
- Push working code to GitHub. Make sure express.static points to build folder.
- Clone project from GitHub to server and
npm install
. - Create .env/config.js files on server.
- Create a build with
npm run build
. - Run
pm2
(so hosted project is always running).
- Set up nginx to host multiple projects on same droplet.
- Set up a domainname to point to your droplet's IP address.
- Try this cheat sheet.
- Read more in this separate file.
An SSH key gives us a secure connection to our server droplet.
- To begin the creation process, from any folder, type
ssh-keygen -t rsa
. Alternatively, addssh-keygen -t rsa -b 4096
to add 4096-bit encryption (rather than the default 2048). - You will be asked for a location and filename. Just use the defaults.
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/username/.ssh/id_rsa):
- You will be asked to enter a password. YOU MUST NOT FORGET THIS PASSWORD. It cannot be recovered. Make sure it is something you can remember.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
- Two files will then be created in the .ssh in the home folder of your computer (i.e., in ~/.ssh). These files are
id_rsa
andid_rsa.pub
, your private and public keys, respectively. You will notice a randomart image of your key like the one below.
Your identification has been saved in demo_rsa.
Your public key has been saved in demo_rsa.pub.
The key fingerprint is:
cc:28:30:44:01:41:98:cf:ae:b6:65:2a:f2:32:57:b5 [email protected]
The key's randomart image is:
+--[ RSA 2048]----+
| o=. |
| o o++E |
| + . Ooo. |
| + O B.. |
| = *S. |
| o |
| |
| |
| |
+-----------------+
- You will copy the public key (
id_rsa.pub
) to give to Digital Ocean. You will not give out the private key. To see the public key, typecat ~/.ssh/id_rsa.pub
. This will show you a long string starting withssh-rsa
and ending with an email address. - You can also copy the key using
pbcopy < ~/.ssh/id_rsa.pub
.
this is the whole ssh-keygen process
- Sign up for a droplet on Digital Ocean.
- In the Security Settings, paste in the public SSH key. Give it a name to remember which key it is, probably something like "Michelle-Macbook" to remember which computer it is connected to.
- Most students should choose Ubuntu for the operation system.
- Select a droplet size. Most students choose the cheapest droplet (about $5 per month).
- When asked, choose a data center close to you (e.g, San Francisco for the West Coast).
- Select the SSH key you registered earlier.
- Choose a droplet name that can represent multiple projects, not just one (e.g., "kevin-droplet" rather than "personal-project").
- Once the droplet is created, you will receive the droplet's IP address. You will use this to connect to your server through the command line, and you will also use it to log on to the site once it is hosted (unless you set up a domain).
- When pasting in your SSH key, you might get an error saying your private key is invalid, even when you are sure you copied the correct key. This can happen if you accidentally copied spaces at the end of the key string. This especially happens with Windows users using git bash, even when it looks like there are no extra spaces in the selection. Try pasting the key into Notepad or another editor (like VS Code) and then copying again from there.
- Open Terminal.app or another command line interface.
- Type
ssh [email protected]
(e.g.,ssh [email protected]
) to connect to your droplet through the command line. - You will need your password to connect. YOU DIDN'T FORGET IT, DID YOU?
Additional SSH login options
Â
If you need to change your password, you can do so after first logging into your droplet. Obviously, this means you need to know your old password. If you do change it, don't forget you cannot recover passwords, so you will have to take care to remember the new one.
- Log in to your droplet using
ssh root@[your.IP.address]
. - Type
passwd
. You will be prompted to enter your old password and then the new password (twice).
Â
To log in without typing your password, you can add the password to the ssh-agent, a program that holds private keys for authentication. See these docs for more.
- Start the ssh-agent by running
ssh-agent -s
. - Modify the hidden SSH config file on your computer to automatically load keys into ssh-agent's keychain.
- Open the config file using
nano ~/.ssh/config
. - Add the following to the config file:
- Open the config file using
Host *
AddKeysToAgent yes
UseKeyChain yes
IdentityFile ~/.ssh/id_rsa
- Add your SSH private key to the ssh-agent by running
ssh-add -K ~/.ssh/id_rsa
.
Â
If you find it inconvenient to type in your IP address when logging into your server, try customizing your SSH login.
- On your local computer open the hidden SSH config file in your home folder. If you want to use nano, you can enter
nano ~/.ssh/config
. - Inside this file, enter a Host, User, and Hostname in the format below. The Host will be the name you want to use for logging in, and the Hostname will be the IP address for the server. The User will be either "root" or the user if you created a user on the server. Optionally, you can specify a port or leave out this line (which sets the port to its default, 22). Below is a sample config file. A person could log in to the 123.456.7.89 droplet from this computer using either
ssh bakerc
to log in as root orssh bakerm
to log in as user mb.
Host bakerc
User root
Hostname 123.456.7.89
Host bakerm
User mb
Hostname 123.456.7.89
this is what it will look like the first time you ssh into your server
We can setup our server with all the basics it will need with the script below. We will talk about what each part is doing in the following sections. If you want to jump to the next step go here. These lines will cover the setup of a swapfile, installing node, installing nginx, installing pm2.
Copy all of this in at once into your server terminal. You will only do this ONCE when creating a new droplet. You do not repeat these steps for each project.
Copy This one
touch /swapfile;fallocate -l 1G /swapfile;chmod 600 /swapfile;mkswap /swapfile;swapon /swapfile;apt-get update -y && apt-get dist-upgrade -y;apt-get install nodejs -y;apt-get install npm -y;npm i -g n;n stable;npm i -g npm;npm i -g pm2;apt-get install nginx -y;
What it's doing.
touch /swapfile;
fallocate -l 1G /swapfile;
chmod 600 /swapfile;
mkswap /swapfile;
swapon /swapfile;
apt-get update -y && apt-get dist-upgrade -y;
apt-get install nodejs -y;apt-get install npm -y;
npm i -g n;
n stable;
npm i -g npm;
npm i -g pm2;
apt-get install nginx -y;
Many students buy a Digital Ocean droplet on the $5 tier, which comes with limited memory. A swapfile can effectively extend the amount of given memory by swapping out less-recently-used files to the hard disk. This can come in handy. For example, sometimes when running a build on a low-memory droplet, the process will time out because there is not enough memory. Having a swapfile in place can help with this. A swapfile is also a good idea if your project uses Gulp.
touch /swapfile
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
- If you need to turn off the swapfile:
swapoff /swapfile
. - If you need to turn the swapfile back on:
swapon /swapfile
. - If you want to have the droplet load with the swapfile on, use
nano /etc/fstab
to open the fstab file, and at the bottom type/swapfile none swap sw 0 0
. - If you want to tell the server to swap files less often, use
nano /etc/sysctl.conf
to open the sysctl.conf file and typevm.swappiness=10
to set the swappiness to 10 (instead of the default 60).
- If the you get an error related to the
fallocate
command, it may be that the swapfile is currently on. You cannot fallocate a swapfile that is currently in use. Try turning off the swapfile withswapoff /swapfile
and then entering the commands again, starting withfallocate -l 1G /swapfile
and ending withswapon /swapfile
(which turns it back on).
The first time you access your droplet, you need to install Node/npm so you can manage and run your code.
- Run
apt-get update && apt-get dist-upgrade
to update the software Linus know about. - Run
apt-get install python-software-properties
. This is only a dependency, we aren't doing anything with Python - Run
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
to add version 8 of Node.js and most up to date npm to yourapt
repository. If you want a different version of node, change the 8 aftersetup_.
- Run
apt-get install nodejs -y
to install Node/npm. - Double-check your versions and install with
node -v
andnpm -v
.
If your project was bootstrapped using create-react-app, a default service worker was registered in your index.js file. Make sure registerServiceWorker()
is commented out or that the service worker is otherwise not registered. Doing so will save some headaches caused when trying to serve your local files and server files together.
-
On local machine, instead of using absolute paths (e.g., 'http://localhost:3200/auth') use environment variables. In other words, everywhere you have a full path with "localhost" in it, replace that path string with a reference to a variable, and store that variable and value in your .env (or config.js) file.
-
For example, if you have an
<a>
tag with an Auth0 link like this...<a href={"http://localhost:3200/auth"}><li>Log in</li></a>
... replace the string so it says something like this:
<a href={process.env.REACT_APP_LOGIN}><li>Log in</li></a>
-
In the .env file, store the variable. No need for a keyword like
var
orconst
. Also, quotation marks are optional (unless there is a space inside the string, in which case they are required). The variable for the example above would look like this inside the .env file:REACT_APP_LOGIN=http://localhost:3200/auth
-
-
Replacing full paths with environment variables is generally a good idea throughout your whole app (both front end and back). For React, however, keep two things in mind:
- If you built your front end with
create-react-app
, your React front end can only access variables that start withREACT_APP_
. Thenpm start
command builds them into the app. Variables that are accessed outside of React (i.e., in your back end), do not need theREACT_APP_
prefix. create-react-app
does not allow you to access files outside the src folder, so if you need environment variables in your front end, you will have to put an .env file inside the src folder.
- If you built your front end with
-
Make sure the project is working on your local machine.
-
Run
npm run build
to create a build folder. -
In your server, use the code below to point your server to your front end static files. This tells express to look for a build folder. The
__dirname
variable tells it to start at the current file where Node is running (i.e., your server file), and/../build
tells it to then go up one file and into a build folder.app.use( express.static( `${__dirname}/../build` ) );
-
If you are using React Router's browserHistory, you'll need to use Node's built-in
path.join()
method as a catch-all to ensure the index.html file is given on the other routes. Althoughpath
is built in, you must require it withconst path = require('path');
. Then invoke thejoin()
method in your server near the end, below all other endpoints, since this endpoint uses an asterisk as a catch-all for everything other than specified endpoints.app.get('*', (req, res)=>{ res.sendFile(path.join(__dirname, '../build/index.html')); });
Other recommendations
Inside your index.html file, find the <title>
tags inside the <head>
. If you used create-react-app, the index.html file will be inside the public/ folder and the title
tags will say <title>React App</title>
. Inside <title></title>
, put the name of your app. This name will appear in the browser tab when you go to your site.
If you used create-react-app, your README is full of boilerplate docs about create-react-app. Delete all of this and replace it with your own content. A good use for this README is to introduce users to your app with an introduction or overview or images that help the user understand how the app works. This can be particularly useful for portfolio pieces, since potential employers will have an intro page showing them what your app is all about and what they should expect.
- Commit and push your working code to GitHub.
- Use
ssh [email protected]
to connect to your droplet, and usecd
to go into your project folder on the server. - Clone the project to the server using
git clone url-to-your-github-project
. Once done, your code will be on the server, except for node_modules, .env variables, and a build folder (since these are all .gitignored and therefore not copied to and from GitHub).
- Run
npm install
inside the project folder on the server to install node packages.
- Recreate any .env file or config.js in the the server.
- Use
touch .env
to make an .env file. - Use
nano .env
to edit the file.
- Use
- Go to your code editor and copy the contents of your local .env file. Inside nano on the server, paste in the contents you copied so they will now be in the server .env file. Change any full paths containing "localhost" (e.g., 'http://localhost:3100/api/users') to relative paths instead (e.g., '/api/users'). We do this because your server might be structured differently than your local machine, so we give the server a relative path, and it knows what to do from there.
- To exit nano, use Ctrl+x to exit. It will ask if you want to save. Type
Yes
ory
, then hit Return to confirm.
- Create a build folder using
npm run build
. This will create a folder called "build" with an optimized version of your project. The express.static line you added to the server file will tell the server to look in that build folder for the static files that need to be served. - Now your entire project is saved to the server, including code, node_modules, .env files, and build folder. The next step is to run Node on our project to see if it works from the server.
- It is possible to get a timeout error when running a build on your server. This may happen if your droplet is the cheapest tier (with the least RAM). You might fix this by implementing a swapfile (see the optional section on swapfiles). If you already created a swapfile, trying running through all those swapfile commands again (perhaps there was an error when creating it the first time).
- If you see an error saying
npm build
was called without any arguments, trynpm run build
instead. Yourpackage.json
file shows bothstart
andbuild
together in thescripts
section, and you are used to runningnpm start
(with no "run" command), so you may think you can runnpm build
the same way. It is true that leaving outrun
is a shorthand way of running scripts, but there is already a built-in npm command fornpm build
(used to build Node add-ons FYI), and that built-in command overshadows thenpm build
shorthand. TL;DR: Trynpm run build
instead.
- Test to see if your hosted project works. Try running node on your server file. If, for example, your server file is called "index.js" and it is inside a folder called "server", run
node server/index.js
. - Now enter your IP address (the one Digital Ocean gave you) in the browser URL bar, followed by a
:
and the port your server is running on (e.g.,127.48.12.123:3100
). Your hosted site should appear. You are almost done! Currently, your site is running but will stop as soon as you close the command line window or otherwise terminate the running Node process. - Once you've tested your site, use Ctrl+C to terminate the Node process. To keep your app running forever, move on to the final required step, in which you will install something called
pm2
.
- You might run Node and find that your app's front end does not appear. Perhaps you see the words
Cannot GET
in the browser. Try testing one of your GET endpoints to see if the back end works by typing the endpoint URL into the browser bar (e.g., '122.48.12.123:3100/api/products'). If the browser correctly displays data from your endpoint, this probably indicates that your project is hosted on the server but your server file is not pointing to your build folder correctly.- Carefully check the express.static line again. It is easy to miss a slash (
/
) or a period (.
). The correct code should probably look like this:app.use( express.static( `${__dirname}/../build` ) );
. Notice the__dirname
variable (with two underscores), followed by a slash, then traveling up one folder and going into thebuild
folder. - You might also double-check that you ran
npm run build
to create a build folder.
- Carefully check the express.static line again. It is easy to miss a slash (
- Use
npm install -g pm2
to install. - Use
cd
to go to the top level of your project file. - Use
pm2 start [path to server file]
to start pm2 (e.g.,pm2 start server/server.js
).
- Once you use the
pm2 start
command, your project should be running and accessible online even after closing the command line window. - If you need to deploy new changes to your project after hosting it, try this cheat sheet.
- Miscellaneous: Sometimes you run pm2 and it fails and you don't know why. It can be helpful to stop pm2 and instead go back to testing the project by running Node, which will often give more useful errors. Running Node also lets you see console logs in your server code, which can help you debug.
Additional pm2 commands
pm2 list
: Shows currently running pm2 processes. Notice how each process has a UID and a PID.pm2 restart [id]
: Restart a specific process, replacing [id] with the process UID or PID.pm2 restart all
: Restart all current processes.pm2 stop all
: Stop all processes.pm2 -h
: Help. Shows other pm2 actions and options.- For more commands, see these Quick Start docs.
When you have multiple files to host, nginx will let you keep them on the same droplet by watching for traffic coming to your droplet and routing that traffic to the appropriate project on the droplet.
nginx installation and configuration
- Run
sudo apt-get install nginx
. - The
nginx/
folder should now be installed in the/etc/
folder. Insidenginx/
, Ubuntu should have installed multiple files and folders, includingsites-available/
andsites-enabled/
. If these two folders are not here, create them inside thenginx/
folder by runningtouch sites-available sites-enabled
. Although the simplest way is to edit the default file that was probably created for you insites-available/
, it may be a better practice to leave the default file alone and instead create and configure a small file for each hosted project site. - After making configuration files in
sites-available
for each project, we will make links to those files in thesites-enabled
folder. nginx uses thissites-enabled/
folder to know which projects should be active.
-
Go to nginx's "sites-available/" folder by running
cd /etc/nginx/sites-available
. -
SIDE NOTE: A default file was probably created automatically in
sites-available/
by the installation. This file has many comments and shows sample configurations for nginx files. If you do not have a default file, below is an example of a default file. It is just a reference. We won't need it for this tutorial, so feel free to move ahead.default file in sites-available/
``` ## # You should look at the following URL's in order to grasp a solid understanding # of Nginx configuration files in order to fully unleash the power of Nginx. # http://wiki.nginx.org/Pitfalls # http://wiki.nginx.org/QuickStart # http://wiki.nginx.org/Configuration # # Generally, you will want to move this file somewhere, and start with a clean # file but keep this around for reference. Or just disable in sites-enabled. # # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. ## # Default server configuration # server { listen 80 default_server; listen [::]:80 default_server; # SSL configuration # # listen 443 ssl default_server; # listen [::]:443 ssl default_server; # # Note: You should disable gzip for SSL traffic. # See: https://bugs.debian.org/773332 # # Read up on ssl_ciphers to ensure a secure configuration. # See: https://bugs.debian.org/765782 # # Self signed certs generated by the ssl-cert package # Don't use them in a production server! # # include snippets/snakeoil.conf; root /var/www/html; # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # include snippets/fastcgi-php.conf; # # # With php7.0-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php7.0-fpm: # fastcgi_pass unix:/run/php/php7.0-fpm.sock; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # Virtual Host configuration for example.com # # You can move that to a different file under sites-available/ and symlink that # to sites-enabled/ to enable it. # #server { # listen 80; # listen [::]:80; # # server_name example.com; # # root /var/www/example.com; # index index.html; # # location / { # try_files $uri $uri/ =404; # } #} ```
-
Inside the
sites-available/
folder, create a file for each project using thetouch
command followed by the name of your project. No file extension is needed for these files. For example, if your project is called "wonderapp", you might typetouch wonderapp
. -
Use
nano
to open each file and put in code in the format below. Notice the comments telling you what changes to make for your project.
server {
listen 80; #80 IS THE USUAL PORT TO USE HERE
server_name your_project.your_domain.com #PUT YOUR DOMAIN HERE
location / {
proxy_pass http://127.0.0.1:3001; #PUT YOUR SERVER PORT HERE
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
- Go to nginx's
sites-enabled/
folder by runningcd /etc/nginx/sites-enabled
. - Inside the
sites-enabled/
folder create a symlink for each project usingln -s ../sites-available/[project_file_in_sites_available]
. For example, if the file you previously made insites-available/
was calledwonderapp
, here you would runln -s ../sites-available/wonderapp
. This creates a symlink between the file that was made insites-available/
and the thesites-enabled/
folder. - Test the nginx configuration by running
sudo nginx -t
. - Restart nginx by running
sudo service nginx restart
. Your enabled sites should now be up and running as soon as you start the server (see the test with Node and pm2 sections below). - If you are wondering how nginx knows to check each of these new files you linked to in
/sites-enabled
, take a look at thenginx.conf
file in thenginx/
folder by runningcat /etc/nginx/nginx.conf
. Near the bottom of the file, you should seeinclude /etc/nginx/sites-enabled/*;
. This line points nginx to each file in/sites-enabled
, so any new file you create there will be included.
- Once you start the server and try going to the site, if you see the "Welcome to nginx" page instead of your site, you may have nginx installed and working but you may need some additional configuration. Double-check the nginx configuration for your project in
sites-available/
and that you have a symlink insites-enabled/
. Double-check also that you tested and restarted nginx usingsudo nginx -t
andsudo service nginx restart
.
forever
- Use
npm install -g forever
to install. - Use
cd
to go to the top level of your project file. - Use
forever start [path to server file]
to start forever (e.g.,forever start server/server.js
).
- Once you use the
forever start
command, your project should be running and accessible online even after closing the command line window. - If you need to deploy new changes to your project after hosting it, try this cheat sheet.
forever list
: Shows currently running forever processes. Notice how each process has a UID and a PID.forever restart [id]
: Restart a specific process, replacing [id] with the process UID or PID.forever restartall
: Restart all current processes.forever stopall
: Stop all processes.forever -h
: Help. Shows other forever actions and options.forever columns [add/rm] [column name]
: Format the list of processes you see when you runforever list
by adding or removing columns using the column name. For instance, you might remove the "command" column or the "logfile" column if you don't find that information useful. You might add the "dir" column because it shows the project folder forever is running in, which is helpful for identifying which project is running which process.-a --uid [custom UID]
options together withforever start
: These options let you set the UID to whatever numbers or characters you want. This can be convenient for both identifying which process is running and for easily remembering the UID so you can stop or restart the process quickly. For example, if you have an app called Foto, you could start forever withforever -a --uid foto start server/server.js
to create a "foto" UID, andforever restart foto
to restart that process.
- -a option: When starting a process, you may see these two errors:
error: Cannot start forever
anderror: log file /root/.forever/[some_process].log exists. Use the -a or --append option to append log
. If so, trying simply adding the-a
option to your previousstart
command. - Miscellaneous: Sometimes you run forever and it fails and you don't know why. It can be helpful to stop forever and instead go back to testing the project by running Node, which will often give more useful errors. Running Node also lets you see console logs in your server code, which can help you debug.
server { listen 80; location / { root } }
Unless you have lots of friends that enjoy accessing websites by ip (You know they exist) You'll want to route your domain to point at your server. This is slightly different for each register. Or you can tell the reigstrar to let Digital Ocean manage your routes. Here is a short description of how to set up Domain records.