Skip to content
cloudemu

Documentation

Standalone Server

Run cloudemu as a long-lived server and point real SDK clients at it over the network

Run cloudemu as a real server and point any SDK — in any language — at it over the network. This is the out-of-process counterpart to the in-process SDK-Compatible Server: same wire protocols, but a long-lived process instead of a httptest.NewServer inside your test.

Use it for local development, a shared dev environment, or a test suite that runs out-of-process.

one long-lived process · stable ports

SDK clientany languagecloudemu servenetwork:4566aws:4568azure:4569gcp:4570k8s:4571oci
FIG.One long-lived server; stable endpoints per cloud.

Run it#

go install github.com/stackshy/cloudemu/v2/cmd/cloudemu@latest
cloudemu serve

Or straight from a checkout:

go run ./cmd/cloudemu serve

On start it prints the live endpoints:

cloudemu — standalone server
────────────────────────────
  AWS         http://127.0.0.1:4566
  Azure       https://127.0.0.1:4568   (self-signed TLS)
  GCP         http://127.0.0.1:4569
  Kubernetes  https://127.0.0.1:4570  (self-signed TLS)
  OCI         http://127.0.0.1:4571

Docker#

No Go toolchain needed — pull the published image (it runs serve --host 0.0.0.0):

docker run --rm -p 4566:4566 -p 4568:4568 -p 4569:4569 -p 4570:4570 -p 4571:4571 ghcr.io/stackshy/cloudemu:latest

Or bring up the whole emulated cloud with the example compose file:

docker compose up

Testcontainers (Go)#

Go test suites can start and stop the container automatically with the Testcontainers module — a separate module, so it doesn't add Docker deps to your app:

ctr, _ := cloudemu.Run(ctx)
defer ctr.Terminate(ctx)

endpoint, _ := ctr.AWSEndpoint(ctx) // point aws-sdk-go-v2 here
ctr.Reset(ctx)                      // clean slate between tests

Ports#

ProviderDefaultProtocolNotes
AWS4566HTTPsame port LocalStack uses
Azure4568HTTPSthe ARM SDK requires TLS
GCP4569HTTP
Kubernetes4570HTTPSshared data-plane for EKS / AKS / GKE (self-signed TLS)
OCI4571HTTPevery OCI service on one listener

Override any of them with --aws-port, --azure-port, --gcp-port, --k8s-port, --oci-port. All four providers start by default; start a subset with --providers=aws,gcp, and bind an interface with --host 0.0.0.0 (the default 127.0.0.1 keeps it local-only).

Point your SDK at it#

One line: cloudemu env#

eval "$(cloudemu env)" exports AWS_ENDPOINT_URL plus dummy credentials and a region, so the AWS CLI and SDKs target a running cloudemu with zero code change:

eval "$(cloudemu env)"
aws s3 mb s3://demo          # no --endpoint-url needed

AWS#

client := s3.NewFromConfig(cfg, func(o *s3.Options) {
    o.BaseEndpoint = aws.String("http://127.0.0.1:4566")
    o.UsePathStyle = true
})

Other languages: set AWS_ENDPOINT_URL=http://127.0.0.1:4566 (or --endpoint-url on the CLI). Any credentials work — cloudemu doesn't validate signatures.

GCP#

client, _ := storage.NewClient(ctx,
    option.WithEndpoint("http://127.0.0.1:4569"),
    option.WithoutAuthentication())

Azure#

Azure is served over HTTPS with a self-signed cert. Point the SDK at it through a cloud.Configuration and either trust the cert or skip verification for local dev:

cloudCfg := cloud.Configuration{
    Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
        cloud.ResourceManager: {
            Endpoint: "https://127.0.0.1:4568",
            Audience: "https://management.azure.com",
        },
    },
}
opts := &arm.ClientOptions{ClientOptions: azcore.ClientOptions{Cloud: cloudCfg}}

OCI#

Every OCI service is served on one port, so each client's Host is the same address. Any configuration provider works, because cloudemu doesn't verify request signatures — a raw provider with a throwaway RSA key is enough:

cp := common.NewRawConfigurationProvider(
    "ocid1.tenancy.oc1..aaaaaaaacloudemulocaltenancy", // the default --oci-tenancy
    "ocid1.user.oc1..local", "us-ashburn-1", "aa:bb", privateKeyPEM, nil)

objects, _ := objectstorage.NewObjectStorageClientWithConfigurationProvider(cp)
objects.Host = "http://127.0.0.1:4571"

The tenancy OCID is also the root compartment, so it works as compartmentId straight away. Other languages: point the client's service endpoint at the same address (Python service_endpoint=, Java setEndpoint).

Common flags#

FlagDefaultPurpose
--providersaws,azure,gcp,ociwhich providers to start
--host127.0.0.1bind interface (0.0.0.0 to expose)
--advertise-host--host (or 127.0.0.1 if binding 0.0.0.0)host/IP the Kubernetes data-plane endpoint and its TLS cert advertise — set a routable address when binding 0.0.0.0 or running under Docker
--account-id / --region / --project-iddefaultsidentity reported by the emulator
--oci-port4571port for the OCI endpoint (HTTP)
--oci-tenancyocid1.tenancy.oc1..aaaaaaaacloudemulocaltenancyOCI tenancy OCID; also the root compartment, and the Object Storage namespace is derived from it
--gcp-grpc-portoffport for a GCP gRPC transport (health + reflection); point BIGTABLE_EMULATOR_HOST / PUBSUB_EMULATOR_HOST clients here
--latency0artificial per-call latency (e.g. 20ms)
--tls-cert / --tls-keyself-signedbring your own cert for the HTTPS endpoints (must be given together)
--tls-host—extra SAN host/IP for the generated self-signed cert (repeatable)
--endpoints-file—write the resolved endpoints as JSON
--admintruemount the /_cloudemu control plane
--init-dir—apply every *.json seed fixture in this directory on startup
--persist / --state-file / --persist-strategy / --persist-interval / --persist-metadata-onlyoffsave/restore state across restarts — see State, persistence & seeding
--vcr / --vcr-cassette / --vcr-strictoffrecord or replay the wire protocol — see VCR record/replay
--shutdown-timeout10sgrace period for in-flight requests on exit
--log-requests / --quietoffrequest logging / suppress the banner
--enforce-authoffrequire authentication on each request — see Auth Enforcement

--endpoints-file cloudemu.json writes a machine-readable bundle so an app can be wired at the whole emulated cloud at once. Providers you didn't start are simply omitted:

{
  "aws": "http://127.0.0.1:4566",
  "azure": "https://127.0.0.1:4568",
  "gcp": "http://127.0.0.1:4569",
  "gcp_grpc": "127.0.0.1:4572",
  "oci": "http://127.0.0.1:4571",
  "kubernetes": "https://127.0.0.1:4570"
}

Building your own binary#

The serve command builds a server from a fully-constructed provider using the NewFromProvider helper, so you can embed the same thing in your own main:

srv := awsserver.NewFromProvider(cloudemu.NewAWS())
http.ListenAndServe(":4566", srv)

DriversFrom(provider) returns the wired Drivers if you want to customize before serving.

Admin control plane#

The server mounts a small /_cloudemu control plane (on by default; disable with --admin=false) for test isolation:

  • POST /_cloudemu/reset — wipes all in-memory state so a suite can start each test from a clean slate.
  • a health endpoint for readiness checks.

Real engines from the server#

The batteries-included cloudemu-server binary wires the opt-in real engines into the standalone servers — so a database, cache, function, or container you create does real work, no Go required:

cloudemu-server --db=postgres --cache=redis    # or --all-real
eval "$(cloudemu env)"
aws rds create-db-instance …                    # → a real Postgres you can connect to

Every engine defaults off, so with no flags it's the same in-memory server. See Real Engines for the full list and the no-Docker vs Docker split.

State, persistence & seeding#

By default the server holds all state in memory and starts empty on each run — ideal for isolated tests. Persistence and seeding are opt-in:

  • Persist across restarts. --persist --state-file ./state.json saves the whole cloud (across AWS, Azure, GCP and OCI) on shutdown and restores it — identity-preserving — on the next start. Tune when it saves with --persist-strategy (scheduled | on-request | on-shutdown | manual) and --persist-interval; --persist-metadata-only keeps structure but drops object bodies for a smaller snapshot.
  • Snapshot on demand. cloudemu snapshot save <name> / load <name> / list / delete freeze and restore named states against a running server.
  • Seed fixtures on startup. --init-dir ./fixtures applies every *.json seed fixture in a directory when the server boots, so an environment comes up pre-populated.

The in-memory default stays ephemeral; nothing is written unless you opt in.

VCR record/replay#

--vcr record --vcr-cassette ./calls.json captures every request/response the server handles into a JSON cassette file, written on shutdown. --vcr replay --vcr-cassette ./calls.json serves those recorded responses back verbatim instead of running the real handlers — useful for a deterministic fixture of a third-party integration, or for replaying a captured session without the original backend state.

A cassette entry is matched by provider, HTTP method, path, query, and request body. By default (--vcr-strict=true) an unmatched request during replay fails with 501 and a diagnostic body naming the unmatched request; pass --vcr-strict=false to fall through to the real in-memory handler instead of failing.

# Record a session
cloudemu serve --vcr record --vcr-cassette ./calls.json

# Replay it later — same responses, no live backend needed
cloudemu serve --vcr replay --vcr-cassette ./calls.json

--vcr-cassette is required whenever --vcr is set.

On this page

On this page