# `spinnery.yml` version 1 fallback reference

This bundled snapshot is the offline fallback for the canonical guide at
<https://spinnery.dev/docs/service-setup>. Use the public guide when available.
Write version 1 manifests only. The CLI temporarily accepts an existing
`version: 2` manifest for compatibility and normalizes it to version 1; do not
introduce version 2 in a new or edited manifest.

## Smallest valid manifest

```yaml
version: 1
compose: {}
```

`compose` is required. An empty mapping defaults `compose.file` to
`docker-compose.yml` and publishes no browser preview. The repository passes
validation only when that Compose file exists and Docker Compose can resolve
it successfully.

## Browser preview

```yaml
version: 1

compose:
  file: docker-compose.yml
  previews:
    - service: web
      port: 5173
      hmr: true

default_preview: web
```

Each preview needs a unique, non-empty Compose `service` and a container `port`
from 1 through 65535. `hmr` is optional and defaults to `false`.
When present, `hmr` must be the YAML boolean `true` or `false`, not a quoted
string, number, or truthy value.
`default_preview` is optional; when present it must name one declared preview.
With exactly one preview, Spinnery infers it as the default. With multiple
previews, declare `default_preview` so the service's bare preview URL and
`service.url` identify the intended primary application.

## Cross-service wiring

The provider declares an output:

```yaml
version: 1

compose:
  previews:
    - service: api
      port: 8080

default_preview: api

outputs:
  api_url:
    from: service.url
```

The consumer declares the same input and maps it to the environment variable
its code already reads:

```yaml
version: 1

compose:
  previews:
    - service: web
      port: 5173
      hmr: true

default_preview: web

inputs:
  api_url:
    required: true

environment:
  VITE_API_URL:
    from: input.api_url
```

Spinnery automatically satisfies an unbound input from a same-name output on
another selected workspace service. Use an explicit project binding only to
override or disambiguate the provider. Do not name another repository in a
manifest and do not add `depends_on`; Compose `depends_on` remains in the
Compose file for services in the same repository.

## Accepted v1 fields

- `version` is required and must be the integer `1`.
- `compose` is a required mapping.
  - `file` is an optional non-empty path; default: `docker-compose.yml`.
  - `previews` is an optional list of `{service, port, hmr}` mappings.
- `default_preview` is an optional top-level preview service name.
- `service` is optional and normally omitted because the connected service owns
  identity. If present, it requires non-empty `name` and `type`; optional
  `storage_mode` is `shared_fs` or `local_mirror`.
- `inputs` is an optional mapping. Each value can be `true`/`false`, `null`, or
  a mapping with optional `required` (boolean), `description` (string or null),
  and `default_value` (a scalar or null). `default` is accepted as an alias for
  `default_value`. When present, `required` must be the YAML boolean `true` or
  `false`, not a quoted string, number, or truthy value.
- `outputs` is an optional mapping. Each value is either a reference string or
  a mapping with required non-empty `from` and optional `description`.
- `environment` is an optional mapping. Each value is either a scalar literal
  or a mapping containing exactly one of:
  - `value`: a scalar literal or null;
  - `from`: a non-empty runtime reference such as `input.api_url`,
    `service.url`, `service.internal_url`, or an available `resource.*` value;
  - `secret`: the non-empty name of a Spinnery-managed secret, never its value.
    Secret mappings may also set `required: false`; secrets are required by
    default. `required` is not valid with `value` or `from`.
- `setup` and `tests` are optional mappings of group names to either one
  non-empty command string or a list of non-empty command strings. Only add
  commands proven by repository evidence.
- `cli` is an optional mapping. Each entry is either a non-empty command string
  or `{command: <non-empty string>, description: <optional string or null>}`.

Top-level `ports`, `start`, `health`, and `resources` are retired; keep those
runtime facts in Docker Compose. A `depends_on` key is rejected anywhere in
`spinnery.yml`. Cross-service references such as `services.api.url` or
`service.api.url` are rejected; declare an input and output instead.

## Secrets

Reference a secret by name only:

```yaml
environment:
  APP_KEY:
    secret: app-key
```

Never read untracked `.env` files, production environment files, credential
files, or private keys. Inspect tracked `.env.development` and
`.env.example`-style files as evidence. Use only variable names from
`.env.development`; treat every value in it as sensitive. Example, sample, and
template files may supply clearly safe placeholder or development defaults.
Never copy a credential, token, private key, ambiguous value, or local secret
value into the manifest, a diff, validation output, or an agent response.

Configure the repository's documented normal development mode before deciding
which credentials it needs. A variable mentioned in source or an example file
is not automatically required. If checked-in evidence proves that ordinary
development works without an external integration, declare its secret as
optional so an unset value is omitted from the runtime environment:

```yaml
environment:
  LOCAL_DEVELOPMENT_MODE:
    value: true
  OPTIONAL_PROVIDER_TOKEN:
    secret: provider-token
    required: false
```

Do not invent a bypass, weaken authentication, or assume a credential is
optional from its name alone. Follow actual checked-in control flow, defaults,
and validation behavior.

## Validation

Run from the repository root:

```sh
spin validate
spin validate ./path/to/repository
spin validate ./spinnery.yml --json
```

The validator is local, requires no login or workspace, and discovers the
enclosing Git checkout as the repository root even when the manifest is nested.
`compose.file` is relative to that repository root, matching the production
runtime. Outside Git, run from the repository root or pass
`--repository-root <path>`. It requires Docker Compose v2 and runs
`docker compose config --services` with a 30-second limit, without printing the
resolved Compose model or raw Compose diagnostics. It exits nonzero when:

- the manifest is missing, malformed, or uses invalid field types;
- `compose.file` is missing or Docker Compose cannot resolve it;
- a declared preview does not exist in the resolved Compose service list;
- an `input.*` reference has no matching `inputs` declaration.

The validator cannot prove that a preview port reaches the intended app, that
an HMR server actually supports HMR, that a project binding supplies a required
input, or that optional commands are appropriate. Review those facts from the
repository and project configuration.

If Docker Compose v2 or `spin validate` is unavailable, report validation as
blocked rather than successful and perform all of these checks manually:

1. Confirm the YAML root is a mapping with integer `version: 1` and a `compose`
   mapping.
2. Resolve `compose.file` (or `docker-compose.yml`) and run
   `docker compose --project-directory <repository> -f <file> config --services`
   successfully.
3. Confirm every preview service exists in the resolved Compose model, preview
   names are unique, and each port is the application container port.
4. Confirm `default_preview`, when present, names a declared preview.
5. Confirm every `input.*` reference has a matching `inputs` declaration and
   every required cross-service need has a same-name provider output on another
   selected service or an explicit project binding.
6. Confirm every environment entry has exactly one literal, reference, or
   secret-name source, contains no secret value, and uses `required` only with
   `secret`. Confirm required secrets are needed by the normal development
   workflow and optional secrets are backed by checked-in behavior.
7. Confirm outputs use resolvable current-service references and no manifest
   contains `depends_on`, retired runtime keys, or another repository reference.
8. Confirm setup, test, and CLI commands come from Compose, Dockerfiles, package
   manifests, scripts, or other checked-in repository evidence.
