LocalCloud in GitHub Actions
Run your Google Cloud application tests against a LocalCloud Docker environment in a GitHub Actions job. Your application uses standard Google Cloud SDKs with local endpoint settings. Use this for pull-request checks, continuous integration, staging-like application tests, or an AI coding agent that needs a cloud environment while it works.
LocalCloud runs independently as a Docker image. The Setup LocalCloud action is a convenience that starts the image, waits for selected services, creates a local project, exports SDK settings, and cleans up its own container. The CLI is optional.
Choose a workflow
Start with PR tests, staging validation, or Feast. See the integration directory for SDKs, Terraform, and MCP.
| Route | Who starts the image? | Who configures SDKs? | Who removes the container? |
|---|---|---|---|
| Setup action | The action | The action exports runtime settings | The action’s post-job hook |
| GitHub service container plus setup action | GitHub | The action connects to your service | GitHub |
| GitHub service container without setup action | GitHub | Your workflow reads the runtime settings | GitHub |
All examples below run application code on an Ubuntu VM runner. The image has a 4 GiB memory limit; allow additional memory and disk for your application. Automatic setup currently targets Linux Docker runners. A job with container: uses different networking and needs separate workflow tests; see container jobs.
Quick start: run a pull-request test
Save this complete workflow as .github/workflows/localcloud.yml. It starts Storage, Pub/Sub, and BigQuery, then verifies a query with Google’s Python BigQuery SDK. No Google Cloud account, service-account key, or billing project is required for this check.
name: LocalCloud integration test
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
integration:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.0.0
with:
python-version: '3.12'
- name: Start LocalCloud
id: localcloud
uses: cloud-orbitor/setup-localcloud@v0.1.0
with:
services: gcs,pubsub,bigquery
project-id: localcloud-ci
- name: Install the SDK
run: python -m pip install google-cloud-bigquery==3.46.1
- name: Verify a local query
run: |
python - <<'PY'
import os
from google.auth.credentials import AnonymousCredentials
from google.cloud import bigquery
client = bigquery.Client(
project=os.environ['GOOGLE_CLOUD_PROJECT'],
credentials=AnonymousCredentials(),
client_options={'api_endpoint': os.environ['BIGQUERY_EMULATOR_HOST']},
)
try:
rows = list(client.query('SELECT 1 AS ok', timeout=30).result(timeout=30))
assert rows[0].ok == 1
print('LocalCloud BigQuery query passed')
finally:
client.close()
PY
To run your application’s suite, replace the last two steps with your dependency installation and test commands, such as python -m pip install -r requirements.txt and python -m pytest. Keep setup before the test process. The exported variables apply to subsequent steps in the same job, not other jobs.
The action dynamically publishes the image’s TCP ports on loopback and rewrites generated endpoints to match. Read the runtime-url, container-id, and project-id outputs when later steps need them. Its post-job hook removes its own container and anonymous volume after success or failure. It leaves an attached runtime with its owner.
Validate a staging build
Each job starts a fresh runtime for application tests before staging deployment. Save this as .github/workflows/staging-validation.yml; adapt requirements.txt and tests/integration/ to your repository. Test fixtures should create representative resources through the SDKs.
name: Validate staging build
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
staging-check:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.0.0
with:
python-version: '3.12'
- name: Start the test environment
uses: cloud-orbitor/setup-localcloud@v0.1.0
with:
services: gcs,pubsub,bigquery
project-id: localcloud-staging
- run: python -m pip install -r requirements.txt
- name: Run application integration tests
run: python -m pytest tests/integration/ --junitxml=localcloud-results.xml
- name: Save test results
if: always()
uses: actions/upload-artifact@v4
with:
name: localcloud-staging-results
path: localcloud-results.xml
if-no-files-found: ignore
Gate a subsequent deployment job with needs: staging-check; use GitHub environments for its approvals and secrets. This job needs no Google Cloud credentials. LocalCloud lasts for the job; validate managed IAM, networking, quotas, and performance against real Google Cloud staging before production.
For a persistent test environment, use existing-runtime mode and manage its state separately. Other languages use their normal toolchain and tests (./gradlew test, go test ./..., or npm test) with SDK configuration.
Integrate an existing project: Feast
The Feast integration proposed in PR #6939 uses Storage for the registry, Bigtable for the online store, and BigQuery for the offline store: services: gcs,bigtable,bigquery. The PR is under review.
Its refreshed GitHub workflow uses the pinned published setup-localcloud action, then runs the project’s build and service tests against its exported SDK endpoints. Run it manually through workflow_dispatch; PR runs require repository Actions variable LOCALCLOUD_CI_ENABLED=true. Workflow comments and the PR description link to this guide for setup.
For laptop testing, retain python3 scripts/localcloud-test.py and add --full-suite for the recorded regression scope. The laptop launcher owns its runtime; in CI, GitHub starts the native service container and the setup action attaches with runtime-url. Choose one owner per test environment. Feast’s BigQuery endpoint wiring illustrates the SDK adaptation; keep each project’s own test commands and service scope.
Select the services your application needs
services is the exact comma-separated set to start. It replaces the default selection; include every service your test needs. It accepts LocalCloud service IDs, not Google API domain names or display names.
- uses: cloud-orbitor/setup-localcloud@v0.1.0
with:
services: gcs,pubsub,bigquery,bigtable,spanner,kms,secretmanager
project-id: localcloud-ci
timeout: '180'
Changing this list does not require an action release. Always set it explicitly: published v0.1.0 defaults to gcs,pubsub,firestore. The quick start selects gcs,pubsub,bigquery for its query test. Firestore is supported; select firestore when your tests need it and allow memory for its runtime.
The reference below uses the site’s service catalog. Follow each guide for SDK connection details, supported operations, and runtime requirements. The catalog describes LocalCloud; it does not mean every service workflow has been tested through this action.
| Service guide | ID for services | Workflow notes |
|---|---|---|
| Firestore | firestore | See the service guide for SDK setup and operations. |
| Bigtable | bigtable | See the service guide for SDK setup and operations. |
| Spanner | spanner | See the service guide for SDK setup and operations. |
| AlloyDB | alloydb | See the service guide for SDK setup and operations. |
| Memorystore (Redis/Valkey) | memorystore | See the service guide for SDK setup and operations. |
| Cloud SQL | cloudsql | See the service guide for SDK setup and operations. |
| Pub/Sub | pubsub | See the service guide for SDK setup and operations. |
| Cloud Tasks | cloudtasks | See the service guide for SDK setup and operations. |
| Cloud Scheduler | cloudscheduler | See the service guide for SDK setup and operations. |
| Cloud Workflows | workflows | See the service guide for SDK setup and operations. |
| Cloud Functions (2nd Gen) | cloudfunctions | Function execution requires your own execution target. |
| GKE | gke | Cluster execution requires k3d and Docker; use a configured runtime. |
| Compute Engine | compute | Unsupported locally; do not select for local tests. |
| Cloud Run | cloudrun | Execution requires Docker runtime access; use a configured runtime. |
| Vertex AI | vertexai | Unsupported locally; do not select for local tests. |
| Secret Manager | secretmanager | See the service guide for SDK setup and operations. |
| Cloud IAM | cloudiam | See the service guide for SDK setup and operations. |
| Cloud KMS | kms | See the service guide for SDK setup and operations. |
| Cloud Resource Manager | cloudresourcemanager | See the service guide for SDK setup and operations. |
| Service Usage | serviceusage | See the service guide for SDK setup and operations. |
| Cloud Billing | cloudbilling | See the service guide for SDK setup and operations. |
| Cloud Logging | logging | See the service guide for SDK setup and operations. |
| Cloud Monitoring | monitoring | See the service guide for SDK setup and operations. |
| Cloud Storage | gcs | See the service guide for SDK setup and operations. |
| BigQuery | bigquery | See the service guide for SDK setup and operations. |
| Dataproc | dataproc | Execution requires Docker runtime access; use a configured runtime. |
| Google Sheets | sheets | Read-only auxiliary fixture API. |
See the compatibility matrix for service operations and the service configuration reference for runtime selection.
Docker-backed execution
The automatic setup action currently sets LOCALCLOUD_DOCKER_ACCESS=false and does not mount a Docker socket. Selecting cloudrun, dataproc, or gke alone does not configure their execution dependencies. Provision an appropriate runtime using the relevant service guide, then connect with runtime-url if it exposes canonical ports. Automatic provisioning of these dependencies is tracked in the action issues.
Pin the action and runtime independently
Examples use published v0.1.0. For immutable pinning, use the full commit on its release page; pin other actions similarly. Publishers can move version tags.
The action’s default image is LocalCloud 0.1.5, pinned to the multi-architecture digest used below. To choose another runtime, pass image: agentcloud/localcloud@sha256:<your-verified-digest> under with. Replace the placeholder with a real digest; the action rejects mutable image tags.
A newer runtime works with the existing action when its readiness, project, environment, and port contracts remain compatible. Changed contracts or bundled defaults need an action update.
| Action input | How to use it |
|---|---|
services | Set the complete comma-separated ID list explicitly. |
project-id | Local project to create or select; default localcloud-ci. |
image | Immutable LocalCloud image digest; used when starting Docker. |
timeout | Selected-service readiness wait in seconds; default 120, range 1–1800. |
runtime-url | Leave empty for automatic startup, or supply an existing http://host:5380 runtime with canonical ports. |
Use a native GitHub service container
If you want GitHub to start and stop the image, declare it under jobs.<job>.services. The setup action can configure that existing runtime without taking ownership of it. Save this as a separate workflow:
name: LocalCloud service container
on: [pull_request, workflow_dispatch]
permissions:
contents: read
jobs:
integration:
runs-on: ubuntu-24.04
timeout-minutes: 15
services:
localcloud:
image: agentcloud/localcloud@sha256:88e85b86b294e1e4f71b8c63ec43de79d170946ff39556f0e2709f66aa0d368c
env:
LOCALCLOUD_SERVICES: gcs,pubsub,bigquery
LOCALCLOUD_PROJECT: localcloud-ci
LOCALCLOUD_TELEMETRY: 'false'
LOCALCLOUD_DOCKER_ACCESS: 'false'
ports:
- 5380:5380
- 5382:5382
- 5383:5383
- 5388:5388
options: --memory 4g
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.0.0
with:
python-version: '3.12'
- name: Configure the existing runtime
uses: cloud-orbitor/setup-localcloud@v0.1.0
with:
runtime-url: http://127.0.0.1:5380
services: gcs,pubsub,bigquery
project-id: localcloud-ci
- run: python -m pip install -r requirements.txt
- run: python -m pytest
This workflow assumes your repository contains requirements.txt and pytest tests configured as described below. You can instead use the BigQuery install/query steps from the quick start. Keep the container’s service selection and the action’s selection aligned. Publish any additional listeners your tests need; BigQuery Storage gRPC, for example, also needs port 5389.
Existing-runtime mode currently accepts http://<host>:5380 with canonical service ports. It does not discover custom external port mappings. GitHub removes its service containers at the end of the job. See GitHub’s service container documentation.
Use the image without the action or CLI
Keep the native service declaration above and replace its “Configure the existing runtime” step with this bootstrap step. It waits for the exact selected services, creates the local project if missing, and reads the four environment values needed by this example directly from the runtime. No LocalCloud CLI or setup action is used.
- name: Prepare LocalCloud directly
run: |
python - <<'PY'
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
gateway = 'http://127.0.0.1:5380'
project = 'localcloud-ci'
selected = {'gcs', 'pubsub', 'bigquery'}
deadline = time.monotonic() + 120
def request(path, body=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(gateway + path, data=data,
headers={'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=5) as response:
return json.load(response)
while True:
try:
ready = request('/readiness?services=' + ','.join(sorted(selected)))
available = {s['service_id'] for s in ready.get('services', []) if s.get('ready') is True}
if ready.get('ready') is True and selected <= available:
break
except urllib.error.HTTPError as error:
if error.code != 503:
raise
except (urllib.error.URLError, TimeoutError):
pass
if time.monotonic() >= deadline:
raise SystemExit('Selected LocalCloud services did not become ready')
time.sleep(1)
if not any(p['project_id'] == project for p in request('/projects')):
request('/projects', {'project_id': project, 'display_name': project})
values = request('/env?format=json&project=' + project)
ports = {'STORAGE_EMULATOR_HOST': 5382, 'PUBSUB_EMULATOR_HOST': 5383,
'BIGQUERY_EMULATOR_HOST': 5388}
assert values['GOOGLE_CLOUD_PROJECT'] == project
with open(os.environ['GITHUB_ENV'], 'a') as output:
for key in ['GOOGLE_CLOUD_PROJECT', *ports]:
value = values[key]
assert isinstance(value, str) and not any(c in value for c in '\r\n\0')
if key in ports:
target = urllib.parse.urlsplit(value if '://' in value else 'http://' + value)
assert target.scheme == 'http' and target.hostname in {'localhost', '127.0.0.1'}
assert not target.username and not target.password and target.port == ports[key]
output.write(f'{key}={value}\n')
PY
This bootstrap deliberately covers the three-service example. When selecting other services, also publish their listeners and load their required local settings. The setup action performs general environment export and automatic port mapping for you. Keep the same immutable image and use the runtime API for custom integrations.
Configure your application’s SDKs
The setup action exports values returned by /env?format=json into GITHUB_ENV. They are available to subsequent steps, including your test process or agent process. Do not hard-code ports in automatic mode.
| Client | Configuration |
|---|---|
| Python Cloud Storage | Reads STORAGE_EMULATOR_HOST; pass the local project to storage.Client. |
| Python Pub/Sub | Reads PUBSUB_EMULATOR_HOST; construct the usual publisher/subscriber clients. |
| Python BigQuery | Pass BIGQUERY_EMULATOR_HOST explicitly as client_options.api_endpoint and use AnonymousCredentials, as above. |
| Java Pub/Sub | Configure the exported PUBSUB_EMULATOR_HOST explicitly with a plaintext gRPC channel and a no-credentials provider for both publisher and subscriber clients. |
| Other languages and services | Use the service guide and SDK examples; some need an explicit endpoint, plaintext transport, or local credentials. |
See copyable SDK examples for resource creation, readback, and cleanup. Environment-variable export does not guarantee every Google SDK automatically consumes every setting. Keep missing endpoint variables as errors instead of silently choosing Google defaults. Local tests do not need production Google credentials.
Run an AI coding agent in the job
Start LocalCloud before installing and launching your agent on the same runner. It inherits the SDK environment. Authenticate the AI provider separately, following your agent’s instructions.
Give the agent these repository instructions:
Use this job's GOOGLE_CLOUD_PROJECT and local SDK endpoints. Follow the
LocalCloud service guides and run application tests. Do not start another
runtime, request Google credentials, or fall back to real Google Cloud.
For another container, forward the environment and use reachable service addresses rather than host loopback. Provide the agent guide and service reference as context.
Optional MCP
SDK tests do not require MCP. Published v0.1.0 does not install the CLI or attach an MCP bridge; the default CLI bridge must not be assumed to target its CI container. The unreleased MCP integration below attaches to that runtime explicitly. See the MCP guide for clients and separate write/destructive permissions.
Container jobs and running code inside the image
With container:, native services use their service name and internal ports, such as http://localcloud:5380; test connectivity and SDK configuration for that network.
To run code inside LocalCloud, preserve its entrypoint, wait for readiness, and provision your toolchain before docker exec. Build tools are not guaranteed to be bundled. See manual Docker setup and the unreleased application-container route. Published v0.1.0 did not qualify same-container toolchains.
Troubleshooting and cleanup
| Symptom | Next check |
|---|---|
| Readiness times out | Check the selected IDs, runtime logs, image version, memory, and the service’s execution dependencies. A container being up does not prove readiness. |
| Unknown service | Use an exact ID from the table; a catalog entry marked Unsupported does not run locally. |
| SDK asks for Google credentials | Verify local endpoint variables and the client-specific constructor; Python BigQuery requires explicit anonymous credentials. |
| Application calls a Google endpoint | Check hard-coded URLs and client configuration. The action configures endpoints; it is not an egress firewall. |
| Native service connects but SDK calls fail | Publish the selected service ports and keep canonical ports when using runtime-url. |
| Agent runs in another job or container | Export settings again for that environment and verify its network route; GITHUB_ENV is job-scoped. |
| Self-hosted runner loses a job | Inspect action-owned containers and volumes. Normal post cleanup is best effort when a runner disappears; do not delete unrelated developer resources. |
Docker pull reports toomanyrequests | The registry rejected the pull before LocalCloud started. Authenticate to Docker Hub as described below, connect to an already-running runtime through runtime-url, or select a compatible registry image through image. |
Automatic mode creates an isolated container and anonymous data volume. Logical project names in a shared existing runtime are not a hard isolation boundary. Parallel jobs should use independent runtimes unless shared state is intentional.
Docker Hub pull limits
Public Docker Hub images can hit anonymous pull limits on shared CI runners. If this happens, store your Docker Hub username and access token as repository secrets named DOCKERHUB_USERNAME and DOCKERHUB_TOKEN. These are image-registry credentials, separate from Google Cloud credentials.
For action-managed Docker, place this step before Setup LocalCloud:
- name: Authenticate image pulls
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: printf '%s' "$DOCKERHUB_TOKEN" | docker login --username "$DOCKERHUB_USERNAME" --password-stdin
Native service containers start before workflow steps, so a login step is too late for their initial pull. Add this mapping to jobs.<job>.services.localcloud, alongside image:
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
GitHub documents service-container registry authentication. A registry mirror and reliable anonymous CI distribution are being gathered in the action backlog.
Preview: image reuse, container applications and MCP
The features below have merged to the action’s main branch through PR #8, but are not in a published action release as of 2026-10-10. The examples above use published v0.1.0. The example follows main; replace it with the full reviewed commit from that pull request for immutable pinning. Check the evidence ledger for runtime-specific results.
- name: Start the CI environment
id: localcloud
uses: cloud-orbitor/setup-localcloud@main
with:
services: gcs,pubsub,bigquery
project-id: localcloud-ci
mcp: 'true'
| Preview input | Usage |
|---|---|
image-cache | Path to a restored OCI image layout. The action verifies the requested digest, selected platform and image blobs before importing it. Omit for ordinary Docker image reuse. |
port-map | JSON canonical-to-published port mapping for an existing HTTP runtime, such as {"5380":15380,"5382":15382,"5383":15383,"5388":15388} with runtime-url: http://127.0.0.1:15380. Include every selected endpoint that uses a remapped listener. |
docker-access | Explicitly set 'true' for native Dataproc, Cloud Run or GKE execution. This mounts the runner’s Docker socket and grants the runtime Docker host access. Keep it off for ordinary SDK tests. The runtime reports its own prerequisite requirements. |
mcp | Set 'true' to generate a job-local read-only MCP configuration attached to this runtime and project. No CLI or second runtime is installed. |
Reduce image pulls
The preview checks for the exact pinned image and platform locally before pulling. Persistent Docker runners can reuse that image across jobs; each job still creates fresh application state. Standard GitHub-hosted runners start fresh, so they need a restored cache or registry distribution for reuse across jobs.
The action’s cache workflow demonstrates verified OCI cache creation and restore. Cache keys include the exact image and platform; do not cache credentials or application data. Measure restore/import time before enabling it widely. Native service images start before cache/login steps. For fork PRs, use a public image or a trusted restore-only cache because ordinary repository secrets are unavailable.
Run an application container
The preview exports network and docker-env-file outputs for its owned runtime. Start an application container on that network and pass the environment file:
- name: Test an application image
env:
LOCALCLOUD_NETWORK: ${{ steps.localcloud.outputs.network }}
LOCALCLOUD_ENV_FILE: ${{ steps.localcloud.outputs.docker-env-file }}
run: docker run --rm --network "$LOCALCLOUD_NETWORK" --env-file "$LOCALCLOUD_ENV_FILE" my-application-tests
Build the test image with your dependencies. These settings use internal service addresses; runner loopback addresses cannot reach LocalCloud from another container. The action removes its owned runtime, volume, execution children, and network. Use --rm and failure cleanup for application containers you launch. Existing runtimes retain their owner’s lifecycle. On interrupted self-hosted runners, recover only resources identified by the job’s ownership labels and receipt.
Attach an MCP client
With mcp: 'true', pass steps.localcloud.outputs.mcp-config to a client supporting a mcpServers configuration. The generated stdio bridge targets the same gateway and project as the SDK environment. It leaves global client settings untouched and keeps runtime MCP writes and destructive operations disabled; ordinary SDK writes are governed separately by the application’s client configuration and runtime IAM mode.
For native container jobs, use the service-name gateway and explicitly permit MCP traffic on that isolated job network in the service environment with LOCALCLOUD_MCP_ALLOW_REMOTE: 'true'. Keep both LOCALCLOUD_MCP_WRITE and LOCALCLOUD_MCP_DESTRUCTIVE false. Do not expose that service publicly.
Related integrations
The integration directory links SDKs, CLI, Terraform, MCP, and fixtures. Other CI systems can use Docker-only setup, replacing GITHUB_ENV and GitHub cleanup with their platform’s mechanisms.
Workflow evidence and support
The action’s evidence ledger records exact action/image identities, SDK versions, tested workflows, and automatic cleanup checks. The Storage/Pub/Sub/BigQuery examples demonstrate a small application profile. Other service workflows need checks for the operations your application uses; the full catalog remains available for service selection.
The action adapter is MIT-licensed. The separately distributed runtime is governed by the LocalCloud license. Automatic mode disables runtime telemetry; existing-runtime preferences remain with its owner. See privacy and outbound behavior. Validate production-sensitive behavior against real Google Cloud before deployment.
Report integration issues in cloud-orbitor/setup-localcloud, including the workflow, action commit, image digest, selected services, SDK versions, and sanitized error output. Do not include credentials.
This comment is sent to PostHog. Do not include secrets, personal data, or customer data. See Privacy.