opsdocsConsole

Ops platform / Concepts

The project file

.ops/project.ts describes what a repository deploys, the way nuxt.config.ts describes a Nuxt app.

Updated 13 Oct 2026View as Markdown

.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

keywhat it sets
dockerfilethe Dockerfile, relative to the repository root
contextthe build context, default .
targeta named build stage
argsbuild arguments
portthe port the container listens on
healthcheckthe path that answers when the service is ready, starting with /
replicas1 to 20
limits{ cpu, memory_mb }, 0.1 to 8 cores and 64 to 32768 MB
env_groupthe secret group the service gets at start
addressvariables of the group that receive the service's public address
usesthe services it calls, and the variables their addresses land in
volumesSQLite 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:

eventfires
pushon a push to a branch an environment follows
check_suite.completedwhen the commit's GitHub checks finish
build.finishedwhen a build reaches its verdict
deploy.finishedwhen a rollout reaches its verdict
pull_request.openedin 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.

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.

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.