Local AWS Emulation at Scale: Using Kumo to Speed Up CI/CD and Reduce Test Flakiness
Use Kumo, a lightweight AWS emulator, to make CI/CD integration tests fast, deterministic, and cheap by persisting state, seeding snapshots, and running local emulation at scale.
Local AWS Emulation at Scale: Using Kumo to Speed Up CI/CD and Reduce Test Flakiness
Integration tests that hit real AWS are often slow, brittle, and expensive. Kumo — a lightweight AWS service emulator written in Go — is a compelling alternative to reduce flakiness and cost while keeping tests fast and deterministic. This hands-on guide shows how to replace brittle AWS integration tests with Kumo in CI/CD pipelines, patterns to make tests deterministic, ways to persist state between runs, and techniques to keep tests cheap and fast.
Why use a local AWS emulator like Kumo?
Kumo emulates many AWS services locally. Its selling points for CI environments are practical: no authentication required (so it works well in isolated CI environments), a single Go binary for easy distribution, Docker support, and optional data persistence via KUMO_DATA_DIR. It supports dozens of services — S3, DynamoDB, Lambda, SQS, SNS, EventBridge, ECS, ECR, RDS, and many more — and is compatible with the Go AWS SDK v2.
Benefits over running against real AWS
- Deterministic environment: eliminate external network and AWS API variability that cause flakey tests.
- Lower cost: avoid repeated calls and resource provisioning in a cloud account.
- Faster feedback: local emulators start quickly and avoid AWS API throttling and regional latency.
- Easy sandboxing: single-binary or containerized Kumo is easy to start/stop per job.
Quick start: run Kumo locally or in CI
You can run Kumo as a container or as a single Go binary. Here are two minimal examples.
Docker Compose (simple reproducible dev/CI)
{
"docker-compose.yml": "version: \"3.8\"\nservices:\n kumo:\n image: ghcr.io/sivchari/kumo:latest\n ports:\n - \"4566:4566\"\n environment:\n - KUMO_DATA_DIR=/data\n volumes:\n - kumo-data:/data\nvolumes:\n kumo-data: {}\n"
}
Mount a named volume for KUMO_DATA_DIR when you want state to persist between restarts. Expose the port your SDK expects (for many emulators it's 4566-style behaviour; confirm Kumo's listen port in its docs/flags).
Single-binary (useful for small CI images)
Because Kumo is a Go single binary, distributing it in CI is straightforward: download the binary in your job, make it executable, and run it in the background. This can be much lighter than a heavier emulator or full container runtime.
curl -L -o /usr/local/bin/kumo https://example.com/kumo && chmod +x /usr/local/bin/kumo
# start with persistent dir
mkdir -p /tmp/kumo-data
KUMO_DATA_DIR=/tmp/kumo-data kumo --port 4566 &
Patterns for deterministic and fast integration tests
Switching to an emulator is only part of the battle. To reduce flakiness you must design tests to be deterministic and isolated.
1. Stable resource naming
Avoid non-deterministic names (timestamps / random UUIDs) unless you intentionally isolate resources. Use a fixed prefix per test run or per test suite, then clean up or reuse known names. Example pattern:
prefix := "test-suite-1234" // deterministic per CI job
s3Key := fmt.Sprintf("%s/my-object", prefix)
2. Inject clocks and seed randomness
Replace calls to time.Now() with an injected clock for tests. Seed any RNGs with a known seed so generated IDs and ordering are reproducible.
3. Idempotent setup & teardown
Design setup to clear or reset state before tests run. With Kumo you can persist state between runs — useful for expensive seeds — but still ensure idempotency by cleaning only the resources that tests touch.
4. Avoid eventual-consistency assumptions
Many integration tests rely on eventual consistency semantics of services like S3 or DynamoDB. In an emulator, behavior can differ; prefer read-after-write patterns and explicit waits with deterministic backoffs. Where possible, design code to handle eventual consistency rather than relying on sleeps in tests.
5. Use contract and unit tests for logic
Only run a small number of focused integration tests against Kumo. Most logic should be covered by unit tests and contract tests that validate the integration surface without launching full end-to-end flows every PR.
Persisting test state between runs
There are tradeoffs between starting with a clean slate and reusing seeded state to speed tests. Kumo's KUMO_DATA_DIR gives you both options.
Use cases for persistence
- Large seed data sets (many S3 objects, DynamoDB rows) — avoid seeding on every job by snapshotting a preseeded store.
- Shared test fixtures across multiple pipelines — accelerate smoke tests by reusing warmed caches or databases.
Practical persistence patterns
- Preseed once: run a job that starts Kumo, seeds data, and archives the data dir as an artifact or cached volume.
- Restore in other jobs: download and extract the archived data into
KUMO_DATA_DIRbefore starting Kumo. - Version snapshots: include a snapshot version in artifacts so schema changes in seeded data are explicit and reproducible.
- Selective reset: provide a lightweight cleanup step that wipes specific prefixes rather than the whole data directory when tests need a fresh target set.
Example: in GitHub Actions you could cache the /tmp/kumo-data folder between workflow runs, or upload a tarball as a workflow artifact and restore it in PR jobs.
Keep tests fast and cheap
Performance and cost savings are central benefits. Follow these tactics to keep CI green and fast.
1. Start Kumo once per runner
Start Kumo in a background step and run multiple test jobs against it to avoid per-job cold starts. If your CI supports job containers with lifecycle hooks, keep Kumo running for the pipeline.
2. Warm caches & reuse snapshots
As above, restore preseeded snapshots to avoid long seed phases. Snapshotting is especially helpful for large S3/DynamoDB datasets.
3. Parallelize safely
Run tests in parallel where isolation is guaranteed (distinct resource prefixes). For shared state tests, run them sequentially or in isolated runners.
4. Gate heavy tests
Run fast smoke tests on every PR. Reserve the full integration suite (longer and heavier) for merges to main or nightly jobs. This reduces cost while keeping feedback quick for developers.
5. Monitor and measure
Track average test duration and failure rates over time. When flakiness drops after switching to Kumo you can justify expanding emulator coverage.
Replacing brittle AWS integration tests: practical checklist
When migrating tests from real AWS to Kumo, follow this step-by-step checklist.
- Inventory which tests currently call AWS services and categorize them by criticality and execution time.
- Start Kumo locally and run a subset of tests against it to surface API differences.
- Fix compatibility issues in your SDK client configuration (endpoint override, region, no-auth).
- Introduce deterministic patterns (fixed clocks, seeded RNG, stable names).
- Decide on persistence: snapshot preseeded data if seeding takes long.
- Integrate Kumo start/stop into CI, using docker-compose or the binary depending on your runner constraints.
- Run smoke tests on PRs; schedule full integration runs on merge/nightly.
Example CI snippet (conceptual)
Below is a conceptual CI job illustrating starting Kumo, restoring a snapshot, running tests, and optionally uploading an artifact snapshot after the run. Adapt to your CI provider.
# conceptual-job
- restore-snapshot:
run: |-
if [ -f snapshot.tar.gz ]; then tar xzf snapshot.tar.gz -C /tmp/kumo-data; fi
- start-kumo:
run: |-
mkdir -p /tmp/kumo-data
KUMO_DATA_DIR=/tmp/kumo-data kumo --port 4566 &
# wait for healthy
- run-tests:
run: go test ./integration -v
- save-snapshot:
run: tar czf snapshot.tar.gz -C /tmp/kumo-data .
When Kumo might not be enough
Emulators are powerful but not a silver bullet. For extremely tight coupling to AWS-managed behavior (e.g., proprietary IAM policy evaluation, nuanced timing in managed services), you may still need selective tests against real AWS accounts. Use Kumo for the bulk of fast integration tests and reserve a small set of real-cloud end-to-end tests for the critical paths that cannot be emulated reliably.
Further reading
For teams adopting cloud-native development patterns alongside local emulation, see our guide on deploying code and workflows to the cloud for practical CI/CD patterns: Climbing the Cloud: Deploying AI-Generated Code into Your Development Workflow.
Conclusion
Kumo provides a fast, lightweight path to replace many brittle AWS integration tests. By using its single binary or Docker image, leveraging KUMO_DATA_DIR for persistence, and applying deterministic testing patterns, teams can dramatically reduce flakiness and CI cost while keeping test speed high. Adopt a hybrid approach: run most tests against Kumo and reserve a small set of real-AWS tests for behavior that cannot be simulated. With careful seeding, snapshotting, and gating of expensive suites, you can scale local AWS emulation effectively across your CI/CD pipelines.
Related Topics
Avery Collins
Senior DevOps Engineer
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
Insights from X Games: The Dynamics of Extreme Sports Competition
Streaming Trends: What Makes the Best Shows and Movies on Netflix in 2026
How to Set Up a Secure Local Workspace for IT Professionals in 2023
Drivers of Change: Enhancing Compatibility with Windows’ Latest Feature Updates
Crisis Management in Sports: What We Can Learn from West Ham v Sunderland
From Our Network
Trending stories across our publication group