Cloud Spanner
gRPC · port 9010 · orchestrated
Orchestrated service. Spanner runs as a Docker container managed by localgcp. Requires Docker (Docker Desktop, OrbStack, or Colima). The container starts lazily on first connection, not at startup.
Quick start
$ localgcp up --services=spanner
The first time your code connects to port 9010, localgcp pulls the official Google Spanner emulator image and starts it. Subsequent startups reuse the cached image (~3s).
Go SDK example
Connect to the emulator, create an instance, create a database, and run a query:
package main import ( "context" "fmt" "log" "os" spanner "cloud.google.com/go/spanner" database "cloud.google.com/go/spanner/admin/database/apiv1" databasepb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb" instance "cloud.google.com/go/spanner/admin/instance/apiv1" instancepb "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb" "google.golang.org/api/iterator" ) func main() { // Point the SDK at the localgcp emulator os.Setenv("SPANNER_EMULATOR_HOST", "localhost:9010") ctx := context.Background() project := "my-project" // Create an instance instanceAdmin, err := instance.NewInstanceAdminClient(ctx) if err != nil { log.Fatal(err) } defer instanceAdmin.Close() op, err := instanceAdmin.CreateInstance(ctx, &instancepb.CreateInstanceRequest{ Parent: fmt.Sprintf("projects/%s", project), InstanceId: "my-instance", Instance: &instancepb.Instance{ DisplayName: "My Instance", Config: fmt.Sprintf("projects/%s/instanceConfigs/emulator-config", project), NodeCount: 1, }, }) if err != nil { log.Fatal(err) } if _, err = op.Wait(ctx); err != nil { log.Fatal(err) } // Create a database with a table databaseAdmin, err := database.NewDatabaseAdminClient(ctx) if err != nil { log.Fatal(err) } defer databaseAdmin.Close() dbOp, err := databaseAdmin.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{ Parent: fmt.Sprintf("projects/%s/instances/my-instance", project), CreateStatement: "CREATE DATABASE `my-db`", ExtraStatements: []string{ `CREATE TABLE Users ( UserId INT64 NOT NULL, Name STRING(1024), Email STRING(1024), ) PRIMARY KEY (UserId)`, }, }) if err != nil { log.Fatal(err) } if _, err = dbOp.Wait(ctx); err != nil { log.Fatal(err) } // Insert a row and query it db := fmt.Sprintf("projects/%s/instances/my-instance/databases/my-db", project) client, err := spanner.NewClient(ctx, db) if err != nil { log.Fatal(err) } defer client.Close() _, err = client.Apply(ctx, []*spanner.Mutation{ spanner.InsertOrUpdate("Users", []string{"UserId", "Name", "Email"}, []interface{}{1, "Alice", "[email protected]"}, ), }) if err != nil { log.Fatal(err) } iter := client.Single().Query(ctx, spanner.Statement{ SQL: "SELECT UserId, Name, Email FROM Users", }) defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { break } if err != nil { log.Fatal(err) } var userId int64 var name, email string if err := row.Columns(&userId, &name, &email); err != nil { log.Fatal(err) } fmt.Printf("User: %d %s %s\n", userId, name, email) } }
Environment variable
The Spanner client libraries check SPANNER_EMULATOR_HOST automatically. Set it before running your code:
$ export SPANNER_EMULATOR_HOST=localhost:9010
Or let localgcp set all environment variables at once:
$ eval $(localgcp env)
How it works
localgcp uses a lazy TCP proxy to manage the Spanner emulator container:
- Port binds instantly -- localgcp listens on port 9010 as soon as it starts, so your code never gets "connection refused."
- Container starts on first request -- the first TCP connection triggers
docker runwith the official Spanner emulator image. localgcp waits for the container to become healthy before forwarding. io.Copyproxies all traffic -- once the container is up, localgcp splices the connection through to the emulator with zero overhead for subsequent requests.- Ctrl+C stops the container -- when localgcp shuts down, it stops and removes the Docker container automatically.
Pre-fetching images
The first cold start pulls the emulator image, which can take 30-60s on a slow connection. To avoid that delay, pre-fetch the image:
# Coming soon $ localgcp pull
Or pull manually:
$ docker pull gcr.io/cloud-spanner-emulator/emulator:1.5.23
Not yet supported
- Data persistence across restarts
- Spanner REST admin API (
:9020)