# Serverless Workers on GCP Cloud Run - Go SDK

> For the complete documentation index, see [llms.txt](https://docs.temporal.io/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.

> Run a Temporal Worker on a GCP Cloud Run worker pool using the Go SDK.

> **Pre-release**
> Cloud Run support is in Pre-release, and its APIs may change in backwards-incompatible ways.
> Create a [support ticket](/cloud/support#support-ticket) or contact your account team for access, and
> [sign up for updates](https://temporal.io/pages/serverless-workers-updates) to hear when Cloud Run reaches Public Preview.

On a [GCP Cloud Run worker pool](https://cloud.google.com/run/docs/resource-model#worker-pools), you run a standard long-lived Temporal Worker.
Register Workflows and Activities the same way you would with any other Go Worker, and Temporal Cloud scales the pool up and down as work arrives and drains.

A Cloud Run Worker needs no Cloud Run-specific runtime or handler.
The one addition to a standard Worker is [Worker Versioning](/worker-versioning), which is required for Serverless Workers.

For the end-to-end deployment guide covering the Worker Pool, IAM, and compute configuration, see [Deploy a Serverless Worker on GCP Cloud Run](/production-deployment/worker-deployments/serverless-workers/cloud-run).

## Create a versioned Worker 

Build the Worker as you would any long-running Go Worker, then set `DeploymentOptions` in [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) to declare the Worker Deployment Version and turn versioning on.

The following Worker reads its connection settings and Task Queue from the environment, so the same image can run against any Namespace:

```go
package main

import (
	"log"
	"os"

	"go.temporal.io/sdk/client"
	"go.temporal.io/sdk/contrib/envconfig"
	"go.temporal.io/sdk/worker"
	"go.temporal.io/sdk/workflow"

	"example.com/myapp"
)

func main() {
	c, err := client.Dial(envconfig.MustLoadDefaultClientOptions())
	if err != nil {
		log.Fatalln("Unable to create client", err)
	}
	defer c.Close()

	w := worker.New(c, os.Getenv("TEMPORAL_TASK_QUEUE"), worker.Options{
		DeploymentOptions: worker.DeploymentOptions{
			UseVersioning: true,
			Version: worker.WorkerDeploymentVersion{
				DeploymentName: "my-app",
				BuildID:        "build-1",
			},
		},
	})

	w.RegisterWorkflowWithOptions(myapp.MyWorkflow, workflow.RegisterOptions{
		VersioningBehavior: workflow.VersioningBehaviorPinned,
	})
	w.RegisterActivity(myapp.MyActivity)

	if err := w.Run(worker.InterruptCh()); err != nil {
		log.Fatalln("Unable to start worker", err)
	}
}
```

`DeploymentName` and `BuildID` together identify the Worker Deployment Version. Both values must match the version you create with `temporal worker deployment create-version` in the deployment guide, or the Worker polls under a version the WCI does not manage.

Every Workflow needs a [versioning behavior](/worker-versioning#versioning-behaviors), either `VersioningBehaviorPinned` or `VersioningBehaviorAutoUpgrade`.
Set it per Workflow at registration as shown above, or set `DefaultVersioningBehavior` in `DeploymentOptions` to cover every Workflow on the Worker.
If a Version is set and neither is specified, registration panics with `workflow type does not have a versioning behavior`.

For general Worker setup and options that are not specific to Cloud Run, see [Run a Worker](/develop/go/workers/run-worker-process).

## Configure the Temporal connection 

The `envconfig` package loads [Temporal Client](/develop/go/client/temporal-client) configuration from environment variables and an optional TOML config file, so the Worker code carries no Namespace or credentials.
Set the non-secret values as environment variables on the Worker Pool, and mount the Temporal Cloud API key or TLS material from Secret Manager.
For the full list of supported variables, the config file format, and profiles, see [Environment configuration](/develop/environment-configuration).

`MustLoadDefaultClientOptions` panics if the configuration is invalid. To handle a bad configuration yourself, use `envconfig.LoadDefaultClientOptions` and check the returned error.

## Keep Activities safe across scale-in 

The WCI decides when to remove an instance from Task Queue activity, not from what an individual instance is doing.
An instance running a long Activity can be stopped mid-execution.

Use [Activity Heartbeats](/develop/go/activities/timeouts#activity-heartbeats) so a retry resumes from the last recorded progress instead of starting over:

```go
func MyActivity(ctx context.Context, input MyInput) (string, error) {
	for i := range input.Items {
		activity.RecordHeartbeat(ctx, i)
		// ... process input.Items[i]
	}
	return "done", nil
}
```

For how scale-in decisions are made, see [Serverless Workers on GCP Cloud Run](/serverless-workers/cloud-run#lifecycle).

## Configure OpenTelemetry 

Run an OpenTelemetry Collector as a sidecar in the Worker Pool. The Cloud Run OpenTelemetry plugin exports metrics and traces by OTLP gRPC to the Collector at `localhost:4317`. By default, it derives the service name from `OTEL_SERVICE_NAME`, `CLOUD_RUN_WORKER_POOL`, or `K_SERVICE`.

Create the plugin before connecting, then add it to the Client options. Client plugins that implement `worker.Plugin` also apply to Workers created from that Client:

<!--SNIPSTART go-cloud-run-worker {"selectedLines": ["27-44"]}-->
[gcp/cloudrun/otel/worker/main.go](https://github.com/temporalio/samples-go/blob/gcp-cloud-run-otel/gcp/cloudrun/otel/worker/main.go)
```go
// ...
	otelPlugin, err := otel.NewPlugin(ctx, otel.PluginOptions{})
	if err != nil {
		log.Fatalln("Unable to create OpenTelemetry plugin", err)
	}

	// Load the Temporal connection from the environment (see temporal.toml or the
	// TEMPORAL_* environment variables) and install the plugin. Client plugins
	// that also implement worker.Plugin are applied to workers automatically.
	clientOptions, err := envconfig.LoadDefaultClientOptions()
	if err != nil {
		log.Fatalln("Unable to load Temporal client options", err)
	}
	clientOptions.Plugins = append(clientOptions.Plugins, otelPlugin)

	c, err := client.Dial(clientOptions)
	if err != nil {
		log.Fatalln("Unable to create Temporal client", err)
	}
```
<!--SNIPEND-->

Configure the Collector sidecar to receive OTLP gRPC on `localhost:4317`, export traces to Google Cloud, and export metrics to Google Managed Service for Prometheus:

<!--SNIPSTART go-cloud-run-worker-otel-collector-config-->
[gcp/cloudrun/otel/otel-collector-config.yaml](https://github.com/temporalio/samples-go/blob/main/gcp/cloudrun/otel/otel-collector-config.yaml)
```yaml
# Google-Built OpenTelemetry Collector configuration for a Cloud Run worker pool
# sidecar. The Temporal worker exports OTLP gRPC to localhost:4317; this collector
# adds GCP resource attributes and exports to Google Cloud.
#
# Metrics use googlemanagedprometheus with NO batch processor: batching can merge
# periodic and forced-shutdown snapshots of the same cumulative series into a
# single Google Monitoring write, which Managed Service for Prometheus rejects as
# duplicate data. Traces may be batched independently.
receivers:
    otlp:
        protocols:
            grpc:
                endpoint: localhost:4317

processors:
    # Guard the sidecar against unbounded memory growth.
    memory_limiter:
        check_interval: 1s
        limit_percentage: 65
        spike_limit_percentage: 20
    # Detect Google Cloud resource attributes (project, region, revision, ...).
    resourcedetection:
        detectors: [gcp]
        timeout: 10s
    # Batch is used ONLY for traces.
    batch:
        send_batch_size: 200
        timeout: 5s

exporters:
    debug:
    googlemanagedprometheus:
    googlecloud:

extensions:
    # Health check used as the container startup probe. Bind on all interfaces
    # so the Cloud Run startup probe can reach it.
    health_check:
        endpoint: 0.0.0.0:13133

service:
    extensions: [health_check]
    pipelines:
        # No batch processor in the metrics pipeline.
        metrics:
            receivers: [otlp]
            processors: [memory_limiter, resourcedetection]
            exporters: [googlemanagedprometheus, debug]
        traces:
            receivers: [otlp]
            processors: [memory_limiter, resourcedetection, batch]
            exporters: [googlecloud, debug]
    telemetry:
        logs:
            level: info
```
<!--SNIPEND-->

On shutdown, stop the Worker and call `otelPlugin.Shutdown` with a deadline shorter than Cloud Run's termination window so telemetry can flush. For a Collector configuration that exports traces to Google Cloud and metrics to Google Managed Service for Prometheus, see the [Go Cloud Run OpenTelemetry sample](https://github.com/temporalio/samples-go/pull/528).

## Set a Worker identity 

Use the Cloud Run Worker Id plugin to identify each Worker instance as `<instance-id>@<revision>`. The plugin reads Cloud Run environment variables and instance metadata once when the Client connects, then Workers created from that Client inherit the identity.

<!--SNIPSTART go-cloud-run-worker-id {"selectedLines": ["30-40"]}-->
[gcp/cloudrun/workerid/worker/main.go](https://github.com/temporalio/samples-go/blob/main/gcp/cloudrun/workerid/worker/main.go)
```go
// ...
	plugin := workerid.NewPlugin(workerid.PluginOptions{})
	clientOptions := client.Options{
		HostPort:  getenv("TEMPORAL_ADDRESS", client.DefaultHostPort),
		Namespace: getenv("TEMPORAL_NAMESPACE", client.DefaultNamespace),
		Plugins:   []client.Plugin{plugin},
	}

	c, err := client.Dial(clientOptions)
	if err != nil {
		log.Fatalf("Unable to create Temporal client (is this running on a Cloud Run worker pool or service?): %v", err)
	}
```
<!--SNIPEND-->

The plugin requires the Cloud Run metadata server, so it fails when the Worker runs outside Cloud Run. For a complete example, see the [Go Cloud Run Worker Id sample](https://github.com/temporalio/samples-go/pull/531).
