Setting Up Strapi on Google Cloud Run with Cloud SQL and GCP Bucket
Configuring Strapi V3/V4 on Google Cloud Run, intricately weaving in the capabilities of Cloud SQL and Cloud Bucket integration.
Once upon a time, crafting and coding APIs from scratch was the norm in my company, followed by their deployment on Cloud Run. However, as the company grew, the demand for swift, standardized, and seamless deployments across a myriad of web and mobile applications prompted the quest for a scalable and unwavering backend solution. Enter Strapi, an open-source headless CMS, renowned for empowering developers to rapidly shape APIs. As I ventured into implementing Strapi on my projects, I encountered the absence of a one-stop solution for the configuration of both V3 and V4. This prompted the exploration of orchestrating Strapi on Cloud Run, seamlessly integrating Cloud SQL for database administration, and embracing Google Cloud Storage for seamless file storage for both Strapi V3 and Strapi V4. In this article, I will delve into the intricacies of this process.
Setting up strapi:
We can either use yarn or npx to set up the strapi:
npx create-strapi-app@latest my-strapi-project --quickstart
yarn create strapi-app my-strapi-project --quickstart
Creating db config:
Strapi has the ability to select the appropriate database.js file based on the NODE_ENV value. In my config, I have two distinct environments, staging, and production and I have structured the folder hierarchy to match the same. The database.js file residing in the config folder takes precedence as the default configuration, except when NODE_ENV = production.
In my setup, I am using Google Cloud SQL to manage my Postgres DB for production and the default SQLite db for local testing or staging. The db config files look like this:
For V3:
Staging:
module.exports = ({ env }) => {
return {
defaultConnection: 'default',
connections: {
default: {
connector: 'bookshelf',
settings: {
client: 'sqlite',
filename: env('DATABASE_FILENAME', '.tmp/data.db'),
},
options: {
useNullAsDefault: true,
},
},
},
};
}
Production:
module.exports = ({ env }) => {
return {
defaultConnection: 'default',
connections: {
default: {
connector: "bookshelf",
settings: {
client: "postgres",
host: `/cloudsql/${env('INSTANCE_CONNECTION_NAME')}`,
port: env.int("DATABASE_PORT"),
database: env("DATABASE_NAME"),
user: env("DATABASE_USERNAME"),
password: env("DATABASE_PASSWORD"),
schema: env("DATABASE_SCHEMA"), // Not mandatory, picks up public by default
},
options: {},
},
},
};
};
For V4:
Staging:
module.exports = ({ env }) => {
return {
connection: {
client: 'sqlite',
connection: {
filename: env('DATABASE_FILENAME', '.tmp/data.db'),
},
useNullAsDefault: true,
debug: false,
},
}
};
Production:
module.exports = ({ env }) => {
return {
connection: {
client: 'postgres',
connection: {
host: `/cloudsql/${env('INSTANCE_CONNECTION_NAME')}`,
port: env.int('DATABASE_PORT'),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
schema: env('DATABASE_SCHEMA'), // Not mandatory, picks up public by default
},
debug: false,
},
}
};
Creating cloud storage config:
Strapi by default uses local storage to store the images. But in our case, we are going to use Cloud Storage by GCP. For this article, I assume the bucket is already set up.
We can either use yarn or npm to install the required extension:
npm i @strapi-community/strapi-provider-upload-google-cloud-storage
yarn add @strapi-community/strapi-provider-upload-google-cloud-storage
For V3:
Create a new file at “my-strapi-project\extensions\upload\config\settings.json” and insert the following code. Unlike the production and staging configurations available for the database, I encountered difficulty in applying a similar approach for the cloud bucket configuration. Therefore, during local testing, I remove this file, which results in the system using the local storage option.
{
"provider": "google-cloud-storage",
"providerOptions": {
"bucketName": "demo_gcp_bucket_name",
"publicFiles": true,
"uniform": false,
"baseUrl": ""
}
}
For V4:
The segregation of the production and staging environments isn’t a concern in V4, unlike in V3. You can configure this by following the steps outlined in the provided code. Simply create a file named “plugins.js” within the “my-strapi-project\config” directory, and insert the following code:
module.exports = ({ env }) => {
if (env('NODE_ENV') === 'production') {
return {
upload: {
config: {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
bucketName: env('BUCKET_NAME'),
publicFiles: true,
uniform: false,
basePath: '',
},
},
}
}
} else {
return {
}
}
//...
}
Note: I am using “publicFiles: true” because making it false will give broken URL in the content manager preview. There is no other impact. An explanation for the same is given in the plugin’s documentation.
Creating Dockerfile:
V3:
FROM node:14 as build
WORKDIR /usr/src/app
COPY package.json .
COPY .env .
ENV NODE_ENV production
RUN yarn install --production --quiet
COPY . .
#needed for files and folder creation by Cloud Run
RUN chmod 777 /usr/src/app/node_modules
RUN chmod 777 /usr/src/app/public/uploads
RUN chmod 777 /usr/src/app
RUN yarn build
EXPOSE 1337
EXPOSE 5432
#changing user
USER 1000
CMD ["yarn","start"]
V4:
FROM node:16 as build
WORKDIR /usr/src/app
COPY package.json .
COPY yarn.lock .
COPY .env .
ENV NODE_ENV production
RUN yarn install --production --quiet --frozen-lockfile
COPY . .
#needed for files and folder creation by Cloud Run
RUN chmod 777 /usr/src/app/node_modules
RUN chmod 777 /usr/src/app/public/uploads
RUN yarn build
EXPOSE 1337
EXPOSE 5432
#changing user
USER 1000
CMD ["yarn","start"]
Deploying on Cloud Run:
Build the image:
You have two options: either construct the image locally and subsequently push it to GCR (Google Container Registry) or utilize the Cloud Build service to automatically build and push the image to GCR. The necessary commands for both approaches are provided below in sequential order:
gcloud builds submit --tag asia.gcr.ioio/[PROJECT-ID]/[IMAGE] .
docker build -t gcr.io/[PROJECT-ID]/[IMAGE]
docker push asia.gcr.io/[PROJECT-ID]/[IMAGE]
Note: Google suggests using Artifact Registry instead of the now deprecated GCR
Deploy on the Cloud Run:
- Go to the cloud run page
- Click on “Create Service” at the top
- Click on the “Deploy one revision from an existing container image” select option and select your GCR repo from the Container Registry option.
- Click on “Container, Networking, Security” dropdown and set
Container port as 1337 - Choose your config. In the next image, I have provided a screenshot of a sample config I use for my strapi.
6. Select your Cloud SQL connection. If you created the SQL in the same project it should be available here.
7. Click on Deploy and you are done.
Thanks for reading! Share this article if you found it useful.
Please do Clap 👏 to show some love :)