Skip to content

Infraspec Reference

Complete field reference for infraspec.yaml.

Top-Level Fields

FieldTypeRequiredDescription
namestringyesApp identifier (used as Nomad job ID, Consul service prefix)
deployboolnoDeployment gate. Defaults to false; it must be explicitly enabled before deploy/recovery workflows include the app
regionsmap[string]RegionTargetnoRegional Nomad targets. Omit for the backward-compatible local global/dc1 target
primaryRegionstringconditionallyRequired when a multi-region spec has scheduled or singleton processes; otherwise defaults to the first region name in lexical order
repoRepoSpecnoGit repository configuration
buildBuildSpecnoDocker build configuration
processesmap[string]ProcessyesNamed process definitions
servicesstring[]noLegacy service list (v1 compat)
secretsstring[]noExpected secret key names
migrationsstringnoPath to migrations directory (relative to repo root)
envmap[string]stringnoStatic environment variables
infrastructureInfrastructurenoBacking service declarations
endpointsEndpoint[]noExternal URL mappings
volumesVolumeSpec[]noHost volume mounts
snapshotsSnapshotPolicynoSnapshot retention defaults
deployPolicyDeployPolicynoDeploy safety policy such as auto-rollback
placementPlacementnoLogical norn-fleet node pool for all processes unless overridden

Process

Each key in the processes map is the process name. The process type is inferred from which fields are set.

FieldTypeDefaultDescription
portintListen port (makes this a service process)
hostPortintOptional fixed host port for platform ingress; incompatible with multiple allocations in a region
commandstringOverride the Docker CMD
schedulestringCron expression (makes this a periodic batch job)
functionFunctionSpecFunction configuration (makes this a batch job)
healthHealthSpecHTTP health check (only for processes with a port)
metricsMetricsSpecPrometheus scrape endpoint for this process
scalingScalingInstance count and autoscaling
drainDrainGraceful shutdown configuration
resourcesResourcescpu: 100, memory: 128CPU (MHz) and memory (MB) limits
tuningTuningPolicyAdvisory resource tuning policy and signal declarations
canaryCanaryConfigCanary allocation count and evaluation window
regionsstring[]all regionsExplicit process placement. Omit for all regions, except scheduled/singleton processes default to primary
singletonboolfalsePin unscheduled singleton work to the primary region unless regions is explicit
nomadVariablesNomad variable filesACL-restricted job-owned Nomad variable values rendered only to private files

Nomad variable files

nomadVariables is an opt-in process-scoped transport for values that must never enter task.Env or command arguments. Norn derives the source strictly as nomad/jobs/<generated-job-id>; InfraSpec cannot select another variable path or submit template text. The generated Nomad templates are always allocation-relative secrets/<destination>, owner-only mode 0400, restart on change, and fail on a missing key. Pipeline-resolved environment values are withheld from that process.

yaml
processes:
  web:
    env:
      MYSQL_DSN_FILE: "${NOMAD_SECRETS_DIR}/mysql-dsn"
    nomadVariables:
      uid: 65532
      gid: 65532
      files:
        - key: MYSQL_DSN
          destination: mysql-dsn

destination is exactly one safe filename (no slash, traversal, or absolute path); keys are uppercase variable names; uid and gid are required positive Unix identities. Use file-path environment variables only, and make the image read its secrets from those files.

Placement

Application specs reference a logical pool and never a provider VM size, region slug, or cloud resource ID:

yaml
placement:
  nodePool: app

processes:
  web:
    port: 8080

nodePool must be DNS-compatible. Standalone InfraSpec validation checks its syntax; passing a fleetDocument to POST /api/v1/validate/infraspec, or using norn validate --file ... --fleet ..., also requires the pool to exist in the pinned norn.dev/fleet/v1 document. Nomad receives the value through its job-level node-pool field, so all processes in an application share the same pool. Split materially different workloads into separate InfraSpecs.

Regions

yaml
primaryRegion: ord
regions:
  ord:
    nomadRegion: us-central
    datacenters: [ord1]
    trafficWeight: 70
  iad:
    nomadRegion: us-east
    datacenters: [iad1]
    trafficWeight: 30

processes:
  web:                    # runs in ord and iad
    port: 8080
    scaling: { min: 3 }
  local-consumer:
    regions: [ord]        # explicit constraint
  leader:
    singleton: true       # defaults to ord
  digest:
    schedule: "0 8 * * *" # defaults to ord
FieldTypeDefaultDescription
nomadRegionstringregion map keyNomad federation region
datacentersstring[][dc1]Eligible Nomad datacenters in that region
trafficWeightint100Desired global traffic weight, activated only after regional readiness passes; set explicitly to 0 for a ready standby region

Normal processes deploy to every declared region. process.regions is a constraint, not an opt-in list required on every process. Scheduled and singleton processes are the exception: they run only in primaryRegion unless they explicitly declare another placement.

Health

FieldTypeDefaultDescription
pathstringHTTP health check path (e.g. /health)
intervalstring10sCheck interval (Go duration)
timeoutstring5sCheck timeout (Go duration)

MetricsSpec

FieldTypeDefaultDescription
enabledboolfalseRegister this process as a Prometheus scrape target
pathstring/metricsMetrics path
portintprocess portInternal metrics port when separate from the main process port

Scaling

FieldTypeDefaultDescription
minint1Minimum instance count
maxintMaximum instance count (for autoscaling)
per_regionintInstance count in each eligible region; overrides min for regional translation
autoAutoScaleAutoscaling configuration

AutoScale

FieldTypeDescription
metricstringScaling metric: cpu, memory, kafka_lag, custom
targetintTarget value for the metric
topicstringKafka topic (required when metric is kafka_lag)

Drain

FieldTypeDefaultDescription
signalstringSIGTERMSignal sent to the process on shutdown
timeoutstringTime to wait after signal before force-killing

Resources

FieldTypeDefaultDescription
cpuint100CPU allocation in MHz
memoryint128Memory allocation in MB

TuningPolicy

tuning declares how Norn should interpret resource signals for a process. The first implementation is advisory: norn tune and /api/tuning/recommendations report current signals and recommended resources or scaling changes, but they do not mutate Nomad jobs.

yaml
processes:
  web:
    resources:
      cpu: 25
      memory: 256
    tuning:
      mode: advisory
      cooldown: 6h
      profiles:
        quiet:
          cpu: 25
          memory: 256
          scale: 1
        normal:
          cpu: 50
          memory: 512
          scale: 1
      limits:
        min:
          cpu: 25
          memory: 128
          scale: 1
        max:
          cpu: 500
          memory: 2048
          scale: 3
      signals:
        - name: live-rss
          source: nomad
          metric: memory_rss
          aggregate: current
        - name: memory-p95
          source: prometheus
          metric: container_memory_working_set_bytes
          window: 24h
          aggregate: p95
FieldTypeDefaultDescription
modestringadvisoryadvisory reports recommendations; auto is reserved for future guarded application
cooldowndurationMinimum interval between automated changes when auto mode is implemented
profilesmap[string]TuningProfileNamed target CPU, memory, and scale profiles such as quiet or busy
limitsTuningLimitsMinimum and maximum recommendation bounds
signalsTuningSignal[]built-in Nomad live signalsSignal declarations used to explain recommendations

TuningProfile

FieldTypeDescription
cpuintCPU allocation in MHz
memoryintMemory allocation in MB
scaleintDesired instance count

TuningLimits

FieldTypeDescription
minTuningProfileLower bound for recommended CPU, memory, and scale
maxTuningProfileUpper bound for recommended CPU, memory, and scale

TuningSignal

FieldTypeDescription
namestringHuman-readable signal name
sourcestringnomad, prometheus, or app
metricstringSignal metric, such as memory_rss, memory_max, or cpu_percent
windowdurationLookback window for historical sources
aggregatestringAggregation such as current, max, or p95

CanaryConfig

FieldTypeDefaultDescription
countintNumber of canary allocations to start before full promotion
evaluateAfterstringDuration to wait before evaluating canary health, such as 2m

When a process declares canary settings, Norn submits the Nomad deployment with canary allocations, waits through the normal health gate, then evaluates allocation health after evaluateAfter. Operators can inspect and promote with norn canary <app> and norn promote <app>.

FunctionSpec

FieldTypeDefaultDescription
timeoutstringMaximum execution time (Go duration)
memoryintMemory override in MB (takes precedence over resources.memory)

Repo

FieldTypeDefaultDescription
urlstringGit clone URL
branchstringmainDefault branch
autoDeployboolfalseAuto-deploy on webhook push
repoWebstringWeb URL for the repo (used in UI links)

Build

FieldTypeDefaultDescription
dockerfilestringDockerfilePath to Dockerfile
teststringTest command (runs before deploy, fails pipeline on error)
imagestringExternally published OCI image. Production requires an immutable image@sha256:... whose signature is bound to the resolved Git commit.

Infrastructure

FieldTypeDescription
postgres.databasestringDatabase name for snapshots, migrations, and DATABASE_URL
redis.namespacestringRedis key namespace
kafka.topicsstring[]Kafka topics to declare
nats.streamsstring[]NATS JetStream stream names
objectStorage.providerstringS3-compatible provider hint; defaults to garage
objectStorage.bucketsObjectStorageBucket[]Buckets to provision and expose to the app

ObjectStorageBucket

FieldTypeDefaultDescription
namestringDNS-compatible bucket name
accessstringreadWritereadOnly, readWrite, or owner
publicboolfalseReserved for future public exposure policy
prefixstringOptional object key prefix exposed as S3_PREFIX...
envstringderived from bucket nameEnv alias for S3_BUCKET_<ENV>

Endpoints

FieldTypeDescription
urlstringExternal hostname (maps to cloudflared ingress rule)
regionstringOptional region hint

Volumes

FieldTypeDefaultDescription
namestringNomad host volume name
mountstringMount path inside the container
readOnlyboolfalseMount as read-only

SnapshotPolicy

FieldTypeDefaultDescription
keepint3Newest local snapshots to keep when retention runs without --keep
preRestoreboolfalseCreate a safety snapshot before restore when the API or CLI does not override the restore request
retentionEnabledboolfalseReserved flag for scheduled retention automation
exportBucketstringS3-compatible bucket for norn snapshots export/remote/import

DeployPolicy

FieldTypeDefaultDescription
autoRollbackbooltrueQueue rollback to the last successful deployment when the deploy health gate fails

Because autoRollback defaults to enabled, omit deployPolicy for normal apps. Set autoRollback: false when a failed health gate should stop for manual operator review.

Defaults Summary

SettingDefault Value
resources.cpu100 MHz
resources.memory128 MB
health.interval10s
health.timeout5s
scaling.min1
repo.branchmain
snapshots.keep3
deployPolicy.autoRollbacktrue
deployfalse

Full Example

A real-world infraspec for an app with a web process, background worker, cron job, and database:

yaml
name: signal-sideband
deploy: true

repo:
  url: git@github.com:antiartificial/signal-sideband.git
  branch: master
  autoDeploy: true
  repoWeb: https://github.com/antiartificial/signal-sideband

build:
  dockerfile: Dockerfile
  test: go test ./...

processes:
  web:
    port: 8080
    command: ./signal-sideband
    health:
      path: /health
    metrics:
      enabled: true
      path: /metrics
    scaling:
      min: 1
    resources:
      cpu: 200
      memory: 256
    canary:
      count: 1
      evaluateAfter: 2m
  poller:
    command: ./signal-sideband --mode=poller
    resources:
      cpu: 100
      memory: 128
  digest:
    schedule: "0 8 * * *"
    command: ./signal-sideband --mode=digest

secrets:
  - DATABASE_URL
  - OPENAI_API_KEY
  - FILTER_GROUP_ID

migrations: ./migrations

env:
  LOG_LEVEL: info
  TZ: America/New_York

infrastructure:
  postgres:
    database: signal_sideband
  objectStorage:
    provider: garage
    buckets:
      - name: signal-sideband-attachments
        access: readWrite
        env: ATTACHMENTS

snapshots:
  keep: 5
  exportBucket: signal-sideband-snapshots

deployPolicy:
  autoRollback: true

endpoints:
  - url: signal.example.com

volumes:
  - name: signal-data
    mount: /var/lib/signal-cli