Skip to main content

Deploy a Countdown Workflow to Google Cloud Run

In this tutorial you will create a countdown notification system using Resonate and ntfy.sh, deploying components to Google Cloud Run and Cloud Functions.

  • Deploy a Resonate Server to Google Cloud Run.
  • Deploy the countdown workflow as a Cloud Function.
  • Subscribe to a ntfy.sh topic to receive the countdown notifications.
  • Trigger the countdown using the Resonate CLI.

The finished setup lets you kick off a long-running countdown that posts a message every x minutes.

What makes the result of this tutorial really cool?

  1. The result is a long-running "logical" process that exists over short-lived "physical" executions, specifically in the context of a Google Cloud Function environment that expects the function to complete quickly.
  2. The same mechanics that enable long-running "logical" processes over short-lived "physical" executions are the same mechanics that make the countdown a Durable Execution.
  3. The simple procedural code you write locally is the same code that runs in the cloud.

Prerequisites

  • A Google Cloud project with Cloud Run, Cloud Functions (2nd gen), and Secret Manager enabled.
  • The gcloud CLI authenticated for your project.
  • The Resonate CLI (brew install resonatehq/tap/resonate), more installation options are in the Run a Server guide.
  • Node.js 20+ locally (for bundling the Cloud Function).
  • A ntfy.sh topic or any webhook endpoint that should receive countdown messages.

System architecture

You'll use the GCloud CLI to deploy a Resonate Server as a Cloud Run service and a countdown workflow as a Cloud Function (serverless function)

The Resonate CLI enables you to invoke the countdown workflow via the Resonate Server.

Architecture diagram showing Resonate Server on Cloud Run communicating with Cloud Function

The Resonate Server sends the invoke message to the Cloud Function, basically calling it like a webhook. The Cloud Function runs, storing state (checkpointing) via promises in the Resonate Server. Whenever the Cloud Function runs, it starts from the beginning of the countdown function, replaying everything up to the current point, but using the stored state to skip over already-completed steps.

As you will see below, Resonate makes it incredible straight forward to write a locially long-running worklow like this that can pause and resume without holding onto compute resources.

Deploy the Resonate Server

To deploy the Resonate Server on Cloud Run, you will use the GCloudud CLI to create a new service within your project using the official Resonate container image (resonatehqio/resonate).

Auth in development

The @resonatehq/gcp package that Resonate provides for Cloud Functions does not yet support auth.

Auth support is planned, but if you have an immediate need please reach out on Discord or GitHub.

So, for this tutorial, you will need to deploy both the Resonate Server and the countdown Cloud Function with --allow-unauthenticated. And it is recommended not to keep the services live longer than needed to complete the tutorial.

When there is auth support, this tutorial will be updated accordingly.

You will need to run the following command twice:

The first time you deploy the Resonate Server, you will not yet have a server URL to pass to the --system-url argument. After the first deployment, you will get the service URL from the output and then re-deploy the Resonate Server with that URL.

First deployment

Run the following command, replacing <your-region> with your desired GCP region (e.g., us-central1):

Deploy the Resonate Server as a Cloud Run service for the first time
Shell
gcloud run deploy resonate-server \
--image=resonatehqio/resonate \
--region=<your-region> \
--platform=managed \
--allow-unauthenticated \
--port=8001 \
--args="serve"
--scaling=1

If this is your first time deploying a Cloud Run service in this project, you may be prompted to enable some services. Enter Y to continue to enable them when prompted.

It then may take several minutes to build and deploy the service. After it is deployed, tou should see output containing the service URL.

Sample output after deploying the Resonate Server
Shell
Deploying container to Cloud Run service [resonate-server] in project [<your-project>] region [<your-region>]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [resonate-server] revision [<revision>] has been deployed and is serving 100 percent of traffic.
Service URL: <your-service-url>

Now, redeploy the Resonate Server this time setting the --system-url flag with the Service URL. This lets the Resonate Server know its public address.

Redeploy the Resonate Server with the correct system URL
Shell
gcloud run deploy resonate-server \
--image=resonatehqio/resonate \
--region=<your-region> \
--platform=managed \
--allow-unauthenticated \
--port=8001 \
--args="serve","--system-url","your-server-url"," \
--scaling=1

Verify the Resonate Server's API is reachable

Verify the Resonate Server is reachable
Shell
curl i <your-service-url>/promises

Expect to see this response:

Expected error response from Resonate Server
JSON
{
"error": {
"code": 40000,
"message": "The request is invalid",
"details": [
{
"@type": "FieldValidationError",
"message": "the field id is required",
"domain": "request",
"metadata": { "url": "https://docs.resonatehq.io/operate/errors#40000" }
}
]
}
}

This indicates the Resonate Server APIs are reachable.

The Cloud Function will need to talk back to these endpoints; confirming connectivity now prevents later debugging surprises.

Now that you validated HTTPS routing and port mapping on your Cloud Run service you will develop the countdown workflow that we will deploy as the Cloud Function.

Countdown workflow

Clone the countdown example repository, and deploy the code to Cloud Functions.

Clone the countdown example repository
Shell
git clone [email protected]:resonatehq-examples/example-countdown-ts-gcp.git
cd example-countdown-ts-gcp

The Functions runtime automatically installs dependencies from package.json before deploying your handler.

Resonate workflows are plain generator functions. Registering countdown and exposing the handler means Cloud Functions can receive webhook calls from the Resonate Server and resume the workflow exactly where it left off.

You built the worker code that Cloud Run will execute each time the Resonate Server needs to advance the countdown.

Deploy the Cloud Function

Deploy the countdown worker as an HTTP-triggered Cloud Function. Pass the Resonate Server URL via environment variable.

Deploy the countdown workflow as a Cloud Function
Shell
gcloud functions deploy countdown-workflow \
--gen2 \
--region=<your-region> \
--runtime=nodejs22 \
--source=. \
--entry-point=handler \
--trigger-http \
--allow-unauthenticated \
--set-env-vars=\
RESONATE_URL=<your-resonate-server-url>

If this is your first time deploying a Cloud Function in this project, you may be prompted to enable some services. Enter Y to continue to enable them when prompted.

It then may take several minutes to build and deploy the function.

You will see output similar to:

Sample output after deploying the countdown workflow as a Cloud Function
Shell
Preparing function...done.
X Updating function (may take a while)...
✓ [Build] Logs are available at ...
[Service]
. [ArtifactRegistry]
. [Healthcheck]
. [Triggercheck]
Completed with warnings:
[INFO] A new revision will be deployed serving with 100% traffic.
You can view your function in the Cloud Console here: ...

buildConfig:
...
serviceConfig:
...
uri: <this-is-the-url-you-need>
state: ACTIVE
updateTime: '2025-11-11T18:56:20.810738211Z'
url: ...

Grab the serviceConfig uri value from the output.

Once deployment finishes, note the Function URL and export it:

Shell
export RESONATE_WORKER_URL="https://<function-url>"

Cloud Functions Gen 2 share the same underlying infrastructure as Cloud Run, so Resonate can call back into your workflow over HTTPS just like any other webhook.

You published the countdown worker as an HTTP endpoint that the Resonate Server can invoke whenever it needs to resume a workflow step.

Subscribe to a topic

If you haven't already, subscribe to your ntfy.sh topic to receive countdown notifications.

Subscribing to a topic that doesn't exist creates it, so you can use any topic name you like.

Either via the web interface:

ntfy.sh web interface

Or using the command line:

Shell
ntfy subscribe <your-topic>

Trigger a countdown

Start a long-running countdown from your terminal using the Resonate CLI invoke command. The invoke command creates a new promise on the Resonate Server. Additionally, it causes the creation of a Task which your Cloud Function worker will claim, telling it to execute the countdown workflow.

Shell
resonate invoke countdown-workflow-1 \
--func countdown \
--arg 5 \
--arg 1 \
--arg https://ntfy.sh/countdown-notification \
--server https://resonate-server-pg3jsgl5sq-nn.a.run.app \
--target https://countdown-workflow-pg3jsgl5sq-nn.a.run.app \

The previous command invokes the countdown function with three arguments, and corresponds the function invocation to the countdown-workflow-1 promise on the Resonate Server.

  • countdown-workflow-1: unique promise ID.
  • --func countdown: which registered function to run.
  • --arg 5: countdown starts at 5.
  • --arg 1: wait 1 minute between notifications.
  • --arg https://ntfy.sh/<your-topic>: destination URL (replace with your ntfy topic or webhook).
  • --server <resonate-server-url>: the Resonate Server URL.
  • --target <cloud-function-url>: the Cloud Function URL. (this tells the Resonate Server where to send messages).

Watch the ntfy topic to see Countdown: 5, Countdown: 4, …, 🚀 Liftoff! roll in. You can use the promise ID to inspect the workflow via resonate promises get countdown-workflow-1 or in the web browser at https://<your-resonate-server>/promises?id=countdown-workflow-1.

Congratulations! You have a durable countdown application deployed to Google Cloud Run.

While you will be receiving countdown notifications, you can also visualize the countdown execution using the Resonate CLI tree command.

Use the CLI to visualize the countdown workflow execution
Shell
resonate tree countdown.1 --server <resonate-server-url>

Example output (while waiting on the second sleep):

Sample output of the countdown workflow execution tree
Shell
countdown.1
├── countdown.1.0 🟢 (run)
├── countdown.1.1 🟢 (sleep)
├── countdown.1.2 🟢 (run)
└── countdown.1.3 🟡 (sleep)