guv.yaml reference
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.jsonThe 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 file
Section titled “The smallest useful file”# 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.jsonversion: 1project: my-app
services: web: port: 3000A complete example
Section titled “A complete example”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.
$schema: https://guv.dev/schema/v1.jsonversion: 1project: primecarersregion: lon1 # cell selection is ours, region is the user'sresidency: uk-only # enforced for compute, volumes, backups, logsclassification: 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-productionTop-level fields
Section titled “Top-level fields”| 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
Value formats
Section titled “Value formats”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
Section titled “duration”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
percent
Section titled “percent”Percentage, as 70% or a number from 1 to 100.
Examples: 70%, 80
hostname
Section titled “hostname”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 }}"}