---
title: Deploy your first project
description: Create a project, describe it in .ops/project.ts, connect the repository and push.
order: 2
---

A project is one product or app: its services, databases, domains and secrets, in one or
more environments. This guide takes a repository with a Dockerfile to a running service in
production.

## 1. Create the project

In the console, open **Projects** and choose **New project**. Give it a name; the slug
follows the name until you edit it. The slug cannot change later, so pick a short one such
as `rungholt`. A new project has one environment, `production`.

Creating projects needs an ops wide role. If you cannot, ask an administrator to create it
and add you as a member.

## 2. Describe the project in the repository

Add `.ops/project.ts` to the repository. It says what the repository deploys:

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

export default defineProject({
  services: {
    api: { dockerfile: 'Dockerfile', port: 8080, env_group: 'api' },
  },
})
```

Each service names its Dockerfile and the port the container listens on. `env_group` is
the [secret group](../resources/secrets.md) whose variables the service gets at start.
The file is read by ops, not run by your app, so the import needs nothing installed. See
[The project file](../concepts/project-file.md) for everything it can hold.

## 3. Connect the repository

Open **Settings**, then **Sources**, and choose **Connect repository**. If no repository is
listed, the GitHub App is not installed on your organisation yet: **Install on GitHub**
opens the installation, and the page fills in by itself when you come back.

Pick the repository. Ops scans it and lists the services it would deploy. Keep the ones you
want and choose **Create**. Each becomes a service in production.

## 4. Add the variables

Open **Secrets**, add the group `api` and its variables, then press **Render** so the
next deploy carries them. From a terminal:

```sh
ops secrets set api SESSION_KEY=...
ops secrets set api LOG_LEVEL=info --text
ops secrets rotate api
```

## 5. Push

Push to the default branch. Ops waits for the repository's GitHub checks, then builds the
image, scans it, pushes and signs it, deploys it and watches it come up healthy. The commit
on GitHub shows the deployment and its status.

Follow it in **Deployments**, or from a terminal:

```sh
ops resources        # every resource in production and its state
ops logs -f          # follow the logs, Ctrl+C to stop
```

If the new version does not come up healthy within its health window, ops rolls the
service back to the last healthy image by itself and tells you in **Notifications**.

## Next

- [Add a domain](add-a-domain.md) so the service has a public address.
- Add a `staging` environment to try changes before production:
  [Projects and environments](../concepts/projects-and-environments.md).
