Skip to content

Concepts

App Discovery

Norn scans NORN_APPS_DIR (default: ~/projects) for directories containing an infraspec.yaml file. Each discovered spec becomes an app in the dashboard and CLI.

There is no registration step — drop an infraspec.yaml into your project and Norn picks it up on the next scan.

Infraspec Anatomy

The infraspec is a YAML file that declares everything Norn needs to manage your app:

yaml
name: myapp

repo:
  url: git@github.com:you/myapp.git
  branch: main
  autoDeploy: true

build:
  dockerfile: Dockerfile
  test: make test

processes:
  web:
    port: 8080
    command: ./server
    health:
      path: /health
    metrics:
      enabled: true
      path: /metrics
    scaling:
      min: 2
  worker:
    command: ./worker
  cleanup:
    schedule: "0 3 * * *"
    command: ./cleanup

secrets:
  - DATABASE_URL
  - API_KEY

migrations: ./migrations

env:
  LOG_LEVEL: info

infrastructure:
  postgres:
    database: myapp
  objectStorage:
    provider: garage
    buckets:
      - name: myapp-uploads
        access: readWrite
        env: UPLOADS

endpoints:
  - url: myapp.example.com

volumes:
  - name: data
    mount: /data

v1 vs v2

In v1, apps declared a single role (webserver, worker, cron, function). In v2, an app declares a processes map — multiple named processes of different types coexist in one spec. This means a single app can have a web server, a background worker, and a cron job without splitting into separate specs.

Process Types

The process type is inferred from its fields:

TypeIdentifying FieldNomad Job TypeDescription
ServiceportServiceLong-running process with a port, registered in Consul
Workerneither port nor schedule nor functionServiceLong-running process without a port
CronschedulePeriodic BatchRuns on a cron schedule
FunctionfunctionBatch (one-shot)HTTP-triggered ephemeral execution

Infrastructure Dependencies

The infrastructure block declares backing services your app needs. Norn uses this for health checks, connection string injection, and snapshot management.

ServiceFieldsPurpose
postgresdatabasePostgreSQL database name — used for snapshots, migrations, and DATABASE_URL injection
redisnamespaceRedis namespace prefix
kafkatopicsKafka topic list
natsstreamsNATS JetStream stream names
objectStorageprovider, bucketsS3-compatible buckets provisioned through Garage or another S3 endpoint

Object storage is platform-scoped but app-declared: a single local Garage service can host many buckets, while each app declares the buckets and access it needs. See Object Storage for Garage setup and runtime env details.

Volumes

Volumes map to Nomad host volumes. The Nomad client must have a matching host_volume stanza in its configuration.

yaml
volumes:
  - name: signal-data
    mount: /var/lib/signal-cli
    readOnly: false

See Volumes for setup details.

Secrets

Secrets are encrypted with SOPS + age and stored alongside the infraspec in a secrets.enc.yaml file. During deploy, the pipeline decrypts secrets and injects them as environment variables into Nomad tasks.

The secrets list in the infraspec declares which secret keys the app expects:

yaml
secrets:
  - DATABASE_URL
  - API_KEY
  - SMTP_PASSWORD

See Secrets for the full workflow.

Metrics

Long-running services and workers can expose Prometheus-compatible metrics. Add a metrics block to the process:

yaml
processes:
  web:
    port: 8080
    metrics:
      enabled: true
      path: /metrics

Norn exposes its own control-plane metrics at /metrics, can generate Prometheus scrape config at /api/observability/prometheus.yml, and can install generated norn-prometheus, norn-grafana, and norn-cadvisor app directories with norn observability install. See Observability for the local 30-day Prometheus/Grafana setup.

Networking Truth

Norn classifies endpoints and live service instances as local, private, public, or none. Use:

bash
norn services
norn network

norn services shows the service manifest table. norn network adds validation hints and guidance for the active NORN_NETWORK_MODE, including when to use 127.0.0.1, host.docker.internal, Tailscale/private addresses, and cloudflared/public hostnames.

Deploy Pipeline

When you deploy an app, Norn runs a 9-step pipeline:

  1. Clone — checkout the repo at the specified ref
  2. Build — build the Docker image
  3. Test — run the test command (if defined)
  4. Snapshot — pg_dump the database (if postgres infrastructure defined)
  5. Migrate — run database migrations (if migrations path defined)
  6. Submit — translate infraspec to Nomad jobs and submit
  7. Healthy — poll Nomad allocations until healthy
  8. Forge — update cloudflared ingress rules (if endpoints defined)
  9. Cleanup — remove temp files

Each step emits saga events and WebSocket broadcasts for real-time progress tracking.

Before the full deploy, norn preflight <app> [ref] runs the non-mutating rehearsal path: validate, clone, inspect, build, and test. It catches missing Dockerfiles, unreadable declared secrets, build failures, test failures, and source-shape surprises before snapshot, migration, Nomad submit, or cloudflared changes happen.

See Deploy Pipeline for the full architecture.

Endpoints

Endpoints map to Cloudflare Tunnel ingress rules. Defining an endpoint causes Norn to use static ports (for predictable routing) and configure cloudflared during the forge step.

yaml
endpoints:
  - url: myapp.example.com
  - url: myapp-staging.example.com
    region: us-east

See Cloudflare for tunnel configuration.