Updated Nov 5, 20265 min read

How to Connect Cloud Run to Memorystore for Redis

Reach a private Memorystore instance from a Go service on Cloud Run, with VPC egress, AUTH and TLS, or let the cache be provisioned from code.

Memorystore for Redis is Google Cloud's managed Redis, and it is a common next step once a Cloud Run service starts reading the same Postgres rows thousands of times a minute. Connecting the two is where it slows down: Memorystore has only a private IP, Cloud Run sits outside your VPC by default, and production setups add an AUTH string and TLS with a custom certificate authority.

This guide adds a cache-aside product cache to a Go API on Cloud Run. It shows the version where Encore (a tool that works alongside your framework) declares the cache in code and provisions Memorystore, then the manual setup with Direct VPC egress and go-redis.

Cloud Run to Memorystore options at a glance

EncoreManual setup with go-redis
Instance creationFrom the code, on deploygcloud, Terraform or console
Network pathPrivate, inside the environment's VPCDirect VPC egress or VPC connector
AUTHEnabled, credentials managed for youAUTH string stored in Secret Manager
Key naming and typesTyped keyspacesStrings you format and parse
Local developmentIn-memory Redis under encore runLocal Redis container or a tunnel

Each option is covered below, starting with Encore.

Declaring a cache cluster that runs on Memorystore with Encore

Encore is a tool that works alongside your framework for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. Each cache cluster in the code becomes a Memorystore for Redis instance on GCP, set up for high availability with Redis authentication, and an ElastiCache cluster on AWS.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.

The catalog service declares the cluster and a typed keyspace for products:

// catalog/catalog.go package catalog import ( "context" "errors" "time" "encore.dev/beta/errs" "encore.dev/storage/cache" "encore.dev/storage/sqldb" ) // Cloud SQL on GCP, RDS on AWS, a local Postgres under encore run. var db = sqldb.NewDatabase("catalog", sqldb.DatabaseConfig{ Migrations: "./migrations", }) // Memorystore for Redis on GCP, ElastiCache on AWS. var cluster = cache.NewCluster("catalog-cache", cache.ClusterConfig{ EvictionPolicy: cache.AllKeysLRU, }) type Product struct { ID int64 `json:"id"` Name string `json:"name"` PriceCents int `json:"priceCents"` SKU *string `json:"sku"` Cached bool `json:"cached"` } // Cached products expire after five minutes. var products = cache.NewStructKeyspace[int64, Product](cluster, cache.KeyspaceConfig{ KeyPattern: "product/:key", DefaultExpiry: cache.ExpireIn(5 * time.Minute), })

The migration in catalog/migrations creates the products table the queries use:

-- catalog/migrations/1_create_products.up.sql CREATE TABLE products ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, price_cents INTEGER NOT NULL, sku TEXT NOT NULL );

The read endpoint checks the cache first and falls through to Postgres on a miss:

//encore:api public method=GET path=/products/:id func Get(ctx context.Context, id int64) (*Product, error) { // Cache-aside: serve from Redis when we can. if p, err := products.Get(ctx, id); err == nil { p.Cached = true return &p, nil } else if !errors.Is(err, cache.Miss) { return nil, err } var p Product err := db.QueryRow(ctx, ` SELECT id, name, price_cents, sku FROM products WHERE id = $1`, id, ).Scan(&p.ID, &p.Name, &p.PriceCents, &p.SKU) if errors.Is(err, sqldb.ErrNoRows) { return nil, &errs.Error{Code: errs.NotFound, Message: "product not found"} } else if err != nil { return nil, err } if err := products.Set(ctx, id, p); err != nil { return nil, err } return &p, nil }

Writes drop the cached copy with products.Delete(ctx, id) after the UPDATE, so the next read loads the new price. The keyspace fixes both the key format and the value type, so a typo in a key or a mismatched struct is a compile error, not a cache miss in production.

Under encore run, Encore uses an in-memory Redis implementation and a local Postgres, so the cache path is testable on a laptop with no VPC or tunnel. On deploy to GCP, Encore creates the Memorystore instance in the environment's private network and connects the Cloud Run service to it. The caching docs cover the other keyspace types, including counters with NewIntKeyspace for rate limiting.

That matters when a coding agent writes the cache layer: it works against typed keyspaces it can compile and run locally. Cloud access sits with Encore's service identity, not the agent.

The infrastructure provisioning docs describe what is created in each environment type.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

Manual setup: Direct VPC egress and go-redis

Create the instance with AUTH and in-transit encryption enabled:

gcloud redis instances create catalog-cache \ --region=europe-west1 --tier=standard --size=1 \ --network=default \ --enable-auth \ --transit-encryption-mode=server-authentication gcloud redis instances get-auth-string catalog-cache --region=europe-west1

Store the AUTH string and the instance's server CA certificate in Secret Manager. Then deploy the Cloud Run service with Direct VPC egress on the same network, so it can reach the private IP:

gcloud run deploy catalog-api \ --image=europe-west1-docker.pkg.dev/my-project/apps/catalog-api:latest \ --network=default --subnet=default \ --vpc-egress=private-ranges-only \ --set-env-vars=REDIS_ADDR=10.0.0.3:6378 \ --set-secrets=REDIS_AUTH=catalog-redis-auth:latest,REDIS_CA=catalog-redis-ca:latest

In Go, the client needs the AUTH string as the password and a TLS config that trusts the instance's CA, since the certificate is not signed by a public authority, as Google's in-transit encryption guide explains:

pool := x509.NewCertPool() pool.AppendCertsFromPEM([]byte(os.Getenv("REDIS_CA"))) rdb := redis.NewClient(&redis.Options{ Addr: os.Getenv("REDIS_ADDR"), Password: os.Getenv("REDIS_AUTH"), TLSConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}, })

From there, cache-aside is the same logic as above, with rdb.Get returning redis.Nil on a miss and your own JSON marshaling of the product. Note the port: instances with in-transit encryption listen on 6378 instead of 6379. Google also rotates the server CA, so the certificate in Secret Manager needs updating when that happens.

Good to know

Google now also offers Memorystore for Valkey, an open source fork of Redis, alongside Memorystore for Redis. The connection pattern from Cloud Run is the same: private networking, then a Redis-compatible client.

How to choose

Pick Encore when you want the cache declared next to the code that uses it, Memorystore provisioned with AUTH in your own GCP project, and the cache path tested locally under encore run. The same code runs on ElastiCache on AWS.

Pick the manual setup when the Memorystore instance is shared with other systems, or when you need Redis commands and data structures beyond what typed keyspaces expose.

Frequently asked questions

How do I connect Cloud Run to Memorystore for Redis?

Memorystore only has a private IP, so the Cloud Run service needs Direct VPC egress or a Serverless VPC Access connector on the same VPC network. Then connect with a Redis client using the instance's host, port, AUTH string and, if in-transit encryption is on, its CA certificate. With Encore, you declare cache.NewCluster("catalog-cache", cache.ClusterConfig{EvictionPolicy: cache.AllKeysLRU}) and a keyspace in Go, and Encore provisions Memorystore with AUTH and connects the service on deploy.

Can I connect to Memorystore from my laptop?

Not directly, since the instance has no public address. Teams use a bastion VM with port forwarding or run Redis locally during development.

What happens when the cache is unavailable?

That is a choice in your code. A cache-aside read can fall back to the database on any cache error, which keeps the endpoint up at the cost of more database load, or return the error so the problem is visible.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders