# Get started

In a few minutes you deploy a small shop, a JSON API over a Postgres database, and then branch it: a second, full copy of the app with its own copy of the data, at its own address.

You need Docker with `docker compose` on your machine, on Linux or macOS.

## 1. Install the CLI

```sh
curl -fsSL https://omniarc.eu/install.sh | sh
```

It puts `omniarc` in `~/.local/bin`, with no sudo, and prints the line to add if that directory is not on your `PATH`.

## 2. Get a key

Create an account at [dashboard.omniarc.eu](https://dashboard.omniarc.eu). During the beta an account comes from an invite: the link in it creates the account. Open **Keys**, mint a key with the role `admin` (deploying needs it), and copy it: it is shown once. Then give it to the CLI:

```sh
export OMNIARC_KEY=<the key you copied>
```

That is the only setting. Every command finds OmniArc on its own.

## 3. Make the shop

The shop is two compose services: `web`, [PostgREST](https://postgrest.org) answering HTTP on port 3000, and `db`, Postgres with a few products, customers and orders. Paste this into a terminal to create it in a new `shop` directory:

```sh
mkdir -p shop/db && cd shop
cat > docker-compose.yml <<'EOF'
# 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:
EOF
cat > db/init.sql <<'EOF'
-- Runs once, when the database is first created.
create table products (
  id serial primary key,
  name text not null,
  price_cents integer not null
);
create table customers (
  id serial primary key,
  email text not null
);
create table orders (
  id serial primary key,
  customer_id integer not null references customers,
  product_id integer not null references products,
  quantity integer not null default 1
);

insert into products (name, price_cents) values ('Mug', 1200), ('T-shirt', 2500), ('Poster', 900);
insert into customers (email) values ('ada@example.com'), ('alan@example.com');
insert into orders (customer_id, product_id, quantity) values (1, 1, 2), (2, 3, 1);

-- The API answers anonymous requests as web_anon, which can read the
-- catalogue and nothing else: customers and orders stay private.
create role web_anon nologin;
grant usage on schema public to web_anon;
grant select on products to web_anon;
grant web_anon to shop;
EOF
```

Nothing in it is written for OmniArc. It is an ordinary compose project, and `docker compose up` runs it on your machine too.

## 4. Write arc.yaml

`arc.yaml` sits next to the compose file and says what compose cannot. Let the CLI draft it from your compose file:

```sh
omniarc sync --write --arc shop
```

```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
wrote ./arc.yaml
```

A service that keeps data is assumed to hold personal data, because a wrong guess the other way would copy real customers into every branch, and it starts empty in every branch until you say how to anonymize it. `web` keeps nothing of its own, so it holds none. Ask whether it is ready:

```sh
omniarc check
```

It is, and it would deploy as it is. But a branch of it would start with an empty database, which is not much of a copy. To branch `db`'s data, say how to anonymize it: open `arc.yaml` and make it say this, the file in the panel:

```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 }
```

`web` keeps no data of its own, so it holds no personal data. `db` holds customers' email addresses, and every branch gets made-up ones instead. The `edge` is the one service that answers the arc's public address. Run `omniarc check` again, and it ends with `checks ok`.

## 5. Deploy

```sh
omniarc deploy
```

The first deploy creates the arc. It uploads the project, builds and starts it, and follows it until every service is up:

```text
  created arc shop
  ...
  revision 1  healthy
```

Every arc starts with one branch, `production`. Its public address is made within about five minutes of the first deploy:

```sh
omniarc arc branch url shop production
```

```text
url  https://<hex>.arc.omniarc.eu (pending)
```

The address is ten random hex characters under `arc.omniarc.eu`. It says `(pending)` until its certificate is issued, `(ready)` after. If it answers that the branch has no public URL yet, ask again in a minute. Open the address with `/products` on the end, and the shop answers with its catalogue:

```text
[{"id":1,"name":"Mug","price_cents":1200},
 {"id":2,"name":"T-shirt","price_cents":2500},
 {"id":3,"name":"Poster","price_cents":900}]
```

The customers and orders stay private: `/customers` answers `permission denied`, as `init.sql` asked.

## 6. Branch it

```sh
omniarc arc branch create shop production preview --wait-url
```

```text
shop/production → shop/preview
anonymized  db: 1 rule(s), 2 row(s)
url  https://<hex>.arc.omniarc.eu (pending)
waiting for the URL to serve (up to 180 s)...
url  https://<hex>.arc.omniarc.eu (ready)
```

It waits, up to three minutes, until the branch's own address serves. `preview` is the whole shop again: both services, a copy of the database, and that address. In its copy, the two customers' email addresses are made up. Open that address with `/products` on the end: same catalogue, separate data. Whatever you do to `preview` never touches `production`.

When you are done with it:

```sh
omniarc arc branch destroy shop preview
```

## Next

- [The model](https://docs.omniarc.eu/guides/the-model.md): arcs, branches and services, in one page.
- [Write arc.yaml](https://docs.omniarc.eu/guides/write-arc-yaml.md) for your own compose project.
- [Branches](https://docs.omniarc.eu/guides/branches.md): a copy for every pull request.

**Example: shop/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 }
```
