
This hands-on session is about deployment of n8n on Cloud Run. This session covers the preparation of Google Cloud Platform (GCP), create revision on Cloud Run, enable Google OAuth for n8n, prepare Telegram bot for automation, and create simple n8n automation workflow to track expenses.
This session is separated into two sections. The first section will focus on the GCP environment. After we finished the work in GCP, we jumped into Telegram bot creation and the n8n environment to create and deploy the automation workflow.
Let's make this session a safe place to learn. Don't hesitate to ask questions. If you have completed several steps, please feel free to assist other participants who might need help. Let's go together!
https://console.cloud.google.com

We will use two approaches: Cloud Console and Cloud Shell. Choose one that fits you.
cloud run on the search bar, then choose Cloud Run. 
Deploy container to start creating the n8n deployment.Deploy one revision from an existing container imagen8nio/n8n on the container image URLn8n-automation as the service nameus-west1 for the regionAllow public access on authentication sectionInstance-basedcontainer port to 5678 and memory to 2 GiBCreate to create first revision.gcloud services enable run.googleapis.com
gcloud run deploy n8n-automation \
--image=n8nio/n8n \
--region=us-west1 \
--allow-unauthenticated \
--port=5678 \
--no-cpu-throttling \
--memory=2Gi \
--min-instances=1 \
--max-instances=1
Edit & deploy new versionVariables & SecretsAdd variableWEBHOOK_URLthe URL you copied beforeDeploy to create new revisiongcloud run services describe n8n-automation --region=us-west1
gcloud run services update n8n-automation \
--region=us-west1 \
--update-env-vars=WEBHOOK_URL=<the URL you copied before>


Get started on the Overview tab.NextPublish app to publish the OAuth.Create credentials then OAuth client ID.Web application on Application type.n8n Automation.Add URI./rest/oauth2-credential/callback (e.g. https://n8n.us-west1.run.app/rest/oauth2-credential/callback)CreateClient ID and Client Secret to be used on 2nd section@BotFather or use this link instead https://t.me/BotFather./newbot command.bot (e.g. expense_bot or ExpenseBot). If the username is already taken, choose the other name.

I'm not using n8n for work on What best describes your company? section.Event on How did you hear about n8n? section.Get started.+ then Credentials.Telegram API on the app to connect.Continue.Save and wait for n8n to test the connection.+ then Credentials.Google Sheets OAuth2 API on the app to connect.Continue.Client ID and Client Secret obtained from 1st section.Sign in With Google.
Continue

This section is quite long and have a little coding involved. Don't worry, I will guide you step by step.
+ then Workflow.Telegram Expenses.+ button, then select On message.Execute step then send a random message to the bot by accessing your bot.entities field gives you additional bot command information if you send a message starting with /.Message starting with /:
Normal message:
+ button on the right of the Telegram trigger node, then choose the If node.message.chat.entities.entities[0].type from the left side to the value1 column.String > is equal to.value2 with bot_command.convert types where required.Execute workflow.+ button then add Telegram > Send a text message node.message.chat.id from the left side to the Chat ID column.Text, fill with the rejection message, e.g. "Only bot commands were accepted."Execute workflow. The Telegram bot will reply to your non bot command with your defined message.+ button then add Switch node.message.chat.text to value1.String > starts with.value2 with /note.Rename Output and rename to /note.convert types where required.Execute workflow.+ button then add Code > Code in JavaScript.Run Once for All Items.const rawMessage = $input.first().json.message.text.trim();
const rawExpenses = rawMessage.split("/note\n")[1];
const expenses = rawExpenses.split("\n");
const output = expenses.map((expense) => {
const e = expense.trim().split("/");
let price = 0;
if (e[1].endsWith("k")) {
price = Number(e[1].split("k")[0].replaceAll(",", "."));
price *= 1000;
} else if (e[1].endsWith("rb")) {
price = Number(e[1].split("rb")[0].replaceAll(",", "."));
price *= 1000;
} else {
price = Number(e[1]);
}
return {
item: e[0],
price: price,
};
});
return output;
Execute workflow./note
ayam/10k
sari roti/50k
hydro coco/10000
+ button then add Google Sheets > Append row in sheet.Date, Month, Year, Item, and Price as the table header.By URL and paste your Spreadsheet's URL.From list, and select your current sheet's name.Map Each Column Manually.{{ $now.day }}{{ $now.month }}{{ $now.year }}item from the left side (output of JavaScript node) into Item.price from the left side (output of JavaScript node) into Price.Execute workflow.+ button then add Telegram > Send a text message node.message.chat.id from the left side to the Chat ID column.Text, fill with the rejection message, e.g. "Your expenses are inserted into Sheet."Settings tab, turn Execute Once on.Execute workflow.Add Routing Rule./note section into value1 in new section.String > starts with./summary.Rename Output and rename to /summary.convert types where required.+ button then add Code > Code in JavaScript.const output = {
month: $now.month,
year: $now.year,
};
const rawMessage = $input.first().json.message.text.trim();
const expression = rawMessage.split("/summary ");
if (expression.length == 1) {
return output;
}
const summaryExpressions = expression[1].split("-");
switch (summaryExpressions.length) {
case 2:
output.year = summaryExpressions[1];
case 1:
output.month = summaryExpressions[0];
break;
}
return output;
Execute workflow./summary or /summary 12 or /summary 12-2025+ button then add Google Sheets > Get row(s) in sheet.By URL and paste your Spreadsheet's URL.From list, and select your current sheet's name.AND as the Combine Filter.Settings tab, turn Always Output Data on.Execute step.If node.value1, change the mode to Expression then use {{ $json }} as the value.Object > is not empty.Execute step.message.chat.id from the left side to the Chat ID column.No data for {{ $('summary transformation').item.json.month }}/{{ $('summary transformation').item.json.year }}.
Execute workflow.Summarize node.Count then drag Item output from the left side to the Field column.Sum on Aggregation, then drag Price from the left side to the Field column.Average on Aggregation, then drag Price from the left side to the Field column.Execute step.message.chat.id from the left side to the Chat ID column.This is your summary for {{ $('summary transformation').item.json.month }}/{{ $('summary transformation').item.json.year }}:
- Item you buy: {{ $json.count_Item }}
- Total expenses: {{ $json.sum_Price }}
- Average expenses: {{ $json.average_Price }}
Execute workflow.Execute workflow.Active on the top right.Download.After following the tutorials above, you should have this workflow. 