Peren documentation
Configuration reference
Fleet TOML fields, defaults, validation, and what each setting changes at startup or runtime.
Peren reads one fleet TOML file before listeners open. This page is the schema: every section, key, type, default, environment variable, validation rule, and when a restart is required. Validation runs at load. Missing required fields, invalid addresses, unsupported export settings, and credential combinations that fail validation refuse startup.
There is no [fleet], [bindings], [tls], [providers], or [storage] table. Bindings live under [services.bindings.NAME] with type = "...".
Change a field in the file, then restart the process (or start a new node) for the new value to take effect. Deployment generations use peren deploy and peren rollback, not edits to this file alone.
Validate with:
peren config validate fleet.toml
Root fields
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
seed_peers |
array of strings | no | [] |
peer advertise addresses | bucket validation; console backend selection | non-empty with bucket.kind = "memory" or "file" fails validation |
peer_identity_token_ttl_secs |
u64 | no | unset | non-negative integer | accepted in config | type errors fail parse |
[node]
Required table. Identifies this process and its peer listener.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
node.node_id |
UUID | yes | none | any UUID | node identity, startup probes | missing or non-UUID fails parse |
node.advertise_addr |
socket address string | yes | none | IP:port |
peer discovery address published for this node | not an IP socket address fails validation |
node.listen |
socket address string | yes | none | IP:port |
peer TCP listener bind | not an IP socket address fails validation |
node.identity_key_path |
path | no | unset | filesystem path | path normalization in development | accepted; live peer path does not load it in this build |
node.lease_mode |
enum | no | continuous |
continuous, lazy, shadow |
config parse | unknown enum fails parse |
node.region |
string | no | unset | any string | config parse | accepted; placement effect is not applied from this field in this build |
[node]
node_id = "00000000-0000-0000-0000-000000000001"
advertise_addr = "127.0.0.1:7000"
listen = "127.0.0.1:7000"
Changing node_id creates a different node identity. Changing listen or advertise_addr requires a restart.
[bucket]
Required table. Fleet durable object store used for ownership and recovery records.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
bucket.kind |
enum | no | s3 |
s3, memory, file, azure_blob |
object-store provider | memory or file with non-empty seed_peers fails validation |
bucket.path |
path | for file |
unset | directory path | file-backed store | missing when kind = "file" fails validation |
bucket.endpoint |
URL string | for s3 |
unset | object-store endpoint | S3 client | missing when kind = "s3" fails validation |
bucket.bucket |
string | for s3 and azure_blob |
unset | bucket or container name | S3 / Azure client | missing for those kinds fails validation |
bucket.region |
string | no | us-east-1 at S3 client build when unset |
AWS-style region | S3 client | wrong region causes client or CAS probe failure at startup |
bucket.access_key_env |
env var name | for s3 when credentials are configured or environment |
unset | name of env var holding access key | S3 static credentials | missing when required fails validation; missing env value fails before listeners open |
bucket.secret_key_env |
env var name | same as access_key_env |
unset | name of env var holding secret key | S3 static credentials | missing when required, or same as access_key_env, fails validation |
bucket.credentials_source |
enum | no | configured |
configured, environment, instance_role, workload_identity, eks_pod_identity |
S3 credential selection | configured / environment without key env names fails validation; Azure Blob accepts only configured environment credentials |
bucket.allow_http |
bool | no | false |
true, false |
S3 client | false against an http:// endpoint fails at client build or probe |
bucket.azure_account_env |
env var name | for azure_blob |
unset | account env var | Azure client | missing fails validation |
bucket.azure_access_key_env |
env var name | for azure_blob |
unset | access-key env var | Azure client | missing, or same as azure_account_env, fails validation |
bucket.azure_emulator |
bool | no | false |
true, false |
Azure client | wrong setting fails connectivity at startup probe |
For kind = "s3":
configuredandenvironmentreadaccess_key_envandsecret_key_envfrom the process environment and build a static-key S3 client.instance_role,workload_identity, andeks_pod_identitycall the bucket client’s instance-role builder (AmazonS3Builderwithout static keys). That path uses the object-store default credential chain for the host.
For kind = "azure_blob", Peren requires configured or environment credentials with distinct azure_account_env and azure_access_key_env names.
This bucket field is not the Worker AWS SigV4 binding. SigV4 credential_source values instance_role, workload_identity, and eks_pod_identity read AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optional AWS_SESSION_TOKEN from the process environment and do not call IMDS. See AWS SigV4.
[bucket]
kind = "file"
path = "./data"
[bucket]
kind = "s3"
endpoint = "https://s3.example.com"
bucket = "peren-fleet"
region = "us-east-1"
credentials_source = "configured"
access_key_env = "PEREN_BUCKET_ACCESS_KEY"
secret_key_env = "PEREN_BUCKET_SECRET_KEY"
[mtls]
Required table. Peer certificate paths for the node. Inbound peer listeners are plain TCP. The process does not terminate inbound TLS with these paths. Keep the peer port off the public network. Worker client certificates use binding type = "mtls_certificate", not this table.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
mtls.ca_cert_path |
path | yes | none | filesystem path | required in config; path absolutized for local development | missing fails parse |
mtls.leaf_cert_path |
path | yes | none | filesystem path | same | missing fails parse |
mtls.leaf_key_path |
path | yes | none | filesystem path | same | missing fails parse |
mtls.require_client_cert |
bool | no | true |
true, false |
config parse | accepted; inbound peer TLS is not terminated from this table in this build |
[mtls]
ca_cert_path = "./certs/ca.pem"
leaf_cert_path = "./certs/leaf-cert.pem"
leaf_key_path = "./certs/leaf-key.pem"
peren devcert ./certs writes ca.pem, leaf-cert.pem, and leaf-key.pem. peren init writes leaf.pem and leaf.key into [mtls]. After peren devcert, edit those two paths so they name the files on disk. Use this command only for local development.
[control]
Optional table. Controls peer-listener mutation checks. Restart required to change.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
control.require_signed_mutations |
bool | no | false |
true, false |
peer control mutations | type errors fail parse |
When require_signed_mutations = true, peer control mutation requests must include Peren control-signature headers. The signature covers the target node id, method, path and query, body bytes, timestamp, and nonce. The node rejects missing signatures, mismatched target-node headers, stale timestamps, body tampering, and nonce replay within the timestamp window.
[control]
require_signed_mutations = true
The signing key is the node data directory’s credential key under credentials/credential.key. Existing fleet files keep unsigned peer mutations unless this setting is enabled.
[routing]
Optional table. Defaults apply when omitted.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
routing.ownership_cache_ttl_secs |
u64 | no | 45 |
non-negative integer | accepted in config | type errors fail parse |
routing.max_forward_retries |
u32 | no | 4 |
non-negative integer | accepted in config | type errors fail parse |
routing.gossip_fanout |
usize | no | 3 |
non-negative integer | accepted in config | type errors fail parse |
This build stores these fields. Live forwarding and gossip paths do not read them in the current node process.
[limits]
Optional table. Defaults apply when omitted. Restart required to change.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
limits.max_request_body_bytes |
u64 | no | 33554432 (32 MiB) |
positive size | HTTP body admission | oversized bodies are refused on the request path |
limits.max_heap_bytes |
u64 | no | 134217728 (128 MiB) |
at least 16 MiB | isolate heap limit | below 16 MiB fails validation |
limits.max_execution_time_ms |
u64 | no | 30000 |
greater than zero | isolate wall-clock limit | 0 fails validation |
limits.max_subrequests_per_invocation |
u32 | no | 10000 |
non-negative integer | subrequest budget per invocation | excess subrequests fail the invocation |
limits.max_isolates |
usize | no | 256 |
greater than zero | isolate admission | 0 fails validation; saturation refuses new work |
limits.isolate_fair_share_percent |
u8 | no | 25 |
1–100 |
validation only in this build | outside range fails validation |
limits.checkpoint_threshold_bytes |
u64 | no | 4194304 (4 MiB) |
non-negative integer | cell replica checkpoint threshold | type errors fail parse |
limits.replica_retention_secs |
u64 | no | 604800 (7 days) |
non-negative integer | storage collection retention for obsolete replica objects | type errors fail parse |
limits.max_cpu_time_ms |
u64 | no | 30000 |
non-negative integer | stored in config | not applied on the live HTTP path in this build |
limits.cron_retry_base_ms |
u64 | no | 2000 |
greater than zero when cron retries are enabled | validation | 0 with cron_retry_max_attempts > 0 fails validation |
limits.cron_retry_max_attempts |
u32 | no | 6 |
non-negative integer | accepted in config | type errors fail parse |
limits.cron_retry_max_backoff_ms |
u64 | no | 64000 |
non-negative integer | accepted in config | type errors fail parse |
limits.max_steps_per_instance |
u64 | no | 100000 |
non-negative integer | accepted in config | type errors fail parse |
[limits]
max_request_body_bytes = 33554432
max_heap_bytes = 134217728
max_execution_time_ms = 30000
max_subrequests_per_invocation = 10000
max_isolates = 256
[logging]
Optional table. Level strings are validated at config load.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
logging.internal_level |
string | no | trace |
trace, debug, info, warn, error |
validation | other strings fail validation |
logging.worker_console_level |
string | no | trace |
same | validation | other strings fail validation |
logging.trace_ownership_hot_path |
bool | no | false |
true, false |
accepted in config | type errors fail parse |
/healthz, /readyz, /metrics, peren logs, and peren tail work without this table. peren logs and peren tail require --service.
[shutdown]
Optional table.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
shutdown.evacuation_deadline_secs |
u64 | no | 40 |
non-negative integer | process shutdown deadline | too low can cut shutdown short before listeners finish |
shutdown.evacuation_concurrency |
usize | no | 8 |
non-negative integer | accepted in config | type errors fail parse |
[rebalance]
Optional table.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
rebalance.interval_secs |
u64 | no | 5 |
non-negative integer | accepted in config | type errors fail parse |
rebalance.placement_weight |
u32 | no | unset | non-negative integer | placement pressure report | type errors fail parse |
This build reports placement_weight through the peer control plane and /metrics. It affects the live placement pressure value operators use during drain and admission decisions. It does not move cells between nodes.
[queues]
Optional table. When omitted, the process uses an in-memory broker.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
queues.broker |
enum | no | memory |
memory, file, cell, nats, rabbitmq, kafka |
queue broker | broker without its connection field fails validation or startup |
queues.file_path |
string | for file |
unset | filesystem path | file broker | missing fails validation |
queues.cell_path |
string | no | {data}/queues/cell.sqlite when unset |
filesystem path | cell broker | invalid path fails broker open |
queues.nats_url |
string | for nats |
unset | NATS URL | NATS broker | missing fails validation |
queues.amqp_url |
string | for rabbitmq |
unset | AMQP URL | RabbitMQ broker | missing fails validation |
queues.kafka_bootstrap_servers |
string | for kafka |
unset | bootstrap server list | Kafka broker | missing fails validation |
[queues.consumer_defaults]
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
queues.consumer_defaults.max_batch_size |
u16 | no | 10 |
1–100 |
queue consumers | outside range fails validation |
queues.consumer_defaults.max_batch_timeout_secs |
u64 | no | 5 |
1–60 |
queue consumers | outside range fails validation |
queues.consumer_defaults.max_retries |
u16 | no | 3 |
at most 100 |
queue consumers | above 100 fails validation |
queues.consumer_defaults.max_concurrency |
u16 | no | 1 |
1–250 |
queue consumers | outside range fails validation |
queues.consumer_defaults.dead_letter_queue |
string | no | unset | queue name | queue consumers | type errors fail parse |
queues.consumer_defaults.retry_delay_secs |
u64 | no | 0 |
at most 86400 |
queue consumers | above 86400 fails validation |
[queues]
broker = "memory"
[queues.consumer_defaults]
max_batch_size = 10
max_batch_timeout_secs = 5
max_retries = 3
max_concurrency = 1
[cache]
Optional table. Default is in-memory. Backs the global caches object. It is not an env binding.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
cache.kind |
enum tag | no | memory |
memory, kv, redis, bucket |
cache provider | unknown kind fails parse |
cache.namespace |
string | for kv |
none | non-empty string | native KV-backed cache | empty fails validation |
cache.url_env |
env var name | for redis |
none | Redis URL env var | Redis cache | missing env fails before listeners open |
cache.endpoint |
string | for bucket |
none | S3-compatible endpoint | bucket cache | missing fails validation |
cache.bucket |
string | for bucket |
none | bucket name | bucket cache | missing fails validation |
cache.prefix |
string | no | "" |
object key prefix | bucket cache | type errors fail parse |
cache.access_key_id_env |
env var name | for bucket |
none | access key env var | bucket cache | missing fails validation |
cache.secret_access_key_env |
env var name | for bucket |
none | secret key env var | bucket cache | missing fails validation |
cache.allow_http |
bool | no | false |
true, false |
bucket cache | HTTP endpoint with false fails client use |
[cache]
kind = "memory"
[secrets]
Optional table. Controls how peren secrets and store-backed secret resolution select a backend. Default provider is local. Non-local providers parse and validate, then refuse at secret-command runtime with an unsupported-provider error in this build.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
secrets.provider |
enum | no | local |
local, aws_secrets_manager, gcp_secret_manager, azure_key_vault, vault |
secrets backend | non-local refuses at rotate/list/get in this build |
secrets.region |
string | for AWS | unset | region string | validation for AWS | missing when provider is AWS fails validation |
secrets.project |
string | for GCP | unset | project id | validation for GCP | missing when provider is GCP fails validation |
secrets.vault_url |
string | for Azure Key Vault or Vault | unset | URL | validation | missing for those providers fails validation |
secrets.credentials_source |
enum | no | configured |
same enum as bucket | validation for external managers | configured or environment with an external manager fails validation |
secrets.prefix |
string | no | unset | prefix string | accepted in config | type errors fail parse |
[secrets_store]
Optional map of store secret name to environment variable name. Values are env var names, not secret material.
[secrets_store]
DB_PASSWORD = "SECRETS_STORE_DB_PASSWORD_ENV"
Unknown names referenced from services.secrets_store_refs fail validation. Missing environment values fail before listeners open.
Refused telemetry tables
These tables parse into the config model. Validation refuses them in this build:
| Path | Validation message |
|---|---|
[logpush] |
Logpush export is not implemented in this build |
Remove the table to pass validation.
[d1_time_travel]
Optional table. Default retention_days is 30.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
d1_time_travel.retention_days |
u64 | no | 30 |
non-negative integer | accepted in config | type errors fail parse |
peren d1 restore refuses. Native D1 has no time-travel snapshots or bookmarks in this build. peren d1 prune-history accepts --retention-days and does not use it.
[[services]]
At least one service is required. Each entry is one Worker bundle and its bindings.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
services[].name |
string | yes | none | 1–63 ASCII letters, digits, -, _ |
sockets, bindings, admission | invalid or duplicate name fails validation |
services[].worker_bundle_path |
path | yes | none | path to .js, .cjs, or .wasm |
isolate loader | missing file fails at startup |
services[].compatibility_date |
date string | yes | none | YYYY-MM-DD |
compatibility resolution | invalid date fails validation |
services[].compatibility_flags |
array of strings | no | [] |
flag names | compatibility resolution | unknown combinations follow compatibility resolution rules |
services[].vars |
string map | no | empty | string values | env plain variables |
type errors fail parse |
services[].expose_node_id |
bool | no | false |
true, false |
injects PEREN_NODE_ID into env |
PEREN_NODE_ID in vars, secrets, secrets_store_refs, or as a secrets_store_secret binding name is reserved when enabled |
services[].secrets |
map of secret declarations | no | empty | env var name string, or { source = "env" | "store", name = "..." } |
env secret strings |
missing secret material fails before listeners open |
services[].secrets_store_refs |
string map | no | empty | keys into [secrets_store] |
secrets | unknown store name fails validation |
services[].consumes_queues |
array | no | [] |
queue name string, or { queue = "...", ...overrides } |
queue consumers | invalid overrides fail validation |
services[].cron_triggers |
array of { expression } |
no | [] |
valid cron expressions | cron scheduler | invalid expression fails validation |
services[].tail_consumers |
array of { service } |
no | [] |
existing service names | tail routing | unknown service fails validation |
services[].additional_modules |
path map | no | empty | module specifier to path | module loader | listing the entry module here fails validation |
services[].source_maps |
path map | no | empty | paths | diagnostics | type errors fail parse |
services[].assets |
table | no | unset | see assets | static file serving before Worker | more than 100 run_worker_first patterns fails validation |
services[].tenant_id |
string | no | unset | id in [[tenants]] |
tenancy validation | unknown tenant fails validation |
services[].project_id |
string | no | unset | id in [[projects]] |
tenancy validation | unknown project or tenant mismatch fails validation |
services[].placement_regions |
array of strings | no | [] |
region labels | accepted in config | type errors fail parse |
services[].max_heap_bytes |
u64 | no | fleet limit | at least 16 MiB | per-service override validation | below 16 MiB fails validation |
services[].max_execution_time_ms |
u64 | no | fleet limit | greater than zero | per-service override validation | 0 fails validation |
services[].max_cpu_time_ms |
u64 | no | unset | non-negative integer | stored | not applied on the live HTTP path |
services[].max_subrequests_per_invocation |
u32 | no | fleet limit | non-negative integer | accepted | type errors fail parse |
services[].isolate_fair_share_percent |
u8 | no | unset | 1–100 |
validation | outside range fails validation |
services[].checkpoint_threshold_bytes |
u64 | no | unset | non-negative integer | service cell replica checkpoint threshold | type errors fail parse |
services[].workflow_retention_days |
u64 | no | unset | non-negative integer | accepted | type errors fail parse |
services[].deploy_max_resident_age_secs |
u64 | no | unset | non-negative integer | accepted | type errors fail parse |
services[].max_steps_per_instance |
u64 | no | unset | non-negative integer | accepted | type errors fail parse |
Set expose_node_id = true only for services that need node-local behaviour. The value is the stable [node].node_id string for the process serving the request. It is not a placement hint, region label, secret, or authorization token.
[services.entrypoint]
Default kind = "stateless". For a Durable Object entrypoint:
[services.entrypoint]
kind = "durable_object"
class_name = "Counter"
unique_key = "counter-v1"
[services.entrypoint.id_from]
source = "first_path_segment"
Or source = "header" with name = "...".
[services.assets]
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
services[].assets.directory |
path | yes when assets set | none | directory path | static serving | missing directory fails at use |
services[].assets.run_worker_first |
bool or string array | no | unset | bool, or at most 100 patterns | request routing | more than 100 patterns fails validation |
An assets binding requires this table.
[services.bindings.NAME]
Bindings use type. Parent table is the current [[services]] entry.
type = "kv"
| Field | Type | Required | Default |
|---|---|---|---|
namespace |
string | yes | none |
unique_key |
string | yes | none |
backend |
tagged table | no | kind = "native" |
Backend kinds: native; redis with url_env; bucket with endpoint, bucket, access_key_id_env, secret_access_key_env, optional prefix, allow_http.
type = "d1_database"
| Field | Type | Required | Default |
|---|---|---|---|
database_name |
string | yes | none |
unique_key |
string | yes | none |
backend |
tagged table | no | kind = "native_sqlite" |
Backend kinds: native_sqlite; turso with url_env, token_env, optional replica_path; external with url_env and driver (postgres, mysql, sqlite) — external backends are refused at query time.
type = "r2_bucket"
| Field | Type | Required | Default |
|---|---|---|---|
endpoint |
string | yes | none |
bucket |
string | yes | none |
credential_scope |
string | yes | none |
region |
string | no | unset |
access_key_env |
env var name | no | unset |
secret_key_env |
env var name | no | unset |
token_env |
env var name | no | unset |
allow_http |
bool | no | false |
prefix |
string | no | unset |
notifications |
array | no | [] (at most 100) |
Each notification needs queue_name, non-empty event_types (object_create, object_delete), optional prefix / suffix.
type = "queue"
| Field | Type | Required |
|---|---|---|
queue_name |
string | yes |
type = "service"
| Field | Type | Required | Default |
|---|---|---|---|
entrypoint |
string | yes | none (must name an existing service) |
props |
JSON | no | null |
class_name |
string | no | unset |
type = "durable_object_namespace"
| Field | Type | Required |
|---|---|---|
class_name |
string | yes |
unique_key |
string | yes |
type = "outbound"
| Field | Type | Required |
|---|---|---|
allowed_hosts |
string array | yes |
Worker calls env.NAME.fetch. A host outside the list throws TypeError. Local, private and metadata-style destinations are refused even when allowlisted. Redirect responses are returned to the Worker; Peren does not follow them in the host client.
type = "aws_sigv4"
| Field | Type | Required | Default |
|---|---|---|---|
credential_source |
enum | yes | none |
region |
string | yes | non-empty |
service |
string | yes | non-empty |
allowed_hosts |
string array | yes | at least one host |
access_key_env |
env var name | for configured / environment |
unset |
secret_key_env |
env var name | for configured / environment |
unset |
token_env |
env var name | no | unset; must be distinct from static key env vars when set |
configured and environment use the named env vars. instance_role, workload_identity, and eks_pod_identity read AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optional AWS_SESSION_TOKEN at request time. They do not call IMDS. See AWS SigV4.
type = "mtls_certificate"
| Field | Type | Required |
|---|---|---|
cert_pem_env |
env var name | yes |
key_pem_env |
env var name | yes |
Missing PEM env vars fail before listeners open. The object has no fetch method. Pass it as fetch(url, { cf: { mtlsCertificate: env.NAME } }).
type = "rate_limiter"
| Field | Type | Required | Constraint |
|---|---|---|---|
limit |
u32 | yes | greater than zero |
period_secs |
u32 | yes | greater than zero |
Counters are process-local.
type = "workflow"
| Field | Type | Required |
|---|---|---|
class_name |
string | yes |
unique_key |
string | yes |
type = "secrets_store_secret"
| Field | Type | Required |
|---|---|---|
secret_name |
string | yes |
Resolves to a string on env. Missing material fails before listeners open.
type = "ai"
| Field | Type | Required | Default |
|---|---|---|---|
endpoint |
string | yes | none |
credential_scope |
string | yes | none |
provider |
tagged table | no | kind = "http" |
Provider kinds: http; openai (api_key_env, optional base_url); anthropic (api_key_env, optional base_url, version); gemini (api_key_env, optional base_url); workers_ai (account_id_env, api_token_env); local (command). Empty required strings fail validation.
type = "vectorize"
| Field | Type | Required | Default |
|---|---|---|---|
endpoint |
string | yes | none |
credential_scope |
string | yes | none |
provider |
tagged table | no | kind = "local" |
Provider kinds: local; http (url, optional token_env); qdrant (url, collection, optional api_key_env); pinecone (url, index, api_key_env, optional namespace); weaviate (url, class_name, optional api_key_env).
type = "hyperdrive"
| Field | Type | Required | Default |
|---|---|---|---|
pgcat_endpoint |
string | yes | none |
credential_scope |
string | yes | none |
caching_disabled |
bool | no | false |
max_age_secs |
u64 | no | 60 |
stale_while_revalidate_secs |
u64 | no | 15 |
pool_max_connections |
u32 | no | 10 (must be greater than zero) |
type = "analytics_engine"
| Field | Type | Required |
|---|---|---|
dataset |
string | yes |
credential_scope |
string | yes |
type = "container"
Requires a top-level [containers] table. Fields: image, default_port, optional env, memory_mb, cpu_millis, idle_sleep_secs (default 300), allow_network_egress (default false).
type = "dispatcher"
| Field | Type | Required |
|---|---|---|
namespace |
string | yes |
Must name a configured [[dispatch_namespaces]] entry.
type = "assets"
No extra fields. Requires services[].assets.
type = "images"
| Field | Type | Required | Default |
|---|---|---|---|
provider |
tagged table | no | kind = "local" |
Provider kinds: local; http with url and optional token_env.
type = "loader"
No extra fields.
[[services]]
name = "api"
worker_bundle_path = "worker.js"
compatibility_date = "2026-01-01"
[services.vars]
ENVIRONMENT = "development"
[services.secrets]
API_KEY = "API_KEY"
[services.bindings.KV]
type = "kv"
namespace = "demo"
unique_key = "demo"
[console]
Optional table. Opens a console listener when present. Restart the process after you change it.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
console.listen |
socket address string | yes when the table is set | none | IP:port |
console listener | not an IP socket address fails validation |
console.data_dir |
path | yes when the table is set | none | directory path | console data | missing fails parse |
console.backend |
enum | no | sqlite when seed_peers is empty, otherwise bucket |
sqlite, bucket |
console storage | unknown value fails parse |
[console]
listen = "127.0.0.1:9102"
data_dir = "data/console"
backend = "sqlite"
[deploy]
Optional table. Restart the process after you change it.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
deploy.enable_preview |
bool | no | true |
true, false |
accepted in config | type errors fail parse |
deploy.poll_interval_secs |
u64 | no | 300 |
non-negative integer | accepted in config | type errors fail parse |
deploy.max_resident_age_secs |
u64 | no | 30 |
non-negative integer | accepted in config | type errors fail parse |
peren deploy still records a digest and a percentage. The percentage does not split traffic, and the running process does not reload the bundle from that command.
[workflow]
Optional table. Defaults apply when the table is omitted. Restart the process after you change it.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
workflow.retention_days |
u64 | no | 0 |
non-negative integer | accepted in config | type errors fail parse |
workflow.sweep_interval_secs |
u64 | no | 3600 |
non-negative integer | accepted in config | type errors fail parse |
workflow.wakeup_sweep_interval_secs |
u64 | no | 15 |
non-negative integer | accepted in config | type errors fail parse |
A workflow binding is type = "workflow" on a service, not this table.
[tracing]
Optional table. Restart the process after you change it.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
tracing.sampling_ratio |
number | no | 1.0 |
0 through 1 inclusive |
validation | outside that range fails validation |
Sampled traces are written locally under ${PEREN_DATA_DIR}/traces/events.jsonl. The active file rotates at 64 MiB, and Peren keeps one previous segment at events.jsonl.1. Use peren trace to read the retained local events.
[otlp]
Optional table. Enables best-effort OTLP HTTP trace export. Local trace JSONL is still written when export is configured. Restart the process after you change this table.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
otlp.endpoint |
string | yes | none | http:// or https:// OTLP traces endpoint |
trace exporter | missing, empty, or non-HTTP(S) fails validation |
otlp.service_name |
string | no | peren-node |
non-empty string | OTLP resource attribute | accepted in config |
otlp.channel_capacity |
usize | no | 1024 |
at least 1 |
exporter queue | 0 fails validation |
otlp.batch_max_spans |
usize | no | 128 |
at least 1 |
exporter batch size | 0 fails validation |
otlp.flush_interval_secs |
u64 | no | 5 |
at least 1 |
exporter flush timer | 0 fails validation |
otlp.max_retries |
u32 | no | 3 |
non-negative integer | exporter retry loop | type errors fail parse |
otlp.retry_base_delay_ms |
u64 | no | 200 |
non-negative integer | exporter retry backoff | type errors fail parse |
otlp.request_timeout_secs |
u64 | no | 10 |
at least 1 |
exporter HTTP timeout | 0 fails validation |
otlp.headers |
map string to string | no | {} |
HTTP header names and values | exporter HTTP request | type errors fail parse |
[tracing]
sampling_ratio = 1.0
[otlp]
endpoint = "https://collector.example.com/v1/traces"
service_name = "peren-node"
batch_max_spans = 128
flush_interval_secs = 5
[containers]
Optional table. A type = "container" binding requires this table. Restart the process after you change it.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
containers.docker_socket |
string | no | unset | socket path | container host | type errors fail parse |
containers.max_instances_per_node |
usize | no | 32 |
non-negative integer | container admission | type errors fail parse |
[[tenants]]
Optional. Each entry names a tenant. Restart the process after you change the list. peren tenant revoke writes a registry under the data directory and does not stop a running isolate.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
tenants[].id |
string | yes | none | unique id using ASCII letters, digits, ., _, or - |
tenant validation; service isolation scope | duplicate or unsafe id fails validation |
tenants[].cell_quota |
u32 | yes | none | greater than zero | tenant validation | 0 fails validation |
[[projects]]
Optional. Each project belongs to a declared tenant.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
projects[].id |
string | yes | none | unique id using ASCII letters, digits, ., _, or - |
tenant validation; service isolation scope | duplicate or unsafe id fails validation |
projects[].tenant_id |
string | yes | none | id in [[tenants]] |
tenant validation | unknown tenant fails validation |
A service that sets project_id must set tenant_id to that project’s tenant. For a scoped service, Peren records and compares the service isolation scope as tenant/<tenant_id>/project/<project_id>/service/<service>. Services without tenant or project metadata keep the service name as their scope, preserving existing unscoped runtime and deployment state.
[[dispatch_namespaces]]
Optional. A type = "dispatcher" binding must name one of these entries.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
dispatch_namespaces[].name |
string | yes | none | unique name | namespace validation | duplicate name fails validation |
dispatch_namespaces[].scripts |
array | no | [] |
script tables | namespace validation | duplicate script name fails validation |
dispatch_namespaces[].scripts[].name |
string | yes | none | unique within the namespace | namespace validation | duplicate fails validation |
dispatch_namespaces[].scripts[].worker_bundle_path |
path | yes | none | Worker bundle path | isolate loader | missing fails parse |
dispatch_namespaces[].scripts[].compatibility_date |
date string | yes | none | YYYY-MM-DD |
compatibility resolution | missing fails parse |
dispatch_namespaces[].scripts[].compatibility_flags |
array of strings | no | [] |
flag names | compatibility resolution | type errors fail parse |
dispatch_namespaces[].scripts[].cell_quota |
u32 | yes | none | greater than zero | namespace validation | 0 fails validation |
dispatch_namespaces[].scripts[].deployment.requested_unique_key |
string | no | "" |
string | accepted in config | type errors fail parse |
dispatch_namespaces[].scripts[].deployment.kv_namespaces |
array of strings | no | [] |
namespace names | accepted in config | type errors fail parse |
dispatch_namespaces[].scripts[].deployment.d1_databases |
array of strings | no | [] |
database names | accepted in config | type errors fail parse |
dispatch_namespaces[].scripts[].deployment.r2_buckets |
array of tables | no | [] |
binding_name, endpoint, bucket, sub_path, credential_scope |
accepted in config | missing field fails parse |
dispatch_namespaces[].scripts[].deployment.durable_object_classes |
array of strings | no | [] |
class names | accepted in config | type errors fail parse |
dispatch_namespaces[].scripts[].deployment.outbound_hosts |
array of strings | no | [] |
host names | accepted in config | type errors fail parse |
[[sockets]]
Public HTTP listeners. Names peer and console are reserved.
| Path | Type | Required | Default | Valid values | Subsystem | Wrong value |
|---|---|---|---|---|---|---|
sockets[].name |
string | yes | none | unique, not peer or console |
listener registry | duplicate or reserved name fails validation |
sockets[].listen |
socket address string | yes | none | IP:port |
public listener bind | invalid address fails validation |
sockets[].service |
string | yes | none | existing services[].name |
request dispatch | unknown service fails validation |
[[sockets]]
name = "public"
listen = "127.0.0.1:8080"
service = "api"
Minimal complete local example
[node]
node_id = "00000000-0000-0000-0000-000000000001"
advertise_addr = "127.0.0.1:7000"
listen = "127.0.0.1:7000"
[bucket]
kind = "file"
path = "./data"
[mtls]
ca_cert_path = "./certs/ca.pem"
leaf_cert_path = "./certs/leaf-cert.pem"
leaf_key_path = "./certs/leaf-key.pem"
[[services]]
name = "api"
worker_bundle_path = "worker.js"
compatibility_date = "2026-01-01"
[[sockets]]
name = "public"
listen = "127.0.0.1:8080"
service = "api"
peren init writes this shape with a new node_id. It does not create worker.js, ./certs, or ./data.
Production-shaped example
[node]
node_id = "b3b6b7f0-2c1a-4e9f-9b1a-0c0f1a2b3c4d"
advertise_addr = "10.0.4.12:7000"
listen = "0.0.0.0:7000"
[bucket]
kind = "s3"
endpoint = "https://s3.example.com"
bucket = "peren-fleet"
region = "us-east-1"
credentials_source = "configured"
access_key_env = "PEREN_BUCKET_ACCESS_KEY"
secret_key_env = "PEREN_BUCKET_SECRET_KEY"
[mtls]
ca_cert_path = "/etc/peren/ca.pem"
leaf_cert_path = "/etc/peren/leaf.pem"
leaf_key_path = "/etc/peren/leaf.key"
[limits]
max_request_body_bytes = 33554432
max_heap_bytes = 134217728
max_execution_time_ms = 30000
max_subrequests_per_invocation = 10000
max_isolates = 256
[[services]]
name = "api"
worker_bundle_path = "/srv/peren/api/worker.js"
compatibility_date = "2026-01-01"
[services.secrets]
API_KEY = "API_KEY"
[[sockets]]
name = "public"
listen = "0.0.0.0:8080"
service = "api"
Related: Configure a fleet, Bindings overview, AWS SigV4.