Project Structure
How the .dokkimi/ folder is organized.
The .dokkimi/ folder
The only requirement is that a .dokkimi/ folder exists at your repo root. You can organize files inside however you want — there are no required subfolder conventions. All **/*.json and **/*.yaml / **/*.yml files inside .dokkimi/ are scanned. You can use JSON or YAML interchangeably for definitions and shared fragments.
A common convention:
.dokkimi/
├── config.yaml # Project-level config (optional)
├── definitions/ # Runnable definition files
├── shared/ # Shared item fragments (referenced via $ref)
└── init-files/ # Database init scripts (SQL, JS)Two file types
Dokkimi distinguishes file types by their JSON shape, not by folder or naming convention:
Runnable definition
Has top-level name (string) + items (array):
{
"name": "my-test-suite",
"config": { "timeoutSeconds": 300 },
"variables": { ... },
"items": [ ... ],
"tests": [ ... ]
}Shared fragment
Any JSON file that does NOT have both name + items. Typically a single item object:
{
"type": "DATABASE",
"name": "postgres-db",
"database": "postgres"
}Fragments are not runnable on their own — they exist to be referenced via $ref from runnable definitions.
Definition config block
The optional config object in a definition holds run-level settings:
{
"name": "my-test-suite",
"config": {
"timeoutSeconds": 300,
"browser": {
"version": "148.0.7778.56"
}
},
"items": [ ... ],
"tests": [ ... ]
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
timeoutSeconds | integer | No | 300 | Timeout for the entire run in seconds |
browser.version | string | No | "148.0.7778.56" | Chrome version tag. Changing this requires regenerating visual baselines. |
The config block is entirely optional — omit it to use defaults. The browser block is ignored when the definition contains no UI actions.
Config file
The .dokkimi/ folder can contain a project-level config file: config.yaml, config.yml, or config.json (first match wins). This file is not a definition — it's loaded separately by name.
dokkimi: 0.1.0
env:
REGISTRY: ghcr.io/dokkimi
IMAGE_TAG: v1.2.3
STRIPE_TEST_KEY: sk_test_abc123| Field | Type | Description |
|---|---|---|
dokkimi | string | Target Dokkimi version. The CLI warns if your installed version is older. |
env | object | Flat string → string map of build-time values. Keys must be alphanumeric + underscores. |
Build-time interpolation: ${{VAR}}
Any string value in a definition or shared fragment can reference a config env variable with ${{VAR}}. These are resolved at build time by the definition resolver, before the definition is sent to Control Tower.
{
"type": "SERVICE",
"name": "my-service",
"image": "${{REGISTRY}}/my-service:${{IMAGE_TAG}}",
"port": 3000,
"healthCheck": "/health"
}After resolution, the image becomes ghcr.io/dokkimi/my-service:v1.2.3.
${{VAR}} vs {{VAR}} — ${{VAR}} always resolves from config.yaml env only. {{VAR}} resolves at build time in item fields (from a map seeded with config.yaml env, then definition-level variables merged on top) and at runtime in test steps (from test variables, loops, and extraction). For keys not overridden by a definition variable, {{FOO}} and ${{FOO}} resolve to the same value. If a ${{VAR}} reference doesn't match any key in env, the resolver errors. Unresolved {{VAR}} in items is also an error.
Staged bootup order
By default, all items boot in parallel. If a service expects its dependencies to be running at startup (e.g., a database with no reconnection logic), use the stage property to control deployment order.
Each item accepts an optional stage field (non-negative integer, defaults to 0). Items deploy in stage order — stage N+1 containers don't start until all stage N items are healthy.
items:
- $ref: ../shared/postgres.yaml
stage: 0
- $ref: ../shared/redis.yaml
stage: 0
- $ref: ../shared/lago-api.yaml
stage: 1Stage 0 (Postgres + Redis) boots and becomes healthy. Only then does stage 1 (lago-api) deploy.
- Items without
stagedefault to stage 0 — no changes needed for existing definitions. - Stage numbers are sort keys, not indices —
0, 2, 5is three stages deployed in order. Gaps are fine. - A single run-level timeout covers all stages.
- The
stageproperty works on SERVICE, DATABASE, and BROKER items. MOCKs don't create containers and do not acceptstage.
Isolation model
Each runnable definition file creates its own isolated Docker namespace — a dedicated network with its own services, databases, browser, and DNS. No state is shared between definitions. When a run finishes, the entire namespace is destroyed.
Tests within a single definition share the same namespace intentionally. This lets you write multi-step workflows where test 2 depends on state that test 1 created — for example, creating a user in one test and verifying their data persists in the next. Tests within a definition execute sequentially in order.
For scenarios that need completely independent data, put them in separate definition files. Each file gets its own fresh environment. You can run multiple definitions in parallel with dokkimi run --max-parallel N, and they'll never interfere with each other because each runs in its own isolated namespace.
Test runtime
Dokkimi's orchestration layer adds under a second of overhead. What determines your total runtime is your containers and your tests:
- Container startup — each image boots and must pass its health check before tests begin. Lightweight images (Alpine-based, small JVM heaps) start faster. Elasticsearch with 256MB heap takes ~15 seconds; a Node.js service takes 2–3.
- Health check convergence — all items must be healthy simultaneously. A single slow container holds up the entire run.
- Test step execution — your HTTP requests, database queries, and assertions. Parallel steps (
type: parallel) can cut wall-clock time when steps are independent.
To keep runs fast:
- Set
minMemoryandminCpufor resource-hungry containers (Elasticsearch, JVM-based services) so they get enough resources to start quickly. - Keep database init files small — large seed scripts run before health checks pass.
- Use lighter image variants where possible (
-alpine,-slim). - Split unrelated test scenarios into separate definitions and run them in parallel with
dokkimi run --max-parallel N.
.dokignore
You can create a .dokkimi/.dokignore file to exclude files from cloud/CI test runs. It uses the same syntax as .gitignore:
# Ignore experimental definitions
experiments/
# Ignore a specific file
definitions/broken-test.json
# Ignore all files matching a pattern
**/wip-*.jsonPaths are relative to the .dokkimi/ directory. Ignored files are still valid for local runs — .dokignore only affects cloud/CI pipelines.