Skip to content

Configuration reference

doze reads HCL from doze.hcl (and any sibling *.doze.hcl, merged automatically — see splitting config). The file has a fixed root plus one block per instance, keyed by engine:

defaults { idle_timeout = "5m" } # root settings
postgres "app" { version = 16 } # instances
valkey "cache" { version = 9 }

Jump to an engine: postgres · valkey · kvrocks · ferret · s3 · sqs · sns. Project-level blocks: modules · TLS.

This is the field-by-field reference. For what each engine is and when to use it, see The engines. Every engine’s full argument reference is also one command away — doze modules docs <engine> — generated from the module itself, so it can’t be stale.

FieldTypeDefaultDescription
listenstring127.0.0.1:6432Base client address. Each instance gets the next port; override per instance with listen, or use a unix:/path.
homestring$DOZE_HOME or ~/.dozeShared toolchain store + cache (deduplicated across projects).
data_dirstring<home>/projects/<slug>This project’s state (data dirs, sockets, logs).
defaults { idle_timeout }duration5mReap an instance after this long at zero connections.
tls { … }blockoffTerminate client TLS for Postgres — see TLS.
FieldTypeDefaultDescription
certstringautoPath to a PEM certificate. Omit cert and key to auto-generate a self-signed cert.
keystringautoPath to the matching PEM private key.
requiredboolfalseReject plaintext TCP clients (require sslmode=require).
tls {} # auto self-signed cert; sslmode=require works

TLS is terminated at the proxy; the backend speaks plaintext over a local socket.

Every <engine> "<name>" { … } block accepts:

FieldTypeDefaultDescription
versionstring/numberThe engine version: a major (16 → newest 16.x, pinned) or exact ("16.14"). Required for database engines; the AWS engines (s3/sqs/sns) take no version.
listenstringnext port from root listenPer-instance client address ("127.0.0.1:5544" or "unix:/path.sock").

version is the only version you declare. The module (the plugin that provides the engine) is selected automatically — the newest release compatible with your doze and the engine versions you declared — and pinned in doze.lock. Some engine arguments are version-gated: using one below the engine version that introduced it fails at doze lint with the argument and required major named (docs mark these, e.g. engine ≥ 18). See modules for the rare overrides.


Values are HCL expressions, not just literals. You can call functions and—most usefully—reference other instances by <engine>.<name>.<attribute>:

sns "events_bus" {
sqs = sqs.jobs.name # reference → builds the dependency edge
}

A reference does two things: it resolves to the attribute’s value, and it makes the referencing instance depend on the referenced one (doze boots and holds the dependency first — no hand-declared ordering). Referencing an instance that isn’t declared is a parse-time error, and reference cycles are rejected.

Every instance exposes these baseline attributes:

AttributeDescription
name / engineThe instance name and its engine type.
addressClient-facing host:port (or unix:/path).
host / portSplit address (empty/0 for a unix socket).
socketUnix socket path (empty for TCP).
urlThe connection string doze injects (e.g. postgres://…).
env_varThe conventional variable name (DATABASE_URL, …).

Functions include the common string/collection/number/encoding helpers (upper, join, format, coalesce, merge, jsonencode, …) plus env("NAME") to read a host environment variable (with an optional default).

variable "pg_version" {
type = number
default = 16
}
locals {
app_db = "app_${var.pg_version}"
}
postgres "app" {
version = var.pg_version
owner = local.app_db
}

variable "<name>" { … } — a typed input.

FieldTypeDefaultDescription
typetypeanyOptional constraint: string, number, bool, list(string), map(string), …
defaultanyValue when no override is given. Omit to make the variable required.
descriptionstringnoneHuman description.
sensitiveboolfalseHint that the value is secret.

Values resolve by precedence (highest first): --var name=valueDOZE_VAR_<name> env var › a sibling *.auto.doze.vars file (name = value assignments) › the default. A required variable with no value is an error.

locals { … } — named intermediate values (local.<name>); may reference variables, functions, and earlier locals.

Stamp several similar instances from one block. Each stamp becomes its own instance with a flat name — <label>_<key> (for_each) or <label>_<index> (count) — addressable like any other (valkey.shard_0.url, sqs.worker_emails.url).

sqs "worker" {
for_each = toset(["emails", "orders", "billing"]) # → worker_emails, worker_orders, worker_billing
queue "main" {}
}
valkey "shard" {
count = 3 # → shard_0, shard_1, shard_2
version = 9
maxmemory = "${(count.index + 1) * 64}mb"
}
Meta-argValueIn-bodyNames
for_eacha set of strings, or a mapeach.key, each.value<label>_<key>
counta non-negative numbercount.index (0-based)<label>_<index>

count = 0 (or an empty for_each) produces no instances. A block can’t set both. Keys are sanitized for use in names/paths (orders.fifoorders_fifo).

References already create dependencies, so you rarely need this. For an ordering that isn’t expressed through a reference, depends_on adds it:

sqs "jobs" {
queue "main" {}
depends_on = { "s3.media" = "healthy" }
}

The key is an instance address (or bare name); the value is the readiness condition — healthy (wait until it accepts connections / its health probe passes, the default for every reference) or started (wait only until the dependency’s process has spawned). started lets a service start before a peer process becomes healthy; for a database/queue, which is ready as soon as it accepts connections, the two are equivalent.


Real PostgreSQL (14–17). On boot, doze creates the database (named after the instance) and converges the declared roles, schemas, grants, and extensions.

postgres "app" {
version = 16
owner = "app"
role "app" { password = "app" }
grant {
role = "app"
database = "app"
privileges = ["ALL"]
}
extensions = ["uuid-ossp"]
}

Block fields

FieldTypeDefaultDescription
ownerstringpostgresRole that owns the database.
encodingstringserver defaultDatabase encoding, e.g. "UTF8".
localestringserver defaultDatabase locale; shorthand for both lc_collate and lc_ctype.
lc_collatestringlocaleCollation, overriding locale.
lc_ctypestringlocaleCharacter classification, overriding locale.
templatestringserver defaultTemplate database to clone from.
connection_limitnumber-1 (unlimited)Database CONNECTION LIMIT.
is_templateboolfalseMark the database a template.
allow_connectionsbooltrueAllow connections to the database.
tablespacestringdefaultTablespace for the database.
commentstringnoneCOMMENT ON DATABASE.
shared_buffersstring16MBPostgres shared_buffers.
max_connectionsnumber50Postgres max_connections.
fsyncboolfalseWhen off (default), also disables synchronous_commit and full_page_writes — fast, not crash-safe. Set true for durability.
autovacuumboolfalseEnable autovacuum.
settingsmap(string){}Raw postgresql.conf passthrough for any parameter without a typed field (e.g. { work_mem = "8MB" }). Applied after the typed tuning; doze-locked params (listen_addresses, …) always win.
extensionslist(string)[]Shorthand for CREATE EXTENSION IF NOT EXISTS per name.

role "<name>" { … } — a login user (default) or, with login = false, a group role.

FieldTypeDefaultDescription
passwordstringnoneLogin password.
loginbooltruefalse makes it a group role.
superuserboolfalseGrant SUPERUSER.
createdbboolfalseGrant CREATEDB.
createroleboolfalseGrant CREATEROLE.
replicationboolfalseGrant REPLICATION.
inheritbooltrueInherit privileges of granted roles.
bypassrlsboolfalseGrant BYPASSRLS (skip row-level security).
connection_limitnumber-1 (unlimited)Max concurrent connections.
valid_untilstringnonePassword expiry timestamp.
member_oflist(string)[]Roles this role is a member of.
commentstringnoneCOMMENT ON ROLE.
configmap(string){}Per-role parameters via ALTER ROLE … SET (e.g. { search_path = "app, public" }).

schema "<name>" { … }

FieldTypeDefaultDescription
ownerstringdatabase ownerRole that owns the schema.

grant { … }

FieldTypeDefaultDescription
rolestringGrantee role. Required.
privilegeslist(string)e.g. ["ALL"], ["SELECT","INSERT"]. Required.
databasestringnoneGrant at the database level.
schemastringnoneGrant within a schema.
objectsstringnoneWith schema: tables / sequences / functions (covers current + future objects).
with_grant_optionboolfalseAllow the grantee to re-grant.

extension "<name>" { … } — for options beyond the extensions shorthand.

FieldTypeDefaultDescription
versionstringlatest availableSpecific extension version.
schemastringdefaultSchema to install into.
sourcestringnonePath to a source/bundle for an extension the binary doesn’t ship — see Extensions.
cascadeboolfalseAdd CASCADE to also create dependency extensions.
optionalboolfalseWhen true, an unavailable or failed extension is a warning, not a hard error. By default a missing/failed extension fails convergence and taints the instance.

In-memory, Redis-protocol cache.

valkey "cache" {
version = 9
maxmemory = "256mb"
}
FieldTypeDefaultDescription
versionstring/numberMajor or exact. Required.
passwordstringnonerequirepass.
maxmemorystringunlimitedMemory cap, e.g. "256mb".
maxmemory_policystringserver defaultEviction policy, e.g. "allkeys-lru".
appendonlyboolfalseEnable the AOF persistence log.
savestringoffRDB snapshot schedule, e.g. "3600 1 300 100".
settingsmap(string){}Raw valkey.conf passthrough, e.g. { "lazyfree-lazy-eviction" = "yes" }. Applied last so it overrides typed fields.

RocksDB-backed, Redis-protocol durable KV store.

kvrocks "store" {
version = 2
password = "default-token"
namespace "tenant_a" { token = "tok-a" }
}
FieldTypeDefaultDescription
versionstring/numberMajor or exact. Required.
passwordstringnonerequirepass (also the default-namespace token).
workersnumberserver defaultWorker thread pool size.
settingsmap(string){}Raw kvrocks.conf passthrough, e.g. { "rocksdb.block_size" = "16384" }.

namespace "<name>" { … } — a kvrocks namespace with an access token. Requires password.

FieldTypeDefaultDescription
tokenstringThe namespace access token. Required.

A self-contained, MongoDB-compatible engine: doze runs a private PostgreSQL with Microsoft’s DocumentDB extension behind a FerretDB v2 gateway, exposing only the Mongo wire (MONGODB_URI). One declared instance is one composite backend — no separate postgres to wire up. See the DocumentDB recipe.

ferret "shop" {
version = "2.7"
port = 27017
database "catalog" {
collection "products" { seed = "./seed/products.json" }
}
}
FieldTypeDefaultDescription
versionstring/numberFerretDB v2.x gateway version. Required.
databaseblockA Mongo database to ensure (repeatable; label = name), with nested collection blocks.
settingsmap(string)Extra FERRETDB_* gateway settings.

Full argument/block reference: doze modules docs ferret or its registry page.


Local object storage. Buckets are created on boot / doze sync.

s3 "media" {
bucket "uploads" {}
bucket "thumbs" { versioning = true }
}

bucket "<name>" { … }

FieldTypeDefaultDescription
versioningboolfalseEnable object versioning (best-effort on the dev backend).

Local message queues.

sqs "jobs" {
queue "emails" { visibility_timeout = "30s" }
queue "emails-dlq" {}
redrive "emails" {
dead_letter = "emails-dlq"
max_receive_count = 5
}
}

queue "<name>" { … } — durations accept Go syntax (30s, 5m, 12h) or bare seconds.

FieldTypeDefaultDescription
fifoboolfalseFIFO queue (name must end in .fifo).
content_based_dedupboolfalseFIFO: dedupe by body hash (5-minute window).
visibility_timeoutduration30sHow long a received message stays invisible.
delayduration0sDelivery delay for new messages.
retentionduration96h (4 days)How long messages are kept.
wait_timeduration0sDefault long-poll wait (server caps at 20s).
max_message_sizenumber262144Max message bytes (256 KiB).

redrive "<queue>" { … } — dead-letter policy for the named queue.

FieldTypeDefaultDescription
dead_letterstringTarget dead-letter queue (in the same sqs instance). Required.
max_receive_countnumberMove to the DLQ after this many receives. Required.

Local pub/sub with SNS→SQS fanout and webhooks.

sns "events" {
sqs = sqs.jobs.name
topic "signups" {}
subscribe "signups" {
protocol = "sqs"
endpoint = "emails"
raw = true
filter = { eventType = ["created"] }
}
}

Block field

FieldTypeDefaultDescription
sqsstringnoneName of a declared sqs instance to deliver to (held running while SNS runs).

topic "<name>" { } — no fields; just declares the topic.

subscribe "<topic>" { … } — a subscription on the named topic.

FieldTypeDefaultDescription
protocolstringsqs, http, or https. Required.
endpointstringQueue name/ARN (for sqs) or a URL (for http(s)). Required.
rawboolfalseRaw delivery (deliver the bare message, not the SNS envelope).
filterobjectnoneMessage-attribute filter policy, e.g. { type = ["a","b"] }.

An application process run directly on the host — no Docker, no virtualization. Unlike the database/AWS engines (which doze proxies and idle-reaps), a process is a long-lived, supervised client of those backends: it binds its own port, is exempt from the idle reaper, gates readiness on a health probe, and restarts per a policy when it exits. Bring processes up with doze up; they boot eagerly (not on connection).

process "api" {
cwd = "../approvals-engine" # working dir, relative to this file
command = "go run main.go -program api" # run via `sh -c` (pipes, &&, expansion OK)
port = 8080 # the port the app binds (doze does NOT bind it)
env = {
DATABASE_URL = postgres.app.url # typed ref → dependency edge + injected value
HTTP_API_PORT = "8080"
}
env_file = ".env.local" # optional; lower precedence than env{}
depends_on = { postgres.app = "healthy" } # explicit; the env refs above also imply edges
hooks {
pre_start = ["go run main.go -program migrate -command up"] # after deps, before start
post_start = ["./scripts/notify-ready.sh"] # after the app is healthy
pre_stop = ["./scripts/drain.sh"] # before SIGINT
}
health {
http = "http://localhost:8080/health/ready"
interval = "2s"
timeout = "3s"
retries = 30 # readiness budget = interval × retries
}
restart {
policy = "on_failure" # no | on_failure | always
backoff = "1s" # exponential, capped at 30s
max_retries = 5
}
}
FieldTypeDefaultNotes
commandstringCommand line, run via sh -c. Required.
cwdstringthe file’s dirWorking directory, resolved relative to the declaring file.
portnumbernoneThe port the app listens on. Exposed as process.<name>.{url,host,port}; doze opens no proxy listener for it. Omit for a worker with no endpoint.
envmapnoneEnvironment for the process and its hooks. Highest precedence; values may reference other instances.
env_filestringnonePath to a KEY=VALUE file (lower precedence than env).
hooksblocknonepre_start / post_start / pre_stop command lists, each run via sh -c in cwd. A non-zero pre_start/post_start aborts the boot and taints the instance.
healthblocknoneOne probe kind — http (2xx), tcp (host:port accepts), exec (exit 0), or log_line (regex over logs) — plus interval, timeout, retries. With no health block, readiness = “the process stayed alive briefly”.
restartblockpolicy = "no"policy (no / on_failure / always), backoff (base, grown exponentially and capped), max_retries (defaults to 5 for a restarting policy).

The process runs with the full environment doze run injects (connection strings, AWS creds/region, DOZE_<NAME>_URL), layered as: os env → doze-injected → env_fileenv {}. v1 runs go/bun/node from PATH; a .go-version/.prototools in cwd only triggers a warning on mismatch. The command and all its children are reaped as a process group on stop.

Note: HCL single-line blocks hold one argument only — write health { … } and restart { … } across multiple lines (one argument per line), as above.


Every engine except process is provided by a module — a signed plugin doze fetches from the registry on first use. The defaults are right for almost everyone: engine type postgres resolves to source doze/postgres on the public registry, doze selects the newest module release compatible with your doze and your declared engine versions, and doze.lock freezes the choice. The optional modules {} block holds the overrides:

modules {
mirror = "file:///path/to/registry" # air-gapped / dev registry base
enabled = true # (implied when mirror is set)
cache { # per engine TYPE (the block keyword)
source = "acme/valkey" # a third-party publisher's module
version = "0.2.0" # rare: pin the MODULE release exactly
}
}
FieldTypeDefaultDescription
mirrorstringthe public registryRegistry base URL (or file:// path). Also settable via DOZE_MODULES_MIRROR.
enabledboolonFetch modules at all. DOZE_MODULES=off disables globally (offline / process-only).
<type> { source }stringdoze/<type>Which <namespace>/<name> provides this engine type.
<type> { version }stringautoExact module release pin — the escape hatch for holding back a regressed release. Not the engine version.

Module workflow in one breath: discovery is doze modules search, docs are doze modules docs <type>, provenance is doze modules info <source>, and updates are explicit — doze modules upgrade --check reports, doze modules upgrade moves the pins (commit the updated doze.lock). A declared engine version the pinned module doesn’t support fails with exactly that upgrade command. See the CLI reference.

Root settings live in doze.hcl; instance blocks may be split across sibling *.doze.hcl files (merged automatically), or pass --config <dir> to merge every *.hcl in a directory. See Files & storage.

A bare major (version = 16) resolves to the newest minor and is pinned in doze.lock; a dotted string (version = "16.14") pins exactly. The lock also pins each engine’s module (release, plugin protocol, supported engine versions, per-platform checksums) and each registry namespace’s publisher key. Commit doze.lock — every machine then runs byte-identical modules and engine binaries. Run doze binaries available <engine> to see engine versions; doze modules upgrade --check to see waiting module updates. See Managing binaries and Files & storage.

A supervised process block receives these env vars for the dependencies it declares; the full set is also written to .doze/endpoints.yaml for external tooling. (doze run does not inject them — see Workflows.)

VariableFor
DOZE_<NAME>_URLevery instance, always
DATABASE_URLthe single postgres instance (if unambiguous)
REDIS_URLthe single valkey/kvrocks instance
MONGODB_URIthe single documentdb instance
AWS_ENDPOINT_URL_S3 / _SQS / _SNSthe AWS services, plus AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (dummy values)