Preview Environments are a powerful way for users and automation to provide rapid feedback on proposed application changes. Whether you know them by the moniker Ephemeral Environments, Review/Release Apps, or Pull Request Apps, they achieve the same end; providing a low overhead, per change environment where your proposed changes can be evaluated by both people and testing automation.

Working with Preview Environments

Developers are constantly making changes, so preview environments must keep pace. To harness the value of preview environments, they should:

  • Be automatically generated and deployed on each relevant commit
  • Notify relevant parties that they exist for human testing
  • Easily work with automation testing tools like mabl for rapid testing insights

Using modern cloud technologies, these objectives can be easily met, as we’ll discuss below.

Deploying Preview Environments in the Cloud

For our example, we’ll use the ubiquitous Single Page Application (SPA) where static HTML and Javascript files render the user facing application in the browser. SPAs are well suited for preview environments because of their compact deployment size, static nature, and the ease with which a human can readily spot defects. In this specific case, we’ll work with a ReactJs SPA.

At the high level, our preview environment has three parts:

  • Hosting static content
  • Hosting dynamic routes
  • Hosting backend APIs

Hosting Static Content

The beauty of SPAs as preview environments is their static nature. Large applications can be compiled down to only a few dozen files, meaning deployment is fast and hosting can be very cost effective. Here we’ll use storage buckets which are a common building block on modern cloud providers, such as AWS S3, Google Cloud Storage, and Azure Blob Storage.

These buckets provide the following useful characteristics:

  • Highly scalable monthly storage for only a few cents per gigabyte
  • Easy to use public hosting access
  • Content Distribution Network functionality to cache/speed up access

Multiple Versions: It’s all in the URL

To access multiple versions of our SPA, we simply place the version into the domain or path.

For example

  • Paths: https://preview.acme.com/petstore/abc1234
  • Sub-domains: https://petstore-abc1234.preview.acme.com

In the first example, the application name is petstore and the version, from the git hash, is abc1234. Because every new commit will have a different commit hash, we can host many versions using this pattern.

In the second example, the application name and version are in the sub-domain name. This can be achieved by using wildcard domain names, so the only DNS entry used is *.preview.acme.com, allowing our servers to handle all requests for preview environments the same way. When the requests hit our server, then we can parse the sub-domain and send back the proper SPA artifacts.

While the path approach is simpler, many applications assume they are running on the domain root (e.g. foo.com/), which can easily be handled by the sub-domain approach. Additionally, some authentication frameworks, like Auth0, don’t work with the path approach, but do work well with sub-domains.

Hosting Dynamic Routes

With React and similar SPA frameworks, the path (or URL) is often dynamic, depending on the browser to properly interpret the path and render the expected page. For example, `https://foo.com/widgets/7` would render the widgets page in your application and display widget number 7, but regardless of the URL requested there is only a single page in this application, index.html.

We must make sure that regardless of the URL requested from the hosting provider, index.html is always the webpage served. For this we will use URL rewriting, where the hosting server will transform the path requested (`/widgets/7`) to index.html, but without doing a redirect, so that the requested path remains the path rendered in the browser, where the SPA will parse the path and render the proper page.

There are a number of ways to handle this path routing, but we’ll use a simple nginx server in the following example code.

Hosting Backend APIs

Usually an SPA frontend will evolve independently from its backend APIs. This means the preview environment does not need to host the APIs, but rather can point many versions of the application at the same API. In this example, we’ll do just that, sharing the same integration environment backend API across frontend preview environments.

Advanced: Hosting Multiple Backend APIs

If you need to test multiple backend APIs, one technique is to publish multiple variants of your preview environment artifacts, each with a configuration pointing to a different set of APIs. For example, you can deploy the same set of artifacts to `/dev/<app-name>/<app-hash>`, `/uat/<app-name>/<app-hash>`, and `/dev/<app-name>/<app-hash>`. Now, by visiting different URLs, you can run your SPA against your developer, UAT, or production APIs for testing purposes.

Deploying on Google Cloud Platform

To deploy on Google Cloud Platform, we’ll use the following services:

Data will flow through our preview environments according to the following diagram.

  1. Cloud Run automatically maps DNS, load balancing, and running your nginx container
  2. The Cloud Run nginx service unpacks the request and serves files from Cloud Storage

Getting Started

We’ll refer to PROJECT_ID as your GCP project identifier.

Create Storage

First create a new Cloud Storage bucket. This can be easily done with the gsutil command line tool, or from the GCP Web Console (see console instructions).

gsutil mb -p <PROJECT_ID> gs://<BUCKET_NAME>

We now have a bucket that can hold our application artifacts.

Setting Up Dynamic Routing

We’ll use Cloud Run to host a simple nginx container that will rewrite routes and send them to the proper part of our Cloud Storage bucket. The following gist will build the necessary Dockerfile.

Note: in the gist, replace “YOUR-BUCKET-NAME-HERE” with your GCS bucket name.

Note: in the gist, replace “preview.acme.com” with your testing domain name.

  1. Customize the nginx configuration from the gist
  2. Build the Docker container and push it to your Docker registry
  3. Deploy the container to Cloud Run
  4. Deploy your preview environment to GCS

To publish your SPA to GCS, simply use gsutil to copy the files over, the flags mean:

  • -m uses parallel upload threads
  • -r uploads all the files in the supplied build directory
  • -Z gzips files
  • -a public-read makes the files publicly accessible

gsutil -m cp -r -Z -a public-read build/ gs://${BUCKET_NAME}/${APP_NAME}/${APP_HASH}/

Where:

  • BUCKET_NAME is your GCS preview environment bucket
  • APP_NAME is the application name - e.g. petstore
  • APP_HASH is the git hash (usually 7 characters) of the commit

Testing It Out

Visiting your preview environment at https://<your cloud run service URL>/${APP_NAME}/${APP_HASH}/ will now load the index.html page of your application.

Deploying on Amazon Web Services

To deploy on Amazon Web Services, we’ll use the following services:

Data will flow through our preview environments according to the following diagram.

  1. Requests are sent to CloudFront (optional vanity domain routing)
  2. CloudFront checks with Lamda to learn proper route to use for request
  3. Request is forwarded to artifacts in S3 bucket

Create Storage

First create a new S3 bucket. This can be easily done with the aws command line tool, or from the AWS Web Console (see console instructions).

Setup Routing

We’ll use CloudFront plus Lamda@Edge to handle transforming URLs and sending them to the proper location in your S3 bucket.

While we could discuss the details of this setup here, Ed Knowles has done an excellent job in this blog post, going into excellent detail, so we’ll refer you to that excellent description.

Conclusions

Congratulations, you now have highly scalable preview environments! However, you’re not done yet. Now you need to make the best use of your preview environments. You can harness this powerful resource by:

  • Including preview environment links in your GitHub/GitLab Pull/Merge Requests
  • Using preview environments with testing automation like mabl in your CI/CD pipelines for testing insights on every commit
  • Encouraging code reviews and testers to review changes with their eyes and mice by seeing the proposed changes as real deployed applications they can interact with.