Deploys a system made up of:
- A web application comprised of cuplfrontend and cuplbackend.
- cuplTag hardware and firmware.
DNAME is short for DEPLOYMENT NAME d3
or latest
Scanning cuplTag with an NFC phone causes the frontend web application to open.
The application is hosted at a domain, for example: latest.f.cupl.uk and registered to the Amazon Route53 DNS server.
An A Record routes traffic to a Amazon CloudFront distribution. CloudFront is a Content Delivery Network, which stores copies of files from a given origin at edge locations worldwide.
- The files are a production optimized build of the cuplfrontend static web application.
- The origin is an Amazon S3 Bucket, a pay-for-what-you-use web folder with near infinite capacity.
CloudFront reduces latency in file access, compared to just hosting from S3. It is also easy to add an Amazon-provided SSL certificate, by requesting one in Amazon Certificate Manager. The frontend should be served over HTTPS in a production environment.
The frontend web application provides a Graphical User Interface to cuplbackend. Data are stored and retrieved via calls to its web-based API.
The backend web application is hosted at a domain name, for example: latest.b.cupl.uk.
The domain is registered with the DigitalOcean DNS server. An A record routes traffic to the IP address of a DigitalOcean Droplet, which is a virtual machine running Linux.
Services are packaged into Docker containers. This is done for isolation: one service is not able to interfere with another. Each sees its own Linux installation and has to install its own dependencies, thereby avoiding dependency hell.
The 3 services run on a single Docker instance using Docker Compose. Each is defined in docker-compose.yml:
- Nginx-certbot runs the web server, Nginx. This acts as a reverse proxy. It rewrites requests received over TCP/IP into the WSGI protocol, which is standard for Python web applications.
- The cuplbackend web application. It is built atop of the Flask framework. The application exposes two HTTPS APIs. The interface is text only: data are read and written as JSON. Data are persisted in an external PostgreSQL database.
- A Redis instance. A cuplbackend dependency named Flask-Limiter uses this to record and block API requests.
This tutorial demonstrates how to run the cupl web application, which consists of a frontend and a backend. This will be set up on your own infrastructure, accessible from a ROOT_DOMAIN
of your choice.
The frontend and backend are hosted on separate subdomains.
Multiple instances, or deployments, of the web application can share a ROOT_DOMAIN
. These can run different application versions.
Each deployment has a DEPLOY_NAME
:
- A test deployment (required).
DEPLOY_NAME = latest
- Production deployment (optional). These are named
dX
where X is any integer. exampleDEPLOY_NAME = d3
Frontend | Backend | |
---|---|---|
Functionality | View cuplTag data. Plot samples in charts and export CSV files. | Stores / retrieves cuplTag samples and metadata from a database. |
Interface | Graphical (screenshots) | Text-based API (docs) |
Subdomain schema | DEPLOY_NAME.f.ROOT_DOMAIN |
DEPLOY_NAME.b.ROOT_DOMAIN |
Subdomain example | https://latest.f.cupl.uk | https://latest.b.cupl.uk |
Protocol | HTTPS | HTTPS |
Host | AWS Cloudfront CDN | DigitalOcean Droplet |
- A GitHub account.
- A DigitalOcean account.
- An AWS account.
This part of the tutorial is derived from an article by Daniel Wachtel.
- Sign into DigitalOcean.
- Select Droplets from the left menu.
- Select Create Droplet.
- Select a datacenter (example San Francisco 3).
- Keep the defaults (screenshot):
- Ubuntu 20.04 (LTS) x64.
- Basic Shared CPU.
- Regular Intel with 2GB of RAM.
- The monitoring add-on is recommended.
- Under Authentication select SSH keys.
- Select New SSH Key
- The Add public SSH key popup will open.
- Open a Bash shell on your local machine.
- Run
cd ~/.ssh
- Run
ssh-keygen
. - Name your keypair
DEPLOY_NAME-ROOT_DOMAIN-root
(examplelatest-lpuc-root
). - Enter a passphrase.
- Press ENTER and the key pair (a public and private key) will be generated.
- Run
cat DEPLOY_NAME-ROOT_DOMAIN-root.pub
(examplecat latest-lpuc-root.pub
) to display the public key. - Copy the public key.
- Paste it into the SSH key content box, which is in the Add public SSH key popup.
- Enter
DEPLOY_NAME-ROOT_DOMAIN-root
(examplelatest-lpuc-root
) as the Name. - Click Add SSH Key.
Recommendation: store the private key and the passphrase in a secure location, such as a password manager. If you lose these credentials, you will lose root access to your droplet, which is needed to install security updates. The private key can be seen by entering cat DEPLOY_NAME-ROOT_DOMAIN-root
without the .pub suffix.
- Under Choose a hostname enter
cupldeploy-DEPLOY_NAME-ROOT_DOMAIN
(examplecupldeploy-latest-lpuc
). - Select Create Droplet.
- Wait for the Droplet to be created.
- Note the IPV4 address of your droplet in the top-left corner as
BACKEND_DROPLET_IPV4
(highlighted in yellow).
NGINX is installed here for test purposes and removed later. The test web page Welcome to NGINX! shows your droplet (and later your DNS) is running ok.
The following steps assume the droplet private key is located in the ~/.ssh
folder on your local machine.
- Open a Bash shell.
- Type
ssh root@BACKEND_DROPLET_IPV4
(examplessh root@147.182.201.13
) - Enter the private key passphrase.
- You will be logged into the droplet as root.
- Run
apt-get install nginx
- Open a web browser.
- Enter
http://BACKEND_DROPLET_IPV4
in the address bar. You must use HTTP, because HTTPS has not been set up yet. - Expect to see the nginx test page (screenshot).
-
Sign into Amazon Route 53 using your AWS account.
-
In the Register domain section, find and buy your choice of
ROOT_DOMAIN
. -
Wait for domain name registration to complete.
The backend web application should be accessible from a domain name and not just an IP address.
This will be referred to as: LATEST_DROPLET_HOST = latest.b.ROOT_DOMAIN
(example latest.b.lpuc.uk
)
An A record points LATEST_DROPLET_HOST
to your web server at BACKEND_DROPLET_IPV4
.
-
Sign into Amazon Route 53 using your AWS account.
-
Select Hosted Zones.
-
Select the
ROOT_DOMAIN
you created in the previous step. This hosted zone was created automatically in the previous step. -
You will see 2 records: NS and SOA. Select Create Record in the top left.
-
Under Record name enter
latest.b
-
Under Value enter
BACKEND_DROPLET_IPV4
(example 147.182.201.13). -
Change TTL (seconds) to 60.
-
Select Create records.
-
The newly created A record will show in the list. Wait at least 60 seconds for the DNS change to propagate.
- Open a web browser.
- Enter
http://LATEST_DROPLET_HOST
in the address bar. You must use HTTP, because HTTPS has not been set up yet. - Expect to see the test page (screenshot).
NGINX is no longer needed, so uninstall it from your droplet. Instructions are similar to those used for installation.
- Open a Bash shell on your local machine.
- Type
ssh root@BACKEND_DROPLET_IPV4
(examplessh root@147.182.201.13
) - Enter the private key passphrase.
- You will be logged into the droplet as root.
- Run
apt-get remove nginx nginx-common
, thenY
to confirm. - The server still runs after removal. Run
killall nginx
to stop it.
Whilst still connected to the droplet as root
, install the latest version of docker using the repository.
The GitHub Actions Runner uses the Docker Contexts feature to deploy containers onto the (remote) droplet.
Both the runner and droplet must run the same version of docker-compose. A mismatch can cause container deployments to fail.
As of writing, cupldeploy supports docker-compose version 1.27.4. To install this on your droplet, run the following as root
:
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
When finished, run docker-compose -v
.
Expect to see:
The cupldeploy GitHub Actions script signs into the droplet cupldeploy-latest-lpuc
as a non-root user: LATEST_DROPLET_USERNAME
.
In this tutorial, LATEST_DROPLET_USERNAME = deployer
.
- Open an SSH connection to your droplet as
root
(if this is not already open). - Run
adduser deployer
. - Set a long password of your choice.
- Remove the need to be
sudo
in order to run docker (source)- Run
sudo groupadd docker
. - Run
sudo usermod -aG docker deployer
.
- Run
This should be done on your local machine. Do not create the key pair on the droplet.
- Open a Bash shell on your local machine.
- Run
ssh-keygen
to create an SSH keypair for the deployer user. - Name your keypair
DEPLOY_NAME-ROOT_DOMAIN-deployer
(examplelatest-lpuc-deployer
). - Do not create a passphrase for this key pair.
The script below is thanks to Michael Wyraz. Modify it according to your needs and run on your local machine. It:
- Makes an SSH connection from your local machine to the droplet as
root
. - Copies the SSH public key file (stored locally as
latest-lpuc-deployer.pub
) into theauthorized_keys
file for the droplet userdeployer
.
cat ~/.ssh/latest-lpuc-deployer.pub | ssh root@latest.b.lpuc.uk "sudo mkdir /home/deployer/.ssh; sudo tee -a /home/deployer/.ssh/authorized_keys"
View the private key by entering cat DEPLOY_NAME-ROOT_DOMAIN-deployer
(example latest-lpuc-deployer
).
From now on, this will be known as LATEST_DROPLET_SSH_PRIVATE_KEY
. Save it to your password manager.
This step verifies that you can use SSH to connect to the backend droplet as deployer
.
- Open a Bash shell on your local machine.
- Run
ssh deployer@LATEST_DROPLET_HOST
(examplessh deployer@latest.b.lpuc.uk
). - Expect the SSH connection to open successfully (screenshot).
cuplbackend stores data in a Postgres 11 database. The connection is set by the following GitHub Secrets:
DB_USER
Database username.
DB_PASS
Database password.
DB_HOST
Database host name or IP address.
DB_PORT
Database port number.
LATEST_DB_NAME
Database name.
These are used by config.py to construct a connection string in the format
postgresql://DB_USER:DB_PASS@DB_HOST:DB_PORT/LATEST_DB_NAME
Any database provider can be used, but a managed solution (with automatic backups) is recommended for production.
There are benefits to co-locating your database server with the backend application server (droplet):
- Reduced latency.
- Improved security. The DB server can communicate over a private network (VPC). There is no need to expose it to the internet.
In this tutorial, we will set up a DigitalOcean managed database in the same datacenter that hosts our droplet.
- Sign into DigitalOcean.
- Select Databases from the left menu.
- Select Create a Database Cluster.
- Under Choose a database engine select PostgreSQL version 11.
- The default cluster configuration will work.
- Select the same datacenter that your droplet is in (example San Francisco 3). (screenshot)
- Under Choose a unique database cluster name enter
db-postgresql-DATACENTER-ROOT_DOMAIN
(exampledb-postgresql-sfo3-lpuc
). - Select the green Create a Database Cluster button.
- You will enter the getting started step-by-step, whilst the database is provisioned. Click Get Started.
- Under Add trusted sources enter the name of your droplet. (screenshot)
- Select Allow these inbound sources only.
- Select Continue, I'll do this later followed by Great, I'm done.
- From the top menu, select Users & Databases.
- Under Add new user enter
deployer
followed by Save. - Under Add new database enter
latest-lpuc-db
followed by Save. An empty database will be created. (screenshot) - Finally we will note our database credentials. From the top menu, select Overview.
- Under Connection Details select VPC network.
- Under User select
deployer
. - Under Database/Pool select
latest-lpuc-db
. (screenshot) - Record the following:
-
Navigate to https://github.com/cuplsensor/cupldeploy.
-
Sign into GitHub.
-
The forked repository will open.
GitHub Secrets are a means of providing sensitive strings to Actions scripts. Similar to environment variables, these must be defined for your forked repository for Actions to run successfully. Secrets ensure that sensitive data are not stored in the GitHub repository itself.
-
Select Settings from the top menu.
-
Select Secrets from the left submenu.
-
Select the New repository secret button.
-
Under Name enter
DB_HOST
. -
Under Value enter the database host name recorded earlier.
-
Repeat steps 3 to 6 until all of the following are defined:
Name | Value |
---|---|
DB_USER |
link |
DB_PASS |
link |
DB_HOST |
link |
DB_PORT |
link |
LATEST_DB_NAME |
link |
DB_SSLMODE |
link |
LATEST_DROPLET_USERNAME |
link |
LATEST_DROPLET_SSH_PRIVATE_KEY |
link |
LATEST_DROPLET_HOST |
link |
LATEST_ADMINAPI_CLIENTSECRET |
*** random string *** |
LATEST_TAGTOKEN_CLIENTSECRET |
*** random string *** |
LATEST_HASHIDS_SALT |
*** random string *** |
LATEST_CSRF_SESSION_KEY |
*** random string *** |
LATEST_SECRET_KEY |
*** random string *** |
CERTBOT_EMAIL |
Your email address |
Recommendation: Save all secrets in a password manager. These will not be readable again on GitHub.
Recommendation: Random strings should be at least 20 characters in length, mixed case and contain letters, symbols and numbers.
- Select Actions from the top menu.
- If this is the first time, you will see a warning message. When you are satisfied, select I understand my workflows, go ahead and enable them.
- Select the workflow CI from the left menu.
- Select Run workflow
- Expect to see 2 out of the 3 tasks pass with a green tick. Refer to troubleshooting if the backend deployment task 'Use docker-compose over SSH to run cuplbackend...' fails. It is normal for the frontend deployment 'Build react app and copy to S3' to fail at this stage.
If either of these tests fail, see troubleshooting.
-
In a web browser, navigate to
latest.b.ROOT_DOMAIN
(examplelatest.b.lpuc.uk
). Expect to see: -
Click on the Consumer API Root link. Expect to see the page below. The consumer API is being served.
This part of the installation guide is based on an article by Andrew Bestbier.
- Sign into Amazon S3.
- Select Create bucket. (screenshot)
- Enter a unique bucket name. This will be referred to as
AWS_S3_BUCKET
(examplelpuc-frontend-bucket
). - Select an AWS Region where the bucket will be created. You can leave the default. This will be referred to as
AWS_S3_REGION
(exampleus-east-2
). - Uncheck block all public access. The web application will be hosted from here, so public access is required.
- Check **I acknowledge that the current settings... **
- Select Create bucket.
- Select your
AWS_S3_BUCKET
. (screenshot) - Select the Properties tab.
- Scroll down to Static website hosting and enable it.
- Set the Index document to
index.html
. - Set the Error document to
index.html
. - Select Save changes.
- Your bucket will be assigned a URL.
When you visit the URL, you will see an error message 403 Forbidden
. There is a problem with permissions.
-
Return to the bucket settings and select the Permissions tab.
-
Scroll down the Bucket policy section. Select Edit.
-
Paste the following into the Policy text box, substituting
lpuc-frontend-bucket
with yourAWS_S3_BUCKET
name.{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::lpuc-frontend-bucket/*" } ] }
-
Select Save changes.
-
Navigate to the bucket URL again and expect to see
404 Not Found
. The bucket is empty for now.
- Sign into AWS Identity and Access Management.
- Select Users from the left-hand menu.
- Select Add users.
- Enter a User name (example
lpuc-aws-user
). - Check Programmatic access.
- Select Next: Permissions.
- Select Attach existing policies directly.
- Search for and check
AmazonS3FullAccess
. - Search for and check
CloudFrontFullAccess
. - Select Next: Tags.
- Select Next: Review (screenshot)
- Select Create user.
- On the next screen you are given credentials for the new user. AWS will not show these to you again and you will need them later.
This is needed for cuplfrontend to be served over HTTPS. The next section assumes your ROOT_DOMAIN
was registered with Amazon Route 53.
-
Sign into AWS Certificate Manager.
-
In the top-right corner, change your region to US East (N. Virginia).
-
Under Provision certificates select Get started.
-
Check Request a public certificate and select Request a certificate.
-
Under Domain name enter the domain your frontend application will be served from. This will be in the format
DEPLOY_NAME.f.ROOT_DOMAIN
(examplelatest.f.lpuc.uk
). -
Select Next.
-
Check DNS validation and select Next.
-
Select Review (screenshot).
-
Select Confirm and request.
-
Open the drop down for your domain and select Create record in Route 53.
-
Select Create. A CNAME DNS record will be created in your Route 53 hosted zone. ACM uses this as proof of domain ownership when issuing a certificate.
CloudFront is a Content Delivery Network. It copies files from the S3 bucket to locations around the world. Web application load times are reduced when files are in close proximity to your users. It also provides an easy means of serving the frontend over HTTPS, using the certificate created above.
- Sign into AWS CloudFront.
- Select Create Distribution.
- Under Select a delivery method select Get Started.
- Under Origin Domain Name select your
AWS_S3_BUCKET
from the drop-down. - Under Origin Path enter the
DEPLOY_NAME
preceded by a/
(example/latest
). - Under Restrict Bucket Access check No.
- Under Viewer Protocol Policy select Redirect HTTP to HTTPS.
- Under Alternate Domain Names (CNAMES) enter
DEPLOY_NAME.f.ROOT_DOMAIN
(examplelatest.f.lpuc.uk
). - Under SSL Certificate check Custom SSL Certificate.
- Select your ACM certificate from the drop down list. (screenshot)
- Select Create Distribution in the bottom right.
- The distribution will take several minutes to start. Wait for In Progress to change to Enabled.
- Make a note of the distribution ID (covered in blue above). There is one distribution per deployment. For
DEPLOY_NAME = latest
, the variable will be namedLATEST_AWS_CLOUDFRONT_DIST_ID
. - Also make a note of the domain name.
The 403 error page does not route to the page defined in the frontend web application, unless these steps are followed:
- Go to the distributions list in CloudFront.
- Select your
LATEST_AWS_CLOUDFRONT_DIST_ID
. - Select the Error Pages tab from the top menu.
- Select Create Custom Error Response.
- Under Customize Error Response check Yes.
- Under Response Page Path enter
/index.html
. - Under HTTP response code enter
200
.
- Sign into Amazon Route 53.
- Select Hosted Zones.
- Select your
ROOT_DOMAIN
. - Select Create record in the top right.
- Under Record name enter
latest.f
- Enable the Alias switch.
- From the dropdown menu select Alias to CloudFront Distribution.
- Select your distribution domain name from the dropdown list.
- Change TTL (seconds) to 60.
- Select Create records.
-
Navigate to your fork of cupldeploy on GitHub.
-
Follow the instructions here to add the following secrets:
Name | Value | Required |
---|---|---|
AWS_S3_BUCKET |
link | Yes |
AWS_S3_REGION |
link | Yes |
AWS_ACCESS_KEY_ID |
link | Yes |
AWS_SECRET_ACCESS_KEY |
link | Yes |
LATEST_AWS_CLOUDFRONT_DIST_ID |
link | Yes |
- Follow the instructions here to run the deployment action again.
- Expect to see green ticks everywhere. Both the backend and frontend should deploy successfully.
In a web browser, navigate to latest.f.ROOT_DOMAIN
(example latest.f.lpuc.uk
). Expect to see:
If you do, stop work for the day and open a beer. I'll have a Staropramen, cheers! You now have a graphical frontend to the backend you deployed earlier.