# Write arc.yaml

`arc.yaml` lives next to your compose file and says what compose cannot. This guide writes one for the shop, whose compose file is in the panel. Your own project goes the same way.

## Start from a draft

In the directory with your compose file:

```sh
omniarc sync
```

It reads your project through `docker compose` itself, prints a draft `arc.yaml`, and lists each guess it made. Nothing is written until you add `--write`. Run it again whenever your compose file changes: it adds entries for new services, drops the ones compose no longer defines, and never changes a decision you already made. It will not rewrite a file with comments in it, which would lose them: it prints the draft for you to merge by hand.

For the shop, the draft guesses three things, and one of them is cautious on purpose:

```text
guess: db: assumed `contains_pii: true`, `branch: false`, `kind: database`: branches start it empty; add anonymize rules or set contains_pii: false for test data to branch its data
guess: web: assumed `contains_pii: false`: it keeps nothing (no named volume, no writable bind inside the project, no database or cache image)
guess: edge: web port 3000, the only published HTTP-looking port
```

## Name the arc

```yaml
arc: shop
```

The name of the arc this file deploys to: lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit, at most 20 characters. A deploy to an arc of another name is refused, because that is almost always the wrong directory. See [`arc`](https://docs.omniarc.eu/reference/arc-yaml/arc.md).

## Choose the edge

The edge is the one service that answers the arc's public address, and the container port it listens on. In the shop that is `web` on 3000:

```yaml
edge: { service: web, port: 3000 }
```

Compose must publish that port at one fixed host port, as the shop does with `ports: ["3000:3000"]`. An arc with no web address at all says `edge: none`. The key is required either way, so that having no address is something you chose. See [`edge`](https://docs.omniarc.eu/reference/arc-yaml/edge.md).

## Say what each service is

Every compose service needs an entry under `services`, by its compose name. Each one says whether it holds personal data, with `contains_pii: true` or `false`. The key has no default. The draft guesses it: a service that keeps data (a volume, a writable bind inside the project, a database or cache image) is never assumed to hold none, and one that keeps nothing gets `false`. Review every guess.

`web` is PostgREST, which keeps nothing of its own and serves what is in the database, so the draft's `false` stays, and its kind says what it is:

```yaml
  web:
    kind: web-service
    contains_pii: false
```

`db` is Postgres, and its `customers` table holds email addresses. A branch is a copy that developers and CI can reach, so personal data in it must be rewritten. The draft plays safe with `branch: false`, which starts `db` empty in every branch. To branch its data instead, replace that line with `anonymize`, which rewrites it in the branch's copy only:

```yaml
  db:
    kind: database
    contains_pii: true
    anonymize:
      engine: postgres
      database: shop
      user: shop
      rules:
        - { table: customers, column: email, transform: fake-email }
```

List every column that holds personal data. A rule can `redact`, `hash`, set `"null"`, or write a made-up email, name or phone number. If a rule cannot be applied, the branch is not created, so a half-anonymized copy is never reachable. Only Postgres can be anonymized. A service with personal data in anything else needs `branch: false`, which starts it empty in every branch.

`kind` tunes the arc's storage: `database` gives it the small blocks a database writes best. See [`services`](https://docs.omniarc.eu/reference/arc-yaml/services.md) for every kind and every key.

## Say what the arc needs

Optional, and for the arc as a whole, not one container:

```yaml
resources: { cpu: "1", memory: 1Gi, disk: 5Gi }
```

See [`resources`](https://docs.omniarc.eu/reference/arc-yaml/resources.md). Limits for a single container stay in compose.

## The whole file

Put together, the shop's `arc.yaml`:

```yaml
arc: shop
edge: { service: web, port: 3000 }
resources: { cpu: "1", memory: 1Gi, disk: 5Gi }
services:
  web:
    kind: web-service
    contains_pii: false
  db:
    kind: database
    contains_pii: true
    anonymize:
      engine: postgres
      database: shop
      user: shop
      rules:
        - { table: customers, column: email, transform: fake-email }
```

## Check it

```sh
omniarc check
```

It runs the checks a deploy runs, on your machine, and uploads nothing. Each error names the file to fix and links to a page that explains it, from the [list of errors](https://docs.omniarc.eu/errors.md). With `docker compose` installed it also checks `arc.yaml` against your compose services, and your compose file against what an arc supports.

For completion and checking as you type, put this line at the top of `arc.yaml`. Editors that use the YAML language server read it:

```yaml
# yaml-language-server: $schema=https://docs.omniarc.eu/schema/arc.yaml.json
```

## Services the shop does not have

A cache whose data is not worth copying, or that holds personal data you do not want in a branch, starts empty in every branch:

```yaml
  cache:
    kind: cache
    contains_pii: true
    branch: false
```

A service that only makes sense on your laptop, such as a mail catcher, is not run at all:

```yaml
  mailhog:
    deploy: false
```

A database that should flush to disk before it is copied gets a [`quiesce`](https://docs.omniarc.eu/reference/arc-yaml/services.md#services-quiesce) command. Compose profiles and extra compose files are turned on with [`profiles`](https://docs.omniarc.eu/reference/arc-yaml/profiles.md) and [`compose`](https://docs.omniarc.eu/reference/arc-yaml/compose.md).

Next: [Deploy](https://docs.omniarc.eu/guides/deploy.md).

**Example: shop/docker-compose.yml**

```yaml
# A small shop: Postgres holds the products, customers and orders, and
# PostgREST serves them as a JSON API on port 3000.
services:
  web:
    image: postgrest/postgrest:v12.2.12
    environment:
      PGRST_DB_URI: postgres://shop:${POSTGRES_PASSWORD:-shop}@db:5432/shop
      PGRST_DB_SCHEMAS: public
      PGRST_DB_ANON_ROLE: web_anon
      PGRST_SERVER_PORT: 3000
    ports: ["3000:3000"]
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: shop
      POSTGRES_DB: shop
      # Set a real one with `omniarc env set POSTGRES_PASSWORD`, which asks for it.
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-shop}
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U shop -d shop"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  db-data:
```
