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:
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: /datav1 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:
| Type | Identifying Field | Nomad Job Type | Description |
|---|---|---|---|
| Service | port | Service | Long-running process with a port, registered in Consul |
| Worker | neither port nor schedule nor function | Service | Long-running process without a port |
| Cron | schedule | Periodic Batch | Runs on a cron schedule |
| Function | function | Batch (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.
| Service | Fields | Purpose |
|---|---|---|
postgres | database | PostgreSQL database name — used for snapshots, migrations, and DATABASE_URL injection |
redis | namespace | Redis namespace prefix |
kafka | topics | Kafka topic list |
nats | streams | NATS JetStream stream names |
objectStorage | provider, buckets | S3-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.
volumes:
- name: signal-data
mount: /var/lib/signal-cli
readOnly: falseSee 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:
secrets:
- DATABASE_URL
- API_KEY
- SMTP_PASSWORDSee Secrets for the full workflow.
Metrics
Long-running services and workers can expose Prometheus-compatible metrics. Add a metrics block to the process:
processes:
web:
port: 8080
metrics:
enabled: true
path: /metricsNorn 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:
norn services
norn networknorn 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:
- Clone — checkout the repo at the specified ref
- Build — build the Docker image
- Test — run the test command (if defined)
- Snapshot — pg_dump the database (if postgres infrastructure defined)
- Migrate — run database migrations (if migrations path defined)
- Submit — translate infraspec to Nomad jobs and submit
- Healthy — poll Nomad allocations until healthy
- Forge — update cloudflared ingress rules (if endpoints defined)
- 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.
endpoints:
- url: myapp.example.com
- url: myapp-staging.example.com
region: us-eastSee Cloudflare for tunnel configuration.