---
title: The project file
description: .ops/project.ts describes what a repository deploys, the way nuxt.config.ts describes a Nuxt app.
order: 4
---

`.ops/project.ts` sits in the repository and says what it deploys: its services, the
domains in front of them, the steps ops may run, and what differs per environment. Ops
reads it on every scan of the default branch and turns it into resources. It never holds
a secret value: it names secret groups and variables, and ops holds the values.

```ts
import { defineProject } from '@syncronet/ops-project'

export default defineProject({
  services: {
    api: { dockerfile: 'deploy/api.Dockerfile', port: 8080, env_group: 'api',
      address: { url: 'APP_BASE_URL', origins: 'PUBLIC_ORIGIN' } },
    web: { dockerfile: 'deploy/web.Dockerfile', port: 3000, env_group: 'web',
      uses: { api: { url: { internal: 'NUXT_API_ORIGIN' } } } },
  },
  domains: {
    site: { target: { service: 'web' }, api: { service: 'api' } },
  },
  steps: {
    migrate: { service: 'api', command: ['/usr/local/bin/api', 'migrate'] },
  },
  hooks: { 'build.finished': ['migrate'] },
  $production: {
    services: { api: { replicas: 2 } },
    domains: { site: { hostname: 'app.rungholt.dk' } },
  },
  $staging: {
    domains: { site: { hostname: 'staging.rungholt.dk' } },
  },
})
```

You can edit the file in the console too: **Studio** opens it, checks a change before it is
saved, and saves it as a pull request.

## Services

| key | what it sets |
| --- | --- |
| `dockerfile` | the Dockerfile, relative to the repository root |
| `context` | the build context, default `.` |
| `target` | a named build stage |
| `args` | build arguments |
| `port` | the port the container listens on |
| `healthcheck` | the path that answers when the service is ready, starting with `/` |
| `replicas` | 1 to 20 |
| `limits` | `{ cpu, memory_mb }`, 0.1 to 8 cores and 64 to 32768 MB |
| `env_group` | the [secret group](../resources/secrets.md) the service gets at start |
| `address` | variables of the group that receive the service's public address |
| `uses` | the services it calls, and the variables their addresses land in |
| `volumes` | SQLite databases mounted into the container |

`replicas`, `limits`, `env_group` and `address` are the file's only where it sets them;
left out, you set them in the console.

**`address`** writes `https://<hostname>` of the domain routing to the service into `url`,
and the hostname with its aliases into `origins`, in every environment and every preview.
The service picks a changed value up with its next restart, which ops does for you.

**`uses`** names another declared service and where its address goes: `public` gets its
`https://` address, `internal` its address on the environment's private network, which a
staging caller resolves to the staging service and nothing else.

## Environments

The top level holds the defaults. A `$<environment>` block, such as `$production` or
`$staging`, changes them for that environment: objects merge key by key, anything else
replaces, and `null` leaves a service or a domain out. Only `services` and `domains` vary.

## Steps and hooks

A step is a command ops may run, in a declared `service` (borrowing its current image and
secret group) or in an `image` pinned by digest, with an optional `timeout_secs`. A hook
runs steps after an event, once each, in order, in the environment the event happened in:

| event | fires |
| --- | --- |
| `push` | on a push to a branch an environment follows |
| `check_suite.completed` | when the commit's GitHub checks finish |
| `build.finished` | when a build reaches its verdict |
| `deploy.finished` | when a rollout reaches its verdict |
| `pull_request.opened` | in the preview the pull request brought up |

A hook reacts after the fact and never holds back a rollout. The first step that fails
stops the rest.

## Checks

`checks` lists the GitHub check runs a build waits for, by the name GitHub shows. See
[Deployments and previews](deployments-and-previews.md).

## Docs

`docs` names a folder of the repository that every production deploy and every tag's
publish run publishes, and `docsReader` says the project shows doc sets. See [Publish docs](../publish/docs.md).

## When the file changes

A merge to the default branch updates the resources: a new service or step is created,
the declared keys of an existing one are updated, and everything the file does not set
stays as it is. A service the file stops declaring keeps running and is listed as no
longer declared, for you to delete.
