Run a local Cosmos emulator in Docker for offline dev, ephemeral test containers in CI, and provision real Cosmos via Bicep / Terraform. The combination — fast local feedback, reproducible CI, and infra-as-code in production — is what makes Cosmos manageable at team scale.
- ▸The Cosmos emulator runs in Docker — `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator`. Start it once, point your SDK at the emulator endpoint with the well-known dev key.
- ▸Two emulator modes — full (heavy, supports all APIs) and the new linux-vNext (lightweight, faster startup, SQL API only). Use vNext for dev; full only when you need MongoDB/Cassandra APIs locally.
- ▸In CI — start the emulator as a service container, run integration tests against it, tear it down. ~30s startup, no Azure spend, deterministic.
- ▸In prod — Bicep is the canonical IaC for Azure resources; Terraform if you're multi-cloud. Either way, version-control your container, partition key, and index policy.
- ▸Don't share dev databases. Use ephemeral databases per developer or per CI run. Use the SDK to create and drop them; Cosmos can hold thousands of databases per account.
The last lesson, and arguably the most important. Everything in the previous 14 lessons is moot if you can’t iterate quickly, test reliably, and ship safely. Three pieces — local emulator, CI-friendly tests, infra-as-code — and you’re set.
Local development with the emulator
Start the emulator in Docker:
docker run --publish 8081:8081 \
--publish 10250-10255:10250-10255 \
mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator-vnext-preview
Point your SDK at it:
var client = new CosmosClient(
"https://localhost:8081",
// Well-known emulator key — same in everyone's repo, not a secret
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
new CosmosClientOptions {
HttpClientFactory = () => new HttpClient(new HttpClientHandler {
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
})
});
(The TLS bypass is only because the emulator uses a self-signed cert. Don’t carry that into prod code.)
The vNext emulator starts in ~5 seconds vs. the legacy one’s ~30. SQL API only, but that’s all most apps need. Run it as a service in your docker-compose.yml alongside your app and you have offline development.
CI integration tests
GitHub Actions example — runs the emulator as a service container:
jobs:
test:
runs-on: ubuntu-latest
services:
cosmos:
image: mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator-vnext-preview
ports: ['8081:8081']
options: --health-cmd "curl -kf https://localhost:8081/" --health-interval 5s --health-retries 30
steps:
- uses: actions/checkout@v4
- run: dotnet test --filter Category=Integration
env:
COSMOS_ENDPOINT: https://localhost:8081
COSMOS_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
Each test creates and drops its own database. Parallel tests don’t interfere. No Azure spend, no flaky shared state.
Infrastructure as code
For the account, container, and policy — pick Bicep (Azure-native) or Terraform (multi-cloud). The Bicep equivalent of “one Cosmos account with one database and one container”:
resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
name: 'cosmos-${appName}-${env}'
location: location
kind: 'GlobalDocumentDB'
properties: {
consistencyPolicy: { defaultConsistencyLevel: 'Session' }
locations: [{ locationName: location, failoverPriority: 0 }]
capabilities: [{ name: 'EnableServerless' }]
disableLocalAuth: true
}
}
resource db 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = {
parent: account
name: 'app'
properties: { resource: { id: 'app' } }
}
resource orders 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = {
parent: db
name: 'orders'
properties: {
resource: {
id: 'orders'
partitionKey: { paths: ['/userId'], kind: 'Hash' }
indexingPolicy: {
indexingMode: 'consistent'
includedPaths: [{ path: '/*' }]
excludedPaths: [{ path: '/payload/*' }, { path: '/_etag/?' }]
}
}
}
}
This is a complete, deployable file. Check it in. Run it in CD. Production Cosmos is now reproducible.
Multi-environment topology
Common pattern — three environments, one repo:
| Env | Account | Throughput | Backup |
|---|---|---|---|
| dev | cosmos-app-dev | Serverless | Continuous (24h) |
| staging | cosmos-app-stg | Autoscale 1000 RU/s | Periodic (7d) |
| prod | cosmos-app-prd | Autoscale 4000 RU/s, multi-region | Continuous + zone-redundant |
Same Bicep template, parameterized. Deploy via your CD pipeline on merge to main (dev), tag (staging), manual approval (prod).
Migrations
Cosmos is schema-free, but your app has implicit schema. When you change the shape — adding a required field, splitting a doc — you need a migration strategy:
- Dual-write — write the new shape to both old and new fields. Run for one release.
- Backfill — Change Feed processor or one-off Spark job to populate new fields on old docs.
- Read-side fallback — read code handles both shapes during transition.
- Cleanup — once all docs are migrated, drop the old fields.
This is the same dance as SQL “expand-contract” migrations — version control both old and new shapes in your tests.
Wrapping the course
You’ve now seen the whole machine — APIs, partitioning, modeling, consistency, CRUD, querying, indexing, SDKs, RUs, monitoring, Change Feed, global distribution, security, vector search, and operations. The next step isn’t more theory — it’s building.
Pick a small app — a URL shortener, a personal CRM, a notes app. Model it Cosmos-first (lesson V03). Provision via Bicep (this lesson). Run it for a few weeks. Watch the metrics. Tune the index. That’s the loop, and it’s how Cosmos becomes muscle memory.
Q1. Does the emulator behave identically to the cloud? ▾
Mostly — the SDK API surface, query language, and indexing are identical. Differences — slightly different RU cost calibration, no cross-region scenarios (it's single-node), no built-in TTL accuracy (timing varies slightly), and a few preview features lag. For 95% of integration tests it's faithful enough; for performance tests, always test against real Cosmos.
Q2. Can I run integration tests in parallel against one emulator? ▾
Yes — give each test class its own database name (e.g. `test_${ci_run_id}_${class}`), create at setup, drop at teardown. The emulator handles ~hundreds of databases comfortably. Don't share containers — flaky test interference is a guaranteed waste of an afternoon.
Q3. Should infra-as-code live in the app repo or a separate repo? ▾
Same repo as the app for the container schema (partition key, indexing policy, throughput) — those are tightly coupled to code changes. Separate "platform" repo for the account, network, and AAD wiring — those have a different change cadence and ownership.
Comments 0
Discuss this page. Markdown supported. Be kind.