Skip to content

guv.yaml reference

View as Markdown

Guv manifest, version 1. One file at the repository root describes every service, database, volume, domain, cron job and environment of a project. Secrets never live in this file; it holds only references (secret://name). References of the form ${{ … }} resolve at deploy time. Removing a database or volume from the file never deletes it: guv plan shows it as orphaned and it is retained.

Add this line to the top of the file and most editors will autocomplete and check it as you type:

$schema: https://guv.dev/schema/v1.json

The schema itself is published at https://guv.dev/schema/v1.json. Check a file locally with guv validate, see what would change with guv plan, and make it so with guv apply.

# The smallest useful guv.yaml: one web service, built by auto-detection, deployed from the default branch
# to a single production environment.
$schema: https://guv.dev/schema/v1.json
version: 1
project: my-app
services:
web:
port: 3000

A real-world manifest for a care provider: a web app, a private API, a worker, a nightly job, highly available Postgres with masking for previews, and a spend cap.

guv.yaml
$schema: https://guv.dev/schema/v1.json
version: 1
project: primecarers
region: lon1 # cell selection is ours, region is the user's
residency: uk-only # enforced for compute, volumes, backups, logs
classification: special-category # turns on PII log masking, branch masking, dedicated pool eligibility
environments:
production:
branch: main
protection: { require_approval: true } # deploys/changes by agents need human OK
staging:
branch: develop
preview:
pull_requests: true
ttl: 72h
spend_cap: 5GBP
databases: branch-masked # ZFS clone + masking rules below
defaults:
sleep_when_idle: false
resources: { cpu: 1, memory: 1GB }
services:
web:
root: apps/web
watch: ["apps/web/**", "packages/ui/**", "pnpm-lock.yaml"]
build:
builder: railpack # railpack | dockerfile | image
# dockerfile: apps/web/Dockerfile
start: pnpm --filter web start
port: 3000
healthcheck: { path: /healthz, timeout: 10s }
pre_deploy: pnpm --filter db migrate deploy
domains:
- www.primecarers.co.uk
- primecarers.co.uk
replicas: { min: 2, max: 6, scale_on: { cpu: 70%, concurrency: 80 } }
resources: { cpu: 2, memory: 2GB } # caps; billed on actual use
env:
NODE_ENV: production
DATABASE_URL: ${{ databases.main.pooled_url }}
REDIS_URL: ${{ databases.cache.url }}
INTERNAL_API: http://${{ services.api.internal_host }}:8080
STRIPE_SECRET_KEY: secret://stripe-secret-key # value set via CLI/UI, never in git
NEXT_PUBLIC_POSTHOG_KEY: { value: phc_xxx, public: true } # explicitly allowed in client bundle
api:
root: services/api
build: { builder: dockerfile, dockerfile: services/api/Dockerfile }
port: 8080
private: true # no public domain; internal network only
resources: { cpu: 1, memory: 1GB }
env:
DATABASE_URL: ${{ databases.main.url }}
JWT_SIGNING_KEY: secret://jwt-signing-key
rota-worker:
root: services/rota
start: node dist/worker.js
private: true
volumes:
- name: rota-cache
mount: /data
nightly-reports:
root: jobs/reports
cron: "0 2 * * *" # Europe/London
timezone: Europe/London
timeout: 30m
databases:
main:
engine: postgres
version: "18"
plan: ha
storage: 50GB
backups: { pitr: 30d }
extensions: [pgvector, pg_trgm]
maintenance_window: "Sun 02:00-04:00"
masking: # used for preview/branch clones
clients.full_name: fake_name
clients.nhs_number: null
clients.date_of_birth: shift_days(±30)
carers.email: hash_email
cache:
engine: valkey
plan: standard
volumes:
rota-cache: { size: 5GB, ha: false }
alerts:
- name: api-errors
when: logs(service=api, level=error) > 20/min
notify: [email:ops@primecarers.co.uk, webhook:https://hooks.slack.com/...]
spend:
monthly_cap: 400GBP
on_cap: sleep-non-production
Field Type What it does
$schema string URL of this schema, for editor autocomplete.
version * 1 Manifest format version.
project * name Project slug.
name string Human-friendly project name shown in the dashboard.
region lon1 Region the project runs in.
residency uk-only, uk-eu Where compute, volumes, backups and logs may be stored.
classification standard, personal-data, special-category Data classification.
include list of string Globs of per-service files (guv.service.yaml) to merge into this manifest.
environments map of names to object Environments by name.
defaults object Defaults applied to every service before its own settings.
services map of names to object Services by name.
databases map of names to object Managed databases by name.
volumes map of names to object Persistent volumes by name, mounted by services through services.<name>.volumes.
variables env Shared variables available to every service in every environment, and to references as ${{ env.NAME }}.
alerts list of object Alert rules.
spend object Monthly spend cap for the whole project.
canvas object Presentation-only dashboard layout.

* required

These formats are used throughout the manifest.

Lowercase name (letters, digits and hyphens, starting with a letter, up to 63 characters).

Pattern: ^[a-z][a-z0-9-]{0,62}$

Examples: web, rota-worker

Duration such as 10s, 30m, 72h, 30d or 1h30m.

Pattern: ^([0-9]+(ms|s|m|h|d))+$

Examples: 10s, 72h

Size in MB, GB or TB (powers of 1000 for billing, 1024 for limits).

Pattern: ^[0-9]+(\.[0-9]+)?\s?[MmGgTt][Bb]$

Examples: 512MB, 2GB

Amount in pounds sterling, written 400GBP, 5.50 GBP or £400.

Pattern: ^(£\s?[0-9]+(\.[0-9]{1,2})?|[0-9]+(\.[0-9]{1,2})?\s?GBP)$

Examples: 400GBP, £10

Percentage, as 70% or a number from 1 to 100.

Examples: 70%, 80

A fully qualified hostname you control. *.example.com needs DNS delegation for certificates.

Pattern: ^(\*\.)?([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$

Examples: www.primecarers.co.uk

A variable value: a literal; a reference ${{ databases.<n>.url|pooled_url|host|port|user|password }}, ${{ services.<n>.internal_host|public_url }} or ${{ env.NAME }}; or secret://name for a secret set separately (guv secrets set name). A value that references a secret is itself secret. Use the object form to mark a value public (allowed in client bundles) or available at build time.

Examples: production, ${{ databases.main.pooled_url }}, secret://stripe-secret-key, {"value":"phc_xxx","public":true}

Environment variables by name.

Examples: {"NODE_ENV":"production","DATABASE_URL":"${{ databases.main.pooled_url }}"}