惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News | PayPal Newsroom
Application and Cybersecurity Blog
Application and Cybersecurity Blog
O
OpenAI News
S
Security @ Cisco Blogs
宝玉的分享
宝玉的分享
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Latest news
Latest news
NISL@THU
NISL@THU
S
Security Affairs
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
AI
AI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
小众软件
小众软件
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
AWS News Blog
AWS News Blog
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
I
InfoQ
Schneier on Security
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
IT之家
IT之家
T
Threatpost
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
L
LINUX DO - 最新话题
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
H
Heimdal Security Blog
S
SegmentFault 最新的问题

Maxime Heckel's Blog

On Rendering the Sky, Sunsets, and Planets - The Blog of Maxime Heckel Shades of Halftone - The Blog of Maxime Heckel Field Guide to TSL and WebGPU - The Blog of Maxime Heckel On Shaping Light: Real-Time Volumetric Lighting with Post-Processing and Raymarching for the Web - The Blog of Maxime Heckel Speaking at Figma Config 2025 - The Blog of Maxime Heckel Post-Processing Shaders as a Creative Medium - The Blog of Maxime Heckel On Crafting Painterly Shaders - The Blog of Maxime Heckel The Art of Dithering and Retro Shading for the Web - The Blog of Maxime Heckel Moebius-style post-processing and other stylized shaders - The Blog of Maxime Heckel Shining a light on Caustics with Shaders and React Three Fiber - The Blog of Maxime Heckel Real-time dreamy Cloudscapes with Volumetric Raymarching - The Blog of Maxime Heckel Painting with Math: A Gentle Study of Raymarching - The Blog of Maxime Heckel Building a magical AI-powered semantic search from scratch - The Blog of Maxime Heckel Beautiful and mind-bending effects with WebGL Render Targets - The Blog of Maxime Heckel Refraction, dispersion, and other shader light effects - The Blog of Maxime Heckel The magical world of Particles with React Three Fiber and Shaders - The Blog of Maxime Heckel The Study of Shaders with React Three Fiber - The Blog of Maxime Heckel Building a Design System from scratch - The Blog of Maxime Heckel Everything about Framer Motion layout animations - The Blog of Maxime Heckel Building a Vaporwave scene with Three.js - The Blog of Maxime Heckel Cubic Bézier: from math to motion - The Blog of Maxime Heckel First steps with GPT-3 for frontend developers - The Blog of Maxime Heckel Building the perfect GitHub CI workflow for your frontend team - The Blog of Maxime Heckel Migrating to Next.js - The Blog of Maxime Heckel Static Tweets with MDX and Next.js - The Blog of Maxime Heckel Advanced animation patterns with Framer Motion - The Blog of Maxime Heckel Scrollspy demystified - The Blog of Maxime Heckel The Power of Composition with CSS Variables - The Blog of Maxime Heckel My first failed SwiftUI project - The Blog of Maxime Heckel Guide to creating animations that spark joy with Framer Motion - The Blog of Maxime Heckel Using Shortcuts and serverless to build a personal Apple Health API - The Blog of Maxime Heckel SEO mistakes I've made and how I fixed them - The Blog of Maxime Heckel Going native: SwiftUI from the perspective of a React developer - The Blog of Maxime Heckel The little guide to CI/CD for frontend developers - The Blog of Maxime Heckel Immigrating to the US - The Blog of Maxime Heckel The physics behind spring animations - The Blog of Maxime Heckel Generate screenshots of your code with a serverless function - The Blog of Maxime Heckel How to use Framer Motion with Emotion styled-components - The Blog of Maxime Heckel Data Fetching with NextJS: What I learned - The Blog of Maxime Heckel Learning in public - The Blog of Maxime Heckel Fixing the dark mode flash issue on server rendered websites - The Blog of Maxime Heckel How to fix NPM link duplicate dependencies issues - The Blog of Maxime Heckel Running scheduled cross-browser end-to-end tests on Github CI - The Blog of Maxime Heckel How I built my first custom ESLint rule - The Blog of Maxime Heckel React Lazy: a take on preloading views - The Blog of Maxime Heckel Automated UI accessibility testing with Cypress - The Blog of Maxime Heckel Building a GraphQL wrapper for the Docker API - The Blog of Maxime Heckel Switching off the lights - Adding dark mode to your React app - The Blog of Maxime Heckel Getting started with Typescript on Gatsby - The Blog of Maxime Heckel Rebuilding Redux with Hooks and Context - The Blog of Maxime Heckel Asynchronous rendering with React - The Blog of Maxime Heckel Using Flow generics to type generic React components - The Blog of Maxime Heckel How to efficiently type your styled-components with Flow - The Blog of Maxime Heckel How I got started with Kubernetes on GKE - The Blog of Maxime Heckel React sub-components Part 3: Whitelisting sub-components with flow - The Blog of Maxime Heckel React sub-components Part 2: Using the new Context API - The Blog of Maxime Heckel React sub-components - The Blog of Maxime Heckel Running Golang tests with Jest - The Blog of Maxime Heckel No title No title
Build your own preview deployment service - The Blog of Maxime Heckel
Maxime Heckel · 2020-08-25 · via Maxime Heckel's Blog

Preview deployments are an essential step in the CI/CD pipelines of many frontend teams. The ability to preview every frontend change in a hosted and self-contained environment can increase the development velocity of a team quite significantly. Moreover, it brings more confidence that any newly added change will not bring any undesirable effect that would not get caught by automated tests before getting merged to production.

I wanted to bring this kind of service to my team at work, however, using one of the already available platforms that provided preview deployments out of the box such as Netlify, Vercel or Serverless was not an option. All our services and deployments were either managed on Google Cloud or Firebase. Thus, if we wanted a preview deployment service, we would have to build it on Google's Cloud platform.

Luckily, Google provides a great serverless service called Cloud Run. Cloud Run enables teams to deploy containers to production in a matter of seconds! Thus, I chose it as the service where the preview deployments would live, then built an automated pipeline around it that would deploy any change made to an app on every pull request and returned a URL to access that new version of that same app. In this article, we will go through each step to implement such an automated pipeline and build your own preview deployment service on Google Cloud Run as I did 🚀.

The perfect preview deployments developer experience

You might have seen this "preview deployments" feature in many other tools or SaaS out there, but I wanted to put together the list of elements that compose a great preview deployments developer experience before deep diving in my implementation. I used this list as my "north star" when building the automated pipeline and looking for how to host my previews, and the following are the key items I took into account:

  • automated: whether it's on every push or every pull request event, the developer should not need to execute any command manually to make the preview operational.

  • easily accessible: once deployed, your preview deployment should have a unique link that allows anyone to access that specific version of your frontend app.

  • fast: the whole process of getting your app deployed should last no more than a couple of minutes

  • self-actualized: each new change on the same branch or pull request should be deployed (preferably) on top of the other one

  • running in a consistent environment: each preview should run in the same replicated environment

Considering these points, I knew I'd have to use Docker containers from the get-go to deploy my previews:

  • their portability ensure that the environment of the preview deployments is constant.

  • having one image per PR is easy: I could build the image and tag it with the number of the pull request. Each new change would be built and tagged with that same number, thus ensuring the image always contains the most up to date version of the UI for that PR.

Thus, the first steps of our preview deployments pipeline would consist of:

  1. Building our UI

  2. Building a Docker image

  3. Tagging our Docker image with the PR number

To help you get started here's one of the Dockerfile I always go back to build my frontend projects. It uses multistage builds and the image it outputs is very small:

Dockerfile sample to build and run an app in a containerized environment

1

FROM node:12.18.3 as build

3

COPY package.json yarn.lock ./

8

FROM node:12.18.3-stretch-slim

9

COPY --from=build /usr/src/app/build /app

10

RUN yarn global add serve

13

CMD ["serve", "-p", "3000", "-s", "."]

Deploying and tagging services on Google Cloud Run

Considering the elements we listed in the previous part for the perfect preview deployments experience, it seemed that leveraging a serverless solution like Google Cloud Run is a great fit to deploy and run previews:

  • it's cheap to run the different revisions of the app: you only pay for the traffic on the revisions

  • each revision can have its own URL to be accessible: by tagging revisions, you can associate a tag to a revision which generates a unique URL for that revision

  • it's fast: it only takes a few seconds to deploy services and revisions

  • it's scalable: you can spin up to 1000 revisions per service! Once you reach that number, the oldest revisions will be simply removed from your service. Thus, no need to worry about taking down our revisions once we merge our pull request.

We will now look into each of the steps necessary to deploy a service, a revision of a service, and how to tag a revision on Google Cloud Run. The commands that will be listed in this section will eventually make it into a Github Workflow that we will detail in the next part.

Push the image to Google Cloud Registry (GCR)

First, we need to push the Docker image of our app that we built in the previous part:

1

docker push gcr.io/PROJECTID/IMAGENAME:TAG

Replace PROJECTID with your project ID,  IMAGENAME with the name of the image you built, and TAG with the tag of that image (the tag will matter the most in the next part that focuses on automating these steps)

Deploy a service on Cloud Run

Running the following command will allow us to deploy the Docker image we just pushed to GCR as a container on Cloud Run:

1

gcloud beta run deploy "myapp" --image "gcr.io/PROJECTID/IMAGENAME:TAG" --platform managed --port=3000 --region=us-east1

myapp will be the name of your service on Cloud Run, you can replace it with whatever name you want --port 3000 allows exposing the port 3000, you can replace it with whatever port your app uses

Our service is now deployed 🚀! We now have a URL for our service. Now let's look at the commands to deploy and tag a revision.

Deploy and tag a revision

Let's run the following command to deploy a revision for our services (remember to replace the name, project ID, image name, and tag with yours!)

1

gcloud beta run deploy "myapp" --image "gcr.io/PROJECTID/IMAGENAME:TAG" --platform managed --revision-suffix=revision1 --port=3000 --region=us-east1

We now have a new revision for our service! This new revision uses the same Docker image and tag as our service. Eventually, we would want to deploy different versions of our app for each revision, which will result in each revision containing a change. We'll see in the next section how we can leverage Pull Request numbers and commit hashes to do that :smile.

One of the key element of the pipeline is tagging revisions: tagging a revision will let us have a unique URL for that revision.

If we have a service URL like https://myapp-abcdef123-ab.a.run.app, tagging it with "test" would give us the URL https://test---myapp-abcdef123-ab.a.run.app. To tag a revision we can run the following command:

1

gcloud beta run beta update-traffic "myapp" --update-tags test=revision1 --platform=managed --region=us-east1

We now have all the key commands to deploy a service and a revision on Cloud Run and get back a unique URL for each revision! The next step is my personal favorite: automation.

Automating the deployments

In this part, we will create a Github workflow to execute the commands we just looked at on every Pull Request event.

The Github Workflow we're about to build is based on the Google Cloud Platform Github Actions, and more particularly we will implement a workflow that is similar to their Cloud Run Github Workflow example. I invite you to follow the README in this repository before continuing, it details how to:

  • Create a service account

  • Set up the service account's key and name as secrets of your project's Github repository.

I will use the same secret labels that they use in their workflow to make things easier for you to follow 😊.

In this part, we'll use a service account as the account that will run our commnands in the automated pipeline. This type of account is more suited than user accounts for that kind of tasks.

Considering that we now have a service account created and its key and name set as a secret of our Github repository, let's look at each step of the workflow on their own before looking at the entire pipeline:

  • First, we have to set up our workflow to **run on every pull requests **against our main branch:

1

name: Preview Deployment

  • Run the checkout action and setup node action:

3

- name: Checkout Commit

4

uses: actions/checkout@v2

6

ref: ${{ github.event.pull_request.head.sha }}

7

- name: Use Node.js ${{ matrix.node-version }}

8

uses: actions/setup-node@v1

10

node-version: ${{ matrix.node-version }}

  • Then we need to install and configure the GCloud SDK and beta components using our service account name and secret key:

2

- name: Setup Google Cloud SDK

3

uses: GoogleCloudPlatform/github-actions/setup-gcloud@master

5

project_id: ${{ secrets.PROJECTID }}

6

service_account_key: ${{ secrets.RUN_SA_KEY }}

7

export_default_credentials: true

8

- name: Install Google Cloud SDK Beta Components

9

run: gcloud components install beta

  • Let's not forget to configure Docker, as we showed earlier, to be able to push on GCR

2

- name: Setup Docker for GCR

3

run: gcloud auth configure-docker

  • Build and Push our Docker image using the PR number as a tag:

2

- name: Build Docker Image

3

run: docker build -t gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}

4

- name: Push Docker Image To GCR

5

run: docker push gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}

  • Get the commit hash of the HEAD commit of this PR. This is necessary because every revision suffix must be unique, and commit hashes are very handy to generate unique strings 😊:

2

- name: Get HEAD Commit Hash

4

run: echo "::set-output name=hash::$(git rev-parse --short HEAD)"

  • **Deploy a new revision on Cloud Run. **

2

- name: Deploy Revision On Cloud Run

3

run: gcloud beta run deploy "myapp" --image "gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}" --no-traffic --platform managed --revision-suffix=${{github.event.number}}-${{steps.commit.outputs.hash}} --port=3000 --region=us-east1

  • Tag the revision:

2

- name: Tag Revision On Cloud Run

3

run: gcloud beta run services update-traffic "myapp" --update-tags pr-${{github.event.number}}=myapp-${{github.event.number}}-${{steps.commit.outputs.hash}} --platform=managed --region=us-east1

  • Post the comment on the PR containing the URL! This will let your reviewers know how to access the revision that's just been deployed. I used the add-pr-comment Github Action. You could use any other action or even build your own (!), as long as you can pass your revision URL as an argument:

2

- name: Post PR comment with preview deployment URL

3

uses: mshick/add-pr-comment@v1

5

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

8

Successfully deployed preview revision at https://pr-${{github.event.number}}---myapp-abcdef123-ab.a.run.app

Here's how the full workflow file looks like:

Preview Deployment Github Workflow

1

name: Preview Deployment

15

- name: Checkout Commit

16

uses: actions/checkout@v2

18

ref: ${{ github.event.pull_request.head.sha }}

19

- name: Use Node.js ${{ matrix.node-version }}

20

uses: actions/setup-node@v1

22

node-version: ${{ matrix.node-version }}

23

- name: Setup Google Cloud SDK

24

uses: GoogleCloudPlatform/github-actions/setup-gcloud@master

26

project_id: ${{ secrets.PROJECTID }}

27

service_account_key: ${{ secrets.RUN_SA_KEY }}

28

export_default_credentials: true

29

- name: Install Google Cloud SDK Beta Components

30

run: gcloud components install beta

31

- name: Setup Docker for GCR

32

run: gcloud auth configure-docker

33

- name: Build Docker Image

34

run: docker build -t gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}

35

- name: Push Docker Image To GCR

36

run: docker push gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}

37

- name: Get HEAD Commit Hash

39

run: echo "::set-output name=hash::$(git rev-parse --short HEAD)"

40

- name: Deploy Revision On Cloud Run

41

run: gcloud beta run deploy "myapp" --image "gcr.io/${secrets.PROJECTID}/IMAGENAME:${{github.event.number}}" --no-traffic --platform managed --revision-suffix=${{github.event.number}}-${{steps.commit.outputs.hash}} --port=3000 --region=us-east1

42

- name: Tag Revision On Cloud Run

43

run: gcloud beta run services update-traffic "myapp" --update-tags pr-${{github.event.number}}=myapp-${{github.event.number}}-${{steps.commit.outputs.hash}} --platform=managed --region=us-east1

44

- name: Post PR comment with preview deployment URL

45

uses: mshick/add-pr-comment@v1

47

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

50

Successfully deployed preview revision at https://pr-${{github.event.number}}---myapp-abcdef123-ab.a.run.app

We now have a fully functional preview deployment workflow! Now let's circle back to the first part and go through the checklist to see whether or not this automated preview deployment pipeline covers all the criteria we established:

  • automated: thanks to the workflow we just detailed above, our preview deployment service will automatically deploy our app on every PR against the main branch

  • easily accessible: the last step of our workflow covers that as it will post the URL of a given deployment as a PR comment.

  • fast: it only takes a few minutes to build and ship our app! Additionally, we leveraged multi-stage build to make the Docker Image of our app lighter which speeds up a bit the workflow when it comes to pushing the image to GCR.

  • self-actualized: for every new commit, the workflow will be executed, plus, thanks to the way we tag our revisions, the URL will remain constant through changes for a given PR!

  • running in a consistent environment: each revision is built following the same recipe: the Dockerfile we introduced in the second part!

I had a lot of fun building this (I'm a big fan of automation!) and I hope you liked this post and that it will inspire you to build more automation for your team to make it ship amazing things even faster 🚀! If you and your team are also in the process of establishing other elements of a CI/CD pipeline on top of the one we just saw, I'd recommend checking out The little guide to CI/CD for frontend developers that summarizes everything I know about CI/CD that can make team unstoppable!