Posted by Pete Jeffryes on Apr 5, 2018
In this tutorial, I tackled two major goals:
I have been using the free tier of Heroku to serve up demo apps and create tutorial sandboxes. It’s a great service, easy to use and free, but it does come with a lengthy lag time on initial page load (about 7 seconds). Thats a looooong time by anyone’s standards. With a 7 second load time, according to akamai.com and kissmetrics, more than 25% of users will abandon your page well before your first div even shows up. Rather than simply upgrading to the paid tier of Heroku, I wanted to explore my options and learn some useful skills in the process.
What’s more, I also have a hosted blog on Ghost. It’s an excellent platform, but it’s a bit pricey. Fortunately, they offer their software open source and provide a great tutorial on getting it up and running with Node and MySQL. You simply need somewhere to host it.
By parting ways with my hosted blog and serving up several resources from one server, I can provide a better UX for my personal apps and save a few bucks at the same time. This post organizes some of the best tutorials on the web to get this done quickly and securely.
This requires several different technologies working together to accomplish the goal:
|EC2||provide cheap, reliable cloud computing power|
|Ubuntu||the operating system that handles running our programs|
|Docker||an isolation layer to provide a consistent execution environment|
|Nginx||handle requests in a robust and secure way|
|Certbot||serve up SSL/HTTPS secured web applications, and in turn, increase SSO (search engine optimization)|
|Ghost||provide a simple blog with GUI and persistance|
|React||allow for fast, composable web applications|
After completing this tutorial, you will be able to:
Current Hosted Solutions (No Lag Time)
|Resource||Service||Price / Month||Info|
|Personal Apps||Heroku Hobby||$7/app||https://www.heroku.com/pricing|
Self Hosted Options
|Resource||Service||Price / Month||Info|
|Blog and Apps||AWS EC2 T2 Micro (1GB Memory)||~$10||https://aws.amazon.com/ec2/pricing/on-demand|
|Blog and Apps||Linode (1GB Memory)||$5||https://www.linode.com/pricing|
|Blog and Apps||Digital Ocean (1GB Memory)||$5||https://www.digitalocean.com/pricing|
So with a hosted solution, for one blog and one app, I would be paying $26 per month and that would go up $7/month with each new app. Per year, thats $312 + $84 per additional app. With a little bit of leg work outlined in this post, I am hosting multiple apps and a blog for less than $10/month.
I decided to go with the AWS solution. While it is more expensive, it is a super popular enterprise technology that I want to become more familiar with.
A BIG THANKS to all the folks who authored any of the referenced material. Much of this post consists of links and snippets of resources that proved to work well and includes the slight modifications needed along the way to suite my needs.
Thank you, as well, for reading. Let’s get to it!
Here is how to create a new EC2 instance.
All you really need is the above tutorial to be on your way with setting up an EC2 instance and installing Nginx. I stopped after the EC2 creation since Nginx gets installed during the Ghost blog platform setup.
Further down the road, you are going to point your DNS (domain name system) at your EC2 instance’s public IP address. That means you don’t want it to change for any reason (for example, stopping and starting the instance). There are two ways to accomplish this:
Both options provide a free static IP address. In this tutorial, I went with the Elastic IP to accomplish this goal as it was really straightforward to add to my server after having already set it up.
Follow the steps in the above resource to create an elastic IP address and associate it with your EC2 instance.
I followed this tutorial to the ‘T’…worked like a charm. You’ll set up your own super user with its own SSH key and create a firewall restricting incoming traffic to only allow SSH.
In a minute you’ll open up both HTTP and HTTPS for requests.
I use Name.com for my DNS hosting because they have a decent UI and are local to Denver (where I reside). I already own petej.org and have been pointing it to a github pages hosted static site. I decided to set up a sub-domain for the blog – blog.petej.org – using A records to point to my EC2 instance’s public IP address. I created two A records, one to handle the
www prefix and another to handle the bare URL:
Now via the command line, use the
dig utility to check to see if the new A record is working. This can be done from your local machine or the EC2 instance:
$ dig A blog.petej.org ; <<>> DiG 9.9.7-P3 <<>> A blog.petej.org ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44050 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ;; QUESTION SECTION: ;blog.petej.org. IN A ;; ANSWER SECTION: blog.petej.org. 300 IN A 220.127.116.11 ;; Query time: 76 msec ;; SERVER: 18.104.22.168#53(22.214.171.124) ;; WHEN: Sat Jan 27 10:13:50 MST 2018 ;; MSG SIZE rcvd: 59
Note: The A records take effect nearly instantaneously, but can take up to an hour to resolve any caching from a previous use of this URL. So if you already had your domain name set up and working, this may take a little while.
Nice: domain –> √. Now you need to get your EC2 instance serving up some content!
Another great tutorial. I followed it every step of the way and it was golden. There are some steps that we have already covered above, such as the best practices of setting up an Ubuntu instance, so you can skip those. Be sure to start from the Update Packages section.
Note: Follow this setup exactly in order. My first time around I neglected to set a user for the MySQL database and ended up having to remove Ghost from the machine, reinstall, and start from the beginning.
After stepping through the Ghost install process, you should now have a blog up and running at your domain name - check it out in the browser!
What have you accomplished?
You are now going to:
Install git on the EC2 instance:
$ sudo apt-get install git
Create a new SSH key specifically for GitHub access: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
Because you set up a user for the Ubuntu server earlier, the /root directory and your ~ directory (user’s home directory) are different. To account for that, on the
ssh-add step do this instead:
cp /root/.ssh/id_rsa ~/.ssh/id_rsa cd ~/.ssh ssh-add
$ sudo cat ~/.ssh/id_rsa
Copy the output and add it to GitHub as a new SSH key as detailed in the below link.
Start with step 2 –> https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account
You are all set up to
git. Clone and then push a commit to a repo to make sure everything is wired up correctly.
Once you have your React app running locally with Docker, push the image up to Docker Hub:
You will need a Docker Hub account –> https://hub.docker.com
$ docker login Username: Password:
$ docker tag <image-name> <username>/<image-name>:<tag-name> $ docker push <username>/<image-name>
This will take a while. About 5 min. Coffee break…
And we’re back. Go ahead and log in to GitHub and make sure that your image has been uploaded.
Now back to your EC2 instance. SSH into it.
$ sudo apt install docker.io
Pull down the Docker image locally that you recently pushed up:
$ sudo docker pull <username>/<image-name>
Get the image id and use it to fire up the app:
$ sudo docker images # Copy the image ID $ sudo docker run -d -it -p 5000:5000 <image-id>
Now that you have the React app running, let’s expose it to the world by setting up the Nginx config.
Note: Instead of using /etc/nginx/sites-available/default like the tutorial suggests, I made one specific for the URL (better practice and more flexible going forward) –> circle-grid.petej.org.conf file, leaving the default file completely alone.
We also need to set up a symlink:
$ sudo ln -s /etc/nginx/sites-available/circle-grid.petej.org.conf /etc/nginx/sites-enabled/
Note: Why the symlink? As you can see if you take a look in /etc/nginx/nginx.conf, only the files in the /sites-enabled are being taken into account. The symlink will take care of this for us by representing this file in the sites_available file making it discoverable by Nginx. If you’ve used Apache before you will be familiar with this pattern. You can also remove symlinks just like you would remove a file:
More about ‘symlinks’: http://manpages.ubuntu.com/manpages/xenial/en/man7/symlink.7.html
Now to be sure that Certbot configured a cron job to auto renew your certificates run this command:
$ ls /etc/cron.d/
If there is a certbot file in there, you are good go.
If not, follow these steps:
Test the renewal process manually:
$ sudo certbot renew --dry-run
If that is successful, then:
$ nano /etc/cron.d/certbot
Add this line to the file:
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew
Save it, all done.
You have now configured a task to run every 12 hours that will upgrade any certs that are within 30 days of expiration.
You should now be able to:
I hope this was a helpful collection of links and tutorials to get you off the ground with a personal app server. Feel free to contact me (pete dot topleft at gmail dot com) with any questions or comments.
Thanks for reading.
Join our mailing list to be notified about course updates and new tutorials.