# pier.yaml

> Every key of the project file — top level, environments, and each resource kind

URL: https://pier.run/docs/pier-yaml

`pier.yaml` declares a project: its environments and, per environment, every resource.
`pier apply` converges the platform to it; `pier deploy` also builds and rolls
([Apply and deploy](/docs/apply-and-deploy)). The file is found by walking up from the
current directory ([CLI configuration](/docs/cli/configuration)).

```yaml title="pier.yaml"
version: 1
name: acme                  # display name
project: acme-8fb4          # slug, generated at pier config init — do not edit
vcs:                        # written by pier github connect
  provider: github
  repo: acme-inc/acme
  auto_deploy: true
templates:                  # reusable target fragments, applied with `use:`
  web:
    replicas: 2
environments:
  prod:
    protected: true
    env:
      LOG_LEVEL: { value: info }
    apps: [ … ]
    sites: [ … ]
    postgres: [ … ]
    buckets: [ … ]
    volumes: [ … ]
  staging:
    …
```

## Top level

| Key            | Type   | Meaning                                                                                                                                                |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `version`      | int    | `1`                                                                                                                                                    |
| `name`         | string | display name                                                                                                                                           |
| `project`      | string | the project slug; generated                                                                                                                            |
| `workspace`    | string | optional; pins the workspace when a user belongs to several                                                                                            |
| `vcs`          | object | `provider` (`github`), `repo` (`owner/name`), `auto_deploy` (default `true`), `commit_status` (default `true`) — [GitHub](/docs/github)                |
| `templates`    | map    | named fragments (`replicas`, `autoscale`, `dockerfile`) a target or app pulls in with `use:`; expanded by the CLI before anything reaches the platform |
| `environments` | map    | slug → environment; each is a complete, independent declaration ([Concepts](/docs/concepts#environment))                                               |

## Environment

| Key                                               | Type   | Meaning                                                                         |
| ------------------------------------------------- | ------ | ------------------------------------------------------------------------------- |
| `name`                                            | string | display name (default: the slug)                                                |
| `protected`                                       | bool   | `pier dev` asks before pulling this environment's secrets; the console marks it |
| `env`                                             | map    | variables at environment scope — [Environment variables](/docs/vars)            |
| `apps`, `sites`, `postgres`, `buckets`, `volumes` | lists  | the resources                                                                   |

## Variables (`env`)

```yaml
env:
  LOG_LEVEL:    { value: info }
  API_KEY:      { secret: true }
  DATABASE_URL: { from: postgres.main.url }
  OPTIONAL:     { value: "1", required: false }
```

`value` · `secret` · `from` (`postgres.<name>.url|host|port|user|password|database`,
`buckets.<name>.url|name|endpoint|access_key|secret_key`) · `required` (default `true`).
Allowed at environment, app/site and target scope; the narrowest wins.

## App

```yaml
apps:
  - name: api
    build_context: ./api         # or targets[].image
    template: node_hono          # starter, instead of build_context
    env: { … }
    previews:
      enabled: true
      branch_pattern: "feat/*"
      plan: app-s
      ttl_hours: 72
      max_targets: 5
      env: { … }
    targets:
      - name: primary
        primary: true
        use: web                 # a template
        plan: app-s
        replicas: 2              # or autoscale
        autoscale:
          min: 1
          max: 3
          idle_after: 10m        # with min 0: scale to zero
          triggers:
            - { type: cpu, target: 70 }
            - { type: latency, target_ms: 250 }
        image: traefik/whoami:v1.10.1   # static-image flow; exclusive with build_context
        port: 80                        # required with image; default 8080 for builds
        dockerfile: Dockerfile.worker
        deploy:
          branch: main                  # or branch_pattern
        domains:
          - host: api.acme.example
        volumes:
          - { volume: data, mount_path: /var/lib/app }
        env: { … }
```

Trigger types: `cpu`, `memory` (`target` percent), `rps` (`target` requests per second per
instance), `latency` (`target_ms`). [Apps](/docs/resources/apps) · [Plans](/docs/plans).

## Site

```yaml
sites:
  - name: www
    build_context: ./www
    template: site_astro
    env: { … }
    previews: { … }              # as for apps, without plan
    targets:
      - name: primary
        primary: true
        framework: vite          # vite · nextjs-static · astro · sveltekit-static · hugo · static · custom
        build_command: npm run build
        output_dir: dist
        index_document: index.html
        spa_fallback: true
        routes:
          redirects: [ { source: /old, destination: /new, status: 308 } ]
          rewrites:  [ { source: /api/*, destination: /v2/* } ]
          headers:   [ { path: /assets/*, headers: { Cache-Control: "public, max-age=31536000" } } ]
          fallback:  { type: spa, paths: [/app/*], exclude: [/api/*], document: index.html }
        deploy: { branch: main }
        domains: [ { host: www.acme.example } ]
        env: { … }
```

[Sites](/docs/resources/sites).

## Postgres

```yaml
postgres:
  - name: main
    targets:
      - name: primary
        version: "17"
        plan: pg-s
        replicas: 1
        bootstrap_default_database: app
        max_storage_gb: 50
```

Exactly one target per environment. [Postgres](/docs/resources/postgres).

## Bucket

```yaml
buckets:
  - name: uploads
    access: private              # or public
```

[Buckets](/docs/resources/buckets).

## Volume

```yaml
volumes:
  - name: data
    storage_gb: 5
    durability: standard         # or ha; set at creation
```

Mounted by an app target's `volumes:` binding. [Volumes](/docs/resources/volumes).

## Validation

`pier config validate` checks the file without a server call: syntax, names, that every
`from:` names a resource in the same environment, that `image` and `build_context` are not
both set, that `replicas` and `autoscale` are not both set, one Postgres target per
environment. `pier apply --dry-run` shows what the platform would change.
