Serverless e-commerce: Vendure on Google Cloud Run
Tags: googlecloud, serverless, typescript, graphql
Published: 2021-11-30 17:20:38

Google’s Cloud Run is a scalable, containerized, fully managed serverless platform. It’s cheap and handles infrastructure and scaling for us. Sounds perfect to run a headless Vendure instance on, right?

Here is what we need to do. Don’t worry, for most of these you can use a plugin:

* Create a database
* Use Cloud Storage for assets
* Use Cloud Tasks to process worker jobs
* Dockerize Vendure
* Build image and deploy to Google Cloud Run

We assume you already have:
* A Vendure project set up locally. If not, checkout [these steps](https://www.vendure.io/docs/getting-started/)
* `gcloud` cli [installed](https://cloud.google.com/sdk/docs/install) and [authorized](https://cloud.google.com/sdk/docs/initializing)

## Create a database for Vendure
Vendure requires a database to store its products, orders, customers etc. In this tutorial, we will use Google’s Cloud SQL: a managed database platform.

1. [Enable Cloud SQL](https://cloud.google.com/sql/docs/mysql/quickstart) in your dashboard and create a database.
2. For this example, 1vcpu and 10 GB SSD is sufficient. PostgreSQL or MySQL is up to you. Make sure to write down the password you entered.
3. Create a database named `vendure`
4. Click on your instance, and go to connection > add network. Create a new network with IP range `0.0.0.0/0`. Careful with production environments, this will make your SQL instance publicly available!

Configure Vendure to use the new database:
```js
// vendure-config.ts
dbConnectionOptions: {
    type: 'mysql',
    synchronize: true, // Disable this after first startup
    logging: false,
    username: 'root', // Don't use this in production
    password: 'your-password',
    host: '192.56.298.3', // The public IP of your SQL instance
    database: 'vendure',
},
```

## Setup Cloud Storage
Cloud run instances are stateless, which also means you shouldn’t use its local file system. Instead, we will use Google Cloud Storage.

Create a storage bucket and [make it publicly accessible](https://cloud.google.com/storage/docs/access-control/making-data-public), 
so that the images can be used in a storefront. You can use [this plugin](https://www.npmjs.com/package/vendure-plugin-google-storage-assets)
to connect Vendure to the bucket.

```js
// vendure-config.ts
AssetServerPlugin.init({
    storageStrategyFactory: () => new GoogleStorageStrategy({bucketName: 'your-bucket-name'}),
    route: 'assets',
    assetUploadDir: '/tmp/vendure/assets',
})
```

Alternatively, you can implement the [AssetStorageStrategy](https://www.vendure.io/docs/typescript-api/assets/asset-storage-strategy/) yourself.

## Setup Cloud Tasks
Cloud run allows no processing outside request context. As soon as your server returned a response, 
there is no guarantee that any leftover processes will be finished. Because of this, we need some way to wrap the Vendure worker Jobs in a request. 
We can do that with [this plugin](https://www.npmjs.com/package/vendure-plugin-google-cloud-tasks).

This plugin puts jobs in a queue and Cloud Tasks posts the messages back to a publicly available endpoint in your Vendure application.
```js
// vendure-config.ts
CloudTasksPlugin.init({
  taskHandlerHost: 'https://your-instance-sds34vbs-ew.a.run.app', // This endpoint needs to be accesible by Google Cloud Tasks
  projectId: 'your-projectId',
  location: 'europe-west1',
  authSecret: 'some-secret',
})
```

For simplicity, we will start the worker in the same instance as the application:
```js
// index.ts
bootstrap(config)
    .then(app => app.get(JobQueueService).start())
    .catch(err => {
        console.log(err);
        process.exit(1);
    });
```
**This is not recommended for production!** [Read more about the worker here.](https://www.vendure.io/docs/developer-guide/vendure-worker/)

Alternatively, you could implement the [JobQueueStrategy](https://www.vendure.io/docs/typescript-api/job-queue/job-queue-strategy/) yourself.

## Dockerize Vendure
Cloud Run requires Vendure to  be Dockerized. We can simply create a Dockerfile in the root of the project:
```
FROM node:16
WORKDIR /usr/src/app
COPY . .
RUN yarn install --production
RUN yarn build
CMD [ "node", "dist/index.js" ]
```

## Deploy
First we need to build an image and push it to Google's container registry:
1. [Install docker](https://docs.docker.com/engine/install/)
2. Execute these commands in the root of your project:
```shell
docker build -t eu.gcr.io/your-projectId/vendure .
# Configure docker to use Google authentication
gcloud auth configure-docker -q
docker push eu.gcr.io/your-projectId/vendure
```

Now all that's left is deploying the image to Google Cloud Run:
```shell
# This sets all your secrets from a .env file in a variable, so we can pass it to Cloud Run
export ENV_VARS=$(paste -sd, .env)
gcloud run deploy shops-test \
            --quiet \
            --image "eu.gcr.io/your-projectId/vendure:latest" \
            --region "europe-west1" \
            --platform "managed" \
            --allow-unauthenticated \
            --memory=1G \
            --project=your-projectId \
            --set-env-vars=$ENV_VARS 
```

Go to https://console.cloud.google.com/run to view your public URL. It should look something like `https://your-instance-sds34vbs-ew.a.run.app`. 
Make sure to also set this URL as `taskHandlerHost` in the Cloud Tasks plugin if you haven't already.

2. Go to `https://your-instance-sds34vbs-ew.a.run.app/admin` and login with your super admin user
3. Go to products and create a new product
4. Add an image to the product
5. Save the product and go back to the overview
6. Your product should appear in the product overview in the admin
7. Congratulations, this means everything works as expected!

**Make sure you set `synchronize: false` after the database has been populated!**

Some optional improvements:
1. [Set up a separate worker instance](https://www.vendure.io/docs/developer-guide/vendure-worker/)
2. Restrict access to your database to specific IP's.
3. Use [Unix sockets](https://cloud.google.com/sql/docs/mysql/connect-run#configuring) for database access

Thats it!