omniux logo
Photo by Tom Fisk: https://www.pexels.com/photo/aerial-photography-of-container-van-lot-3063470/
Technology

Deploying a Payload CMS Instance with Docker & Google Cloud Run

MB

Written by Mark Barton

27th of March, 2023

Introduction

If you've worked with OMNIUX before, you'll know we are obsessed with Payload CMS. Not only does Payload allow us to ship fast and beautiful websites for our clients, but it also gives us the freedom to extend the platform for our client's needs. Payload is Headless, meaning it can be used independently from a front-end framework. It also means we can deploy it to a provider of our choice. At OMNIUX, we like our cloud architecture to be fast, scalable, and cost-effective, so we use Google Cloud as our primary cloud service provider,



Why Google Cloud?

There are a lot of different cloud service providers out there. From Digital Ocean to Amazon Web Services - it can be difficult finding a provider that is right for you. We chose Google Cloud Services due to their generous free allowance, their developer-friendly documentation, and their commitment to running on 100% renewable energy.


Google Cloud Run, the platform we use to deploy our Payload Instances, allows for 2 million free invocations per month. This is more than enough for our use case and, with a single instance, we rarely use up our allocation. This keeps our costs low (around $2-4 a month) with no performance degradation.


Google offers a generous free tier for most of the services we use. If you wish to calculate your potential costs, you can use this price calculator. We have pre-populated it with all of the services used for this tutorial. As you can see, this base line will allow you to deploy on Google Cloud for FREE.

Getting Started

In order to deploy on Google Cloud there are a couple of things you'll need. First, you'll need an account with Google Cloud. Once you've set up an account you'll need to create a new project. This can be called whatever you like. You'll also need a GitHub Repository, ideally one with your PayloadCMS code readily available as this will be used to kickstart the CI/CD Pipeline.


With your Google project all setup, you'll be greeted with a welcome screen with a ton of services. This can all get pretty overwhelming because Google offers lots of individual services (over 70 at the time of writing!), but for now, we're going to focus on 4 services;



We are going to combine these services into a CI/CD pipeline - allowing us to deploy our Payload Apps via a GitHub Repository seamlessly.

Instruction diagram (GCP)



How Does This Work?

We are going to containerize our Payload app using Docker. We do this in Cloud Build, generating a new Docker image and pushing it to Artifact Registry. Artifact Registry stores our latest images in a Cloud Storage Bucket. We retrieve our latest image in Cloud Run when a new container instance is "spun up". The main benefit of Dockerizing our app is that if we decide we want to move to another Cloud Provider (Like AWS) we don't need to write any platform-specific code. Our Payload app will deploy and run just like it does on Google.



Configuring the Build Trigger

Head over to the Cloud Build page. You'll find yourself on the History tab with two options; Run Sample Build and Create Trigger. We're going to press Create Trigger

Google Cloud Platform build page

You'll be taken to the Triggers Page. Triggers are where we control how and when our builds get created. For example, we can trigger our builds every time a new Pull Request is created in our GitHub Project.


You'll again see two options; Create Trigger and Connect Repository. Click Connect Repository and a side panel will open.

Create a New Trigger (GCP)

In the new side panel, we're going to select GitHub (Cloud Build GitHub App) as our source. After selecting this step, proceed to authenticate your GitHub connection and choose your repo from the dropdown.

Connect repo (GCP)

Once you've connected your repo, create your trigger and you'll be taken to the Create Trigger page.


This page is where we set up the stages of our Trigger. You can leave most of the options in their default state (feel free to change your desired build event, such as pushing to a specific branch or opening a Pull Request). Most importantly, however, we want to change our Configuration slightly. Scroll down to the Configuration section and choose Cloud Build configuration file (yaml or json).

Build trigger (GCP)

Also remember to set your Cloud Build configuration file location to /deployment.yaml. Cloud Build will look for your yaml file from the root of your repository. So if your deployment.yaml file is in a folder called "cms" - your file location should say cms/deployment.yaml.



Configuring the Build Agent

We need to configure a special Service Account called a Build Agent in order to build and deploy a project. A Build Agent is an automated service worker that has strict access to resources in Google Cloud. Google creates this account for us, however, we still need to add a few extra permissions in order to allow it access to other services such as Cloud Run.


On the Cloud Build Page, navigate to the Settings Panel. This will bring up a list of Service Account Permissions. Make sure that the Cloud Run, Service Accounts, and Cloud Build options are enabled.

Service account setup (GCP)

Additionally, we need to add an extra permission to the Build Agent in order to allow Logging build steps and errors. Navigate to IAM & Admin and click the edit button next to the cloudbuild.gserviceaccount.com account. Add the Cloud Build Logging Service Account role and then Save your changes.

roles and permissions


Docker Setup

Now that our Trigger has been configured, let's configure the Docker image. Head over to your code and create a file at the same level as your package.json. Call it Dockerfile and paste the following code inside;



FROM node:18.8-alpine as base

FROM base as builder

WORKDIR /home/node/app

COPY ./package*.json ./

COPY . .

RUN yarn install

RUN yarn build

FROM base as runtime

ENV NODE_ENV=production

ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js

WORKDIR /home/node/app

COPY package*.json ./

RUN yarn install --production

COPY --from=builder /home/node/app/dist ./dist

COPY --from=builder /home/node/app/build ./build

EXPOSE 8080

CMD ["node", "dist/server.js"]



If you're a pro at Docker you probably know what's going on. If you're new, however, this might all seem a bit overwhelming. In a nutshell, we are telling Docker to download the necessary packages using Yarn. Then we build the project and set the environment to Production. The build process creates a folder called dist. This folder contains all the files needed to run Payload standalone. With these files created, we move them to a specific folder inside our Docker container and then run the server on port .


This code should be enough to get your app running locally on your machine using Docker. Give it a try and tweak whatever is needed. If you use npm you may want to replace "yarn" with "npm" so as not to mix package managers.



Cloud Build YAML Setup

Lastly, we need to create a new Cloud Build file called "deployment.yaml" at the same level as the DockerFile. This file is what Google Cloud uses in order to build and deploy our app on Google Cloud Run. It is picked up by our build trigger and managed by the build agent.


The Cloud Build file is split into 3 sections;


  • Docker Build
  • Docker Push
  • Google Cloud Deploy

Paste the following code into your deployment.yaml file;


steps:

- name: 'gcr.io/cloud-builders/docker'

dir: '.'

args: [

'build',

'-t',

'us-docker.pkg.dev/$PROJECT_ID/my-repository/cms:$SHORT_SHA',

'-f',

'./Dockerfile',

'.'

]


- name: 'gcr.io/cloud-builders/docker'

args: [

'push',

'us-docker.pkg.dev/$PROJECT_ID/my-repository/cms:$SHORT_SHA'

]


- name: 'gcr.io/cloud-builders/gcloud'

entrypoint: gcloud

args:

- run

- deploy

- cms

- --region=us-central1

- --platform=managed

- --image=us-docker.pkg.dev/$PROJECT_ID/my-repository/cms:$SHORT_SHA

timeout: 1800s


Most of the hard work is done for you by the pre-built Cloud Run tasks. However, you may need to update the image location with your Artifact repository. In order to create one, navigate to Artifact Registry and click Create Repository.

New Artifact Repository (GCP)

Give your Repository a name and select a region (We selected us-central1 (Iowa)). Save your Repository and then update the values in the build.yaml file.


You should not need to change any of the other values used in this file unless you know what you are doing. For instance, the arguments in the first stage tell Google Cloud to use the Build command with some additional Docker arguments such as tag and file location. This allows for more customisation depending on your project's needs.



Putting it All Together

With all of this code added to your project, push your changes to GitHub and watch the project build but ultimately fail. If the project is built but the Run stage fails, don't panic! It's probably caused by the environment variables we added in the Run stage of the Cloud Build file.


The values we passed aren't actually reaching the Cloud Run container yet. So in order to fix this, open Cloud Run in the Google Cloud Console and navigate to your Instance (we called ours CMS) and click Edit & Deploy New Revision. In this new screen, scroll down to Environment Variables and create two new variables based on the ones we added to the Cloud Build file.

Environment Variables (GCP)

Add in these variables and then save your changes, the container will attempt to re-run, eventually spinning up and showing all green signals. The very last thing to do now is to make your instance publicly accessible.


Navigate to the Security tab and enable Allow unauthenticated invocations. Your Payload instance will now be accessible on the public internet. Congratulations, your Continuous Deployment Pipeline is now complete. Just click on the automatically generated URL and your Payload login screen will be waiting for you.



Summary

That was quite a lot to take in, but now that your instance is up and running, all you need to do is push changes to you GitHub repository in order to deploy them to Google Cloud Run. It takes a little bit of time to set up, but the payoff is definitely worth it.


If you found this article useful, let us know! Omniux are active in the Payload Discord Group and on Twitter. Share your thoughts with the team.


Build Your Business

Get started with our custom package builder;

What's included?


Access to marketing, development, & finance experts

Performance & marketing audits

Ad buying oppertunities through The US Open, Comcast, Apple, and more...

Not sure what services you want? Try our...

Service Quiz