Build a Slack Bot with Node.js on Cloud Run

1. Overview


In this codelab, you'll learn how to build a Slack bot using the Botkit toolkit and run it on Google Cloud Platform. You'll be able to interact with the bot in a live Slack channel.

What you'll learn

  • Creating a bot custom integration in Slack.
  • Building a Node.js image in Docker.
  • Uploading a Docker image to a private Google Container Registry.
  • Running a Slack bot on Cloud Run, a fully managed compute platform that automatically scales your stateless containers

What you'll need

  • A Google Cloud Platform Project
  • A Browser, such Chrome or Firefox

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would you rate your experience with using Google Cloud Platform?

Novice Intermediate Proficient

2. Setup and Requirements

Self-paced environment setup

  1. Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or G Suite account, you must create one.)




Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID.

  1. Next, you'll need to enable billing in Cloud Console in order to use Google Cloud resources.

Running through this codelab shouldn't cost much, if anything at all. Be sure to to follow any instructions in the "Cleaning up" section which advises you how to shut down resources so you don't incur billing beyond this tutorial. New users of Google Cloud are eligible for the $300USD Free Trial program.

Using Google Cloud Shell

While Google Cloud Platform and Node.js can be operated remotely from your laptop, in this codelab you will use Google Cloud Shell, a command line environment running in the Cloud.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. This means that all you will need for this codelab is a browser (yes, it works on a Chromebook).

  1. To activate Cloud Shell from the Cloud Console, simply click Activate Cloud Shell fEbHefbRynwXpq1vj2wJw6Dr17O0np8l-WOekxAZYlZQIORsWQE_xJl-cNhogjATLn-YxLVz8CgLvIW1Ncc0yXKJsfzJGMYgUeLsVB7zSwz7p6ItNgx4tXqQjag7BfWPcZN5kP-X3Q (it should only take a few moments to provision and connect to the environment).


Screen Shot 2017-06-14 at 10.13.43 PM.png

Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your PROJECT_ID.

gcloud auth list

Command output

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Command output

project = <PROJECT_ID>

If, for some reason, the project is not set, simply issue the following command:

gcloud config set project <PROJECT_ID>

Looking for your PROJECT_ID? Check out what ID you used in the setup steps or look it up in the Cloud Console dashboard:


Cloud Shell also sets some environment variables by default, which may be useful as you run future commands.


Command output

  1. Finally, set the default zone and project configuration.
gcloud config set compute/zone us-central1-f

You can choose a variety of different zones. For more information, see Regions & Zones.

3. Create a Slack workspace

You will need a Slack workspace where you are allowed to create custom integrations. You can create a workspace for free if you do not already have one that you wish to use for this tutorial.


4. Create a Slack bot user

A bot user can listen to messages on Slack, post messages, and upload files. In this codelab, you will create a bot to post a simple greeting message.

Create a new Slack app

  • Go to the Slack apps management page.
  • Click the Create new app button in the upper-right corner.
  • Give the app a name, such as "Kittenbot".
  • Choose the Slack team where you want it installed.

Create your Bot User

  • Go to App Home on the left-side panel under Features


  • Assign a scope to your bot token by clicking Review Scopes to Add
  • Scroll down to Bot Token Scopes and click Add an OAuth Scope. Select chat:write to "Send messages as Kittenbot"


  • Scroll up and click the Install App to your Workspace button.
  • This will install the app to your team, add the bot user you just created, and generate a Bot Token.
  • When prompted, click "Allow" to authorize the bot to chat in your workspace.

Save your Bot Token

  • Click the Copy button to copy the Bot User OAuth Access Token text into your clipboard.
  • You're going to need this token later, so save it in your Cloud Shell environment variables by running the following command, pasting in your Bot Token where indicated

Get the Client Signing Secret

  • Go to Basic Information on the apps management page.
  • Scroll down to Signing Secret, click Show, and then copy it to your clipboard


  • Save the secret in your environment variables

Don't worry. You can come back to this configuration page from the apps management page if you need to get these tokens again.

5. Save your secrets to Secret Manager

We want to ensure that your Bot Token and Client Signing Secret are stored securely. Hard-coding the Slack token in source code makes it likely to accidentally expose your token by publishing it to version control or embedding it in a docker image.

The Secret Manager provides a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. Secret Manager provides a central place and single source of truth to manage, access, and audit secrets across Google Cloud.

To use the Secret Manager, we need to enable the API, update permissions, and create the secrets.

Enable the API

gcloud services enable

Set Permissions

You'll need to update your project permissions so that you can create and access secrets in the Secret Manager. Here we're using the output of gcloud auth list, which returns the current active account. This should be the currently logged in user account.

gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT --member=user:$(gcloud auth list --format 'value(account)') --role=roles/secretmanager.admin

Create your Secrets

Now that your account has permission to create and access the values of your secrets, you'll save your Bot Token and Signing Secret with the following commands.

  • Bot Token
echo -n $BOT_TOKEN | gcloud secrets create bot-token --replication-policy=automatic --data-file=-
  • Client Signing Secret
echo -n $SIGNING_SECRET | gcloud secrets create client-signing-secret --replication-policy=automatic --data-file=-

Access your Secrets

Let's confirm that your secrets were created properly and your permissions are working. Access your secrets with the following commands:

gcloud secrets versions access 1 --secret="bot-token"
gcloud secrets versions access 1 --secret="client-signing-secret"

You can also view and manage your secrets in the Cloud Console Secret Manager page.

6. Get the sample code

In Cloud Shell on the command-line, run the following command to clone the GitHub repository:

git clone

Change directory into cloud-slack-bot/start.

cd cloud-slack-bot/start

Understanding the Code

Open up the kittenbot.js file with an editor. You can use any editor of your choice, such as emacs or vim. This tutorial uses the code editor feature of Cloud Shell for simplicity.

  • Open the code editor by clicking on the pencil above the Cloud Shell. e794dabd2a01b8cd.png
  • Select the cloud-slack-bot/start/kittenbot.js file. b0333915b4df0c04.png

The kittenbot code has two main functions. One is to retrieve the secrets, and the other is to run the bot.

First we import our dependencies:


const {Botkit} = require('botkit');
const {SlackAdapter, SlackEventMiddleware} = require(
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');

The SlackAdapter and SlackEventMiddleware are packages that extend Botkit and allow the bot to easily translate messages to and from the Slack API. The Secret Manager client will allow you to access the secrets that you saved in an earlier step.

Next we have our function for retrieving the secrets:

* Returns the secret string from Google Cloud Secret Manager
* @param {string} name The name of the secret.
* @return {payload} The string value of the secret.
async function accessSecretVersion(name) {
 const client = new SecretManagerServiceClient();
 const projectId = process.env.PROJECT_ID;
 const [version] = await client.accessSecretVersion({
   name: `projects/${projectId}/secrets/${name}/versions/1`,
 // Extract the payload as a string.
 const payload ='utf8');
 return payload;

This function returns the string values of the secrets that are required to authenticate the bot.

The next function initializes the bot:

* Asynchronous function to initialize kittenbot.
async function kittenbotInit() {
 const adapter = new SlackAdapter({
   clientSigningSecret: await accessSecretVersion('client-signing-secret'),
   botToken: await accessSecretVersion('bot-token'),
 adapter.use(new SlackEventMiddleware());
 const controller = new Botkit({
   webhook_uri: '/api/messages',
   adapter: adapter,
 controller.ready(() => {
   controller.hears(['hello', 'hi'], ['message', 'direct_message'],
      async (bot, message) => {
       await bot.reply(message, 'Meow. :smile_cat:');

The first part of the function configures the SlackAdapter with the secrets, and then specifies an endpoint for receiving messages. Then, once the controller is on, the bot will reply to any message containing "hello" or "hi" with "Meow. 😺"

Unfortunately, you cannot run the bot locally and have it interact with Slack. The Slack Events API uses webhooks, which means it requires an HTTP endpoint to send events to. To integrate with Slack, you'll need to host this app on a public URL.

Understanding the Dockerfile

To host our application and create a public URL, we'll containerize our bot with a Dockerfile, and then host it on Cloud Run. A Docker image bundles all of your dependencies (even the compiled ones) so that it can run in a lightweight sandbox.


FROM node:10-slim

# Install app dependencies.
COPY package.json /src/package.json
RUN npm install

# Bundle app source.
COPY kittenbot.js /src

CMD ["node", "kittenbot"]

A Dockerfile is a recipe for a Docker image. This one layers on top of the Node.js base image found on the Docker hub, copies package.json to the image and installs the dependencies listed in it, copies the kittenbot.js file to the image, and tells Docker to that it should run the Node.js server when the image starts.

7. Build the Docker Image

  • Go back to the Cloud Shell and run this command to save your project ID to the environment variable PROJECT_ID. Commands in this tutorial will use this variable as $PROJECT_ID.
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
  • To build the image, we'll use the Cloud Build API. Enable the API by running the following command.
gcloud services enable
  • We'll use the following command to both build the image and push it to the Google Container Registry, a private repository for your Docker images accessible from every Google Cloud project (but also from outside Google Cloud Platform).
gcloud builds submit --tag${PROJECT_ID}/slack-codelab .

This command takes about 2 minutes to complete. It has to download the base image and Node.js dependencies, and push it to the registry.

Viewing images in Google Container Registry

When the image upload completes, you can see the container image listed in the Google Cloud Console: Container Registry.


If you're curious, you can navigate through the container images as they are stored in Google Cloud Storage by following this link:

8. Deploy the App to Cloud Run

The Slack Events API uses webhooks to send outgoing messages about events. When you configure the Slack App, you'll have to provide a publicly accessible URL for the Slack API to ping.

Cloud Run is a good solution for hosting webhook targets. It allows you to use any language or runtime that you like and it provides concurrency, meaning your application will be able to handle much higher volume.

Enable the Cloud Run API

gcloud services enable

Configure your Cloud Run preferences

Cloud Run offers both a fully managed option and an Anthos option, which supports both Google Cloud and on-premise environments. For this tutorial, we'll use Cloud Run fully managed.

gcloud config set run/platform managed

Next, set the compute zone:

gcloud config set run/region us-central1

Update Secret Manager Permissions

In order to call the accessSecretVersion function, the Cloud Run service account will need to have the role: roles/secretmanager.secretAccessor.

First, save the default service account into an environment variable:

export SERVICE_ACCOUNT=$(gcloud iam service-accounts list --format 'value(EMAIL)' --filter 'NAME:Default compute service account')

Confirm you have the email address saved:


You can also find your Default compute service account in the Cloud console.

Once you have the email address, enable the Secret Manager role for the service account:

gcloud projects add-iam-policy-binding $PROJECT_ID --member=serviceAccount:$SERVICE_ACCOUNT --role=roles/secretmanager.secretAccessor

Deploy the App

A Cloud Run service exposes a unique endpoint and automatically scales the underlying infrastructure to handle incoming requests. To create a service and deploy the kittenbot image, use the following command.

gcloud run deploy kittenbot --image${PROJECT_ID}/slack-codelab --set-env-vars PROJECT_ID=${PROJECT_ID}

This command has three distinct parts:

  • Creates a service called "kittenbot"
  • Deploys the image we saved in the Google Container Registry
  • Sets the environment variables for the service

When prompted, specify the region us-central1.


Allow unauthenticated invocations to kittenbot. Remember, the URL has to be public!


View your Cloud Run Service

By now, your app will have finished deploying and the terminal should have returned something like:

Service [kittenbot] revision [kittenbot-.......] has been deployed and is serving 100 percent of traffic at 

This URL will be the base of the URL you use to enable the Slack Events API. Copy it into your clipboard to use in the next step.

Your service is now live and publicly available! Go to the Cloud Run console to see more information about it. 79c20beb256e1dc8.png

You can see when the last revision was created, how much traffic it's receiving, and look at the logs. If we click into the logs, we can see that the Botkit controller is on and ready to receive messages.

Now let's start sending messages from our Slack channel!

9. Enable the Slack Events API

As we saw earlier, our kittenbot code specifies a relative endpoint for our webhook target.


 const controller = new Botkit({
    webhook_uri: '/api/messages',
    adapter: adapter,

This means, our full URL will be the base part from the Cloud Run service, plus "/api/messages."

To retrieve the base part of the URL:

gcloud run services describe kittenbot

Enable Events

In the apps management page, go to the Events Subscriptions section on the sidebar, and toggle Enable Events on. Input your URL like this:



Depending on how fast you type the URL in, it might try to verify before you're finished. If it fails, click "Retry."


Subscribe to all the message bot events.


Click Save Changes at the bottom of the page. You will be prompted to Reinstall Your App. Go through the prompts and click Allow.

At this point, your bot is fully integrated! Messages in the workspace will trigger Slack to send messages to your Cloud Run service, which will in turn respond with a simple greeting.

10. Try it out

  • Send a direct message to Kittenbot. 4e181d606a0f2ad0.png
  • Add kittenbot to your channel by entering "@kittenbot" and then clicking "Invite Them." 6e18bf45b81e7329.png

Now everyone in your channel can interact with Kittenbot!


Each message in Slack triggers an event and sends an HTTP POST message to our Cloud Run service. If you take a look at the Cloud Run service logs, you'll see that each message corresponds to a POST entry in the log.


The kittenbot responds to each message with "Meow. 😺".

11. Extra Credit - Update your bot to a new version

This extra credit section should take you about 10 minutes to complete. Feel free to skip directly to Cleanup.

Conversational Threads

We'd like for the bot to do more than just say "meow". But how do you deploy a new version of something that is running on Cloud Run?

First, let's modify the application. Botkit offers the ability to handle conversations. With these, the bot can request more information and react to messages beyond a one word reply. Open the kittenbot.js file in the web editor to make the following changes.

Define the Dialog

  • First, let's define our conversation and conversational dependencies. Add this code at the bottom of the file.


// ...

const maxCats = 20
const catEmojis = [

function makeCatMessage(numCats){
  let catMessage = "";
  for (let i = 0; i < numCats; i++) {
    // Append a random cat from the list
    catMessage += catEmojis[Math.floor(Math.random() * catEmojis.length)];
  return catMessage;

// Start kitten-delivery convo
function createKittenDialog(controller) {
  let convo = new BotkitConversation('kitten-delivery', controller);

  convo.ask('Does someone need a kitten delivery?', [
          pattern: 'yes',
          handler: async(response, convo, bot) => {
              await convo.gotoThread('yes_kittens');
          pattern: 'no',
          handler: async(response, convo, bot) => {
              await convo.gotoThread('no_kittens');
          default: true,
          handler: async(response, convo, bot) => {
              await convo.gotoThread('default');


  convo.addQuestion('How many would you like?', [
                  pattern: '^[0-9]+?', 
                  handler: async(response, convo, bot, message) => {
                    let numCats = parseInt(response);
                    if(numCats > maxCats){
                      await convo.gotoThread('too_many');
                      convo.setVar('full_cat_message', makeCatMessage(numCats));
                      await convo.gotoThread('cat_message');
                { default: true,
                  handler: async(response, convo, bot, message) => {
                    if (response){ 
                      await convo.gotoThread('ask_again');
                    // The response '0' is interpreted as null
                    else { await convo.gotoThread('zero_kittens');
                ],'num_kittens', 'yes_kittens');

  // If numCats is too large, jump to start of the yes_kittens thread
    'Sorry, {{vars.num_kittens}} is too many cats. Pick a smaller number.', 
  convo.addAction('yes_kittens', 'too_many');

  // If response is not a number, jump to start of the yes_kittens thread
  convo.addMessage('Sorry I didn\'t understand that', 'ask_again');
  convo.addAction('yes_kittens', 'ask_again');

  // If numCats is 0, send a dog instead
    {'text': 'Sorry to hear you want zero kittens. ' +
             'Here is a dog, instead. :dog:',
     'attachments': [
          'fallback': 'Chihuahua Bubbles -',
          'text': '<|' +
            'Chihuahua Bubbles>!'
    ]}, 'zero_kittens');
  // Send cat message
  convo.addMessage('{{vars.full_cat_message}}', 'cat_message')

  convo.addMessage('Perhaps later.', 'no_kittens');

  return (convo);
// END: kitten-delivery convo

This new conversation directs the thread based on the responses. For example, if the user responds "no" to the kitten question, it jumps to the message labeled "no_kittens", which is the end of that conversational thread.

Add Dialog to Controller

Now that the conversation is defined, you need to add it to your controller. In the kittenbotInit function, add the new dialog to the controller, after the controller is configured and before it is ready.

async function kittenbotInit() {

  const controller = new Botkit({
      webhook_uri: '/api/messages',
      adapter: adapter,

  // Add Kitten Dialog
  convo = createKittenDialog(controller);

  // Controller is ready 
  controller.ready(() => {


Trigger the Dialog

Now that dialog is available for the controller to use, we'll add a trigger for it to start. When the chatbot hears "kitten", "kittens", "cat", or "cats", it will start the conversation.

// ...
// Controller is ready 
  controller.ready(() => {
    controller.hears(['hello', 'hi'], ['message', 'direct_message'],
        async (bot, message) => {
            return await bot.reply(message, 'Meow. :smile_cat:');

    // START: listen for cat emoji delivery
      ['message', 'direct_message'],
      async (bot, message) => {
        // Don't respond to self
        if (message.bot_id != message.user){
          await bot.startConversationInChannel(, message.user);
          return await bot.beginDialog('kitten-delivery');
    // END: listen for cat emoji delivery

Build & Deploy

Now that the controller is updated, you'll need to re-build and re-deploy the application.

gcloud builds submit --tag${PROJECT_ID}/slack-codelab .
gcloud run deploy kittenbot --image${PROJECT_ID}/slack-codelab 

Try it out


Congratulations! You just updated a Slack bot running on Cloud Run to a new version.

Slash Commands

What if you don't want to have a conversation with the user? What if you'd prefer to simply trigger an action with one simple command?

Slack offers this functionality via Slash Commands, which allow users to invoke your application by entering the command into the message box.

Enable Slack Slash Commands

  • Go to the Slash Commands section under Features on your Apps Management Page.
  • Click "Create New Command"
  • Configure a /cats command with your kittenbot service URL. Remember to use the same endpoint that you used to enable the Events API! This is your URL, plus '/api/messages'


Add Slash Commands to your Controller

In kittenbot.js, inside the controller.ready function, add a handler for slash commands.

// ...
// Controller is ready 
  controller.ready(() => {
    // START: slash commands
    controller.on('slash_command', async(bot, message) => {
        let numCats = parseInt(message.text);
        response = makeCatMessage(numCats);
        bot.httpBody({text: response});
    // END: slash commands

Build & Deploy

Now that the controller is updated, you'll need to re-build and re-deploy the application.

gcloud builds submit --tag${PROJECT_ID}/slack-codelab .
gcloud run deploy kittenbot --image${PROJECT_ID}/slack-codelab 

Try it out

Enter /cats plus a number to send the slash command. Eg: /cats 8


The bot will respond with 8 cats, only visible to you:


12. Cleanup

Congratulations, you now have a Slack bot running on Google Cloud Run. Time for some cleaning of the resources used (to save on cost and to be a good cloud citizen).

Delete the Project

If you prefer, you can delete the entire project. In the GCP Console, go to the Cloud Resource Manager page:

In the project list, select the project we've been working in and click Delete. You'll be prompted to type in the project ID. Enter it and click Shut Down.

Alternatively, you can delete the entire project directly from Cloud Shell with gcloud:

gcloud projects delete [PROJECT_ID]

If you prefer to delete the different components one by one, proceed to the next section.

Delete the Deployment

gcloud run services delete kittenbot

Command output

Service [kittenbot] will be deleted.
Do you want to continue (Y/n)?  y
Deleted service [kittenbot].

Delete your bot token secret

gcloud beta secrets delete bot-token

Command output

You are about to destroy the secret [bot-token] and its [1]
version(s). This action cannot be reversed.
Do you want to continue (Y/n)?  y
Deleted secret [bot-token].

Delete your client signing secret

gcloud beta secrets delete client-signing-secret

Command output

You are about to destroy the secret [client-signing-secret] and its
[1] version(s). This action cannot be reversed.
Do you want to continue (Y/n)?  y
Deleted secret [client-signing-secret].

Delete the storage buckets

First, list the Google Cloud Storage buckets to get the bucket path.

gsutil ls

Command output

  • Now, delete the artifacts bucket.
gsutil rm -r gs://artifacts.${PROJECT_ID}

Command output

Removing gs://artifacts.<PROJECT_ID>
  • Delete the cloudbuild bucket
gsutil rm -r gs://${PROJECT_ID}_cloudbuild/

Command output

Removing gs://<PROJECT_ID>_cloudbuild/...

Of course, you can also delete the entire project but you would lose any billing setup you have done (disabling project billing first is required). Additionally, deleting a project will only happen after the current billing cycle ends.

13. Congratulations!


You now know how to run a Slack bot on Cloud Run!

We've only scratched the surface of this technology and we encourage you to explore further with your own Cloud Run deployments.

What we've covered

  • Building a Node.js Slack bot Docker image.
  • Hosting a Docker Image on Cloud Run.
  • Creating and Accessing Secrets in the Google Cloud Secret Manager.

Next Steps

Learn More