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
Run it#
go install github.com/stackshy/cloudemu/v2/cmd/cloudemu@latest
cloudemu serveOr straight from a checkout:
go run ./cmd/cloudemu serveOn 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:4571Docker#
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:latestOr bring up the whole emulated cloud with the example compose file:
docker compose upTestcontainers (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 testsPorts#
| Provider | Default | Protocol | Notes |
|---|---|---|---|
| AWS | 4566 | HTTP | same port LocalStack uses |
| Azure | 4568 | HTTPS | the ARM SDK requires TLS |
| GCP | 4569 | HTTP | |
| Kubernetes | 4570 | HTTPS | shared data-plane for EKS / AKS / GKE (self-signed TLS) |
| OCI | 4571 | HTTP | every 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 neededAWS#
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#
| Flag | Default | Purpose |
|---|---|---|
--providers | aws,azure,gcp,oci | which providers to start |
--host | 127.0.0.1 | bind 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-id | defaults | identity reported by the emulator |
--oci-port | 4571 | port for the OCI endpoint (HTTP) |
--oci-tenancy | ocid1.tenancy.oc1..aaaaaaaacloudemulocaltenancy | OCI tenancy OCID; also the root compartment, and the Object Storage namespace is derived from it |
--gcp-grpc-port | off | port for a GCP gRPC transport (health + reflection); point BIGTABLE_EMULATOR_HOST / PUBSUB_EMULATOR_HOST clients here |
--latency | 0 | artificial per-call latency (e.g. 20ms) |
--tls-cert / --tls-key | self-signed | bring 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 |
--admin | true | mount 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-only | off | save/restore state across restarts — see State, persistence & seeding |
--vcr / --vcr-cassette / --vcr-strict | off | record or replay the wire protocol — see VCR record/replay |
--shutdown-timeout | 10s | grace period for in-flight requests on exit |
--log-requests / --quiet | off | request logging / suppress the banner |
--enforce-auth | off | require 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 toEvery 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.jsonsaves 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-onlykeeps structure but drops object bodies for a smaller snapshot. - Snapshot on demand.
cloudemu snapshot save <name>/load <name>/list/deletefreeze and restore named states against a running server. - Seed fixtures on startup.
--init-dir ./fixturesapplies every*.jsonseed 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.