# Projects and environments

> How ZopDay organises deployments into projects and environments; the catalog hierarchy that drives everything else.

Source: https://zop.dev/docs/zopday/concepts/projects-environments

---

ZopDay's deployment catalog is a three-level hierarchy: **Project → Environment → Deployment**. Get this right at the start and the rest of the product slots in around it cleanly. Get it wrong and you'll be reshuffling for weeks.

![ZopDay Projects list with payment-service, user-dashboard, and notification-engine cards, each showing its environment count and health status](https://storage.googleapis.com/zopdev-blog-resources/1/files/originals/20260707/229f09f8-2604-472d-9582-5ee8070c7151-projects.png)

*The Projects list; each project with its environments and health, the top of the Project → Environment → Deployment hierarchy.*

## The hierarchy

```
Organisation
  └── Project              (org-scoped grouping; usually a service, team, or app)
        └── Environment    (binds to a space + namespace + registry)
              └── Deployment   (a git repo + branch + config)
                    └── Revision   (one rollout; built image + Helm release)
```

| Level | Owned by | Stored as | What it represents |
|---|---|---|---|
| Project | Org | Catalog | An org-scoped grouping. Org name + project name is unique. |
| Environment | Project | Catalog | The "where"; a space + namespace + (optional) registry integration. |
| Deployment | Environment | Catalog | The "what"; repo URL, branch, config. Infra fields are inherited from the parent environment. |
| Revision | Deployment | Runtime | One rollout. Carries the built image and its rollout state. |

The split between catalog and runtime data is deliberate; see the [deployments concept page](https://zop.dev/docs/zopday/concepts/deployments) for why.

## Projects

A project is the top-level grouping. Org-scoped, soft-deletable, no nesting.

### When to create a project

- One service that has its own repo, owners, and release cadence → one project
- A monorepo with multiple deployable services → still one project; multiple deployments inside it
- A platform team's shared infrastructure (gateways, ingress controllers, observability stack) → its own project, separate from product projects

### Project fields

| Field | Required | Notes |
|---|---|---|
| `name` | yes | Org-unique. Used in URLs and as a label. Lowercase kebab-case recommended. |
| `description` | no | Free-form. Shows on the project list. |
| `canvasPosition` | no | `{x, y}` finite numbers. Drives layout on the architecture canvas. |

### Project endpoints

```
POST   /projects              Create
GET    /projects              List with filters
GET    /projects/{id}         Get one
PATCH  /projects/{id}         Partial update
DELETE /projects/{id}         Soft-delete
GET    /projects/{id}/stats   Per-project deployment / revision counts
```

**Note**

These are relative paths. See the API reference for the base URL, the `Authorization: Bearer` header, and full request/response examples.

## Environments

An environment binds a project to a runnable target. Every deployment lives inside exactly one environment, and the environment provides the infrastructure context.

### Environment fields

| Field | Required | Notes |
|---|---|---|
| `name` | yes | Project-unique. Common patterns: `prod`, `staging`, `qa`, `dev`, or `preview-{slug}`. |
| `space_id` | yes | The registered Kubernetes cluster. See [Cluster registration](https://zop.dev/docs/zopday/cloud-setup/cluster-registration). |
| `namespace` | yes | The Kubernetes namespace inside the space. ZopDay creates it if it doesn't exist (with the right RBAC). |
| `registry_integration_id` | no | Where built images are pushed. If unset, ZopDay uses the cloud-default registry (ECR / Artifact Registry / ACR) on the same cloud account as the space. |

### Why bind the namespace at the environment level

Because **deploys cross-reference each other by environment**. If two deployments live in `payments-prod` (same environment), they can wire up via [service connections](https://zop.dev/docs/zopday/concepts/service-connections) without specifying a namespace explicitly; the platform knows where to look.

Multiple environments **can** share a namespace if you really need to (e.g. a staging environment that lives in the same namespace as another project's staging) but it's discouraged; you give up isolation guarantees on resource limits, RBAC, and tear-down.

### Environment endpoints

```
POST   /projects/{projectID}/environments       Create
GET    /projects/{projectID}/environments       List
GET    /environments/{envID}                    Flat get (no project context needed)
PATCH  /projects/{projectID}/environments/{id}  Partial update
DELETE /projects/{projectID}/environments/{id}  Delete

GET    /environments/{envID}/deployments        List deployments in this env
POST   /environments/{envID}/deployments        Create deployment in this env
GET    /environments/{envID}/stats              Per-env deployment / revision counts
```

Namespace discovery on the cluster behind an environment is served at `GET /space-discovery/{spaceID}/namespaces` (it needs a kubeconfig); see [Deployment spaces](https://zop.dev/docs/zopday/concepts/deployment-spaces) and the API reference for request/response details.

## Common patterns

### One project per service, multiple environments

The most common shape. Each service is a project; each project has `dev`, `staging`, `prod` environments, each bound to a different cluster (or the same cluster, different namespaces).

```
payments (project)
  ├── dev      → space: shared-dev-cluster, namespace: payments-dev
  ├── staging  → space: staging-cluster,    namespace: payments-staging
  └── prod     → space: prod-cluster,       namespace: payments-prod
```

### One project, many ephemeral environments (preview branches)

Each PR gets its own short-lived environment. Common pattern for review apps.

```
payments (project)
  ├── prod                → space: prod, namespace: payments-prod
  ├── staging             → space: staging, namespace: payments-staging
  ├── preview-pr-1234     → space: review, namespace: pr-1234       (auto-created on PR open)
  ├── preview-pr-1245     → space: review, namespace: pr-1245
  └── ...
```

The preview environments are torn down by automation on PR close; call `DELETE /environments/{id}` from your CI on the close webhook. Cascading deletion removes the deployment rows and any pending revisions.

### Multi-tenant platform team

A platform team running shared services (ingress controllers, observability) keeps them in their own project, bound to a different namespace per cluster.

```
platform (project)
  ├── prod   → space: prod, namespace: platform-system
  └── dev    → space: dev,  namespace: platform-system
```

## What changes when you change an environment

Two infra fields on the environment can change in place:

| Change | What happens |
|---|---|
| `namespace` | Existing deployments **do not migrate**. ZopDay flags a divergence; you can re-deploy each one to land it in the new namespace. |
| `registry_integration_id` | New builds push to the new registry. Existing revisions still reference their original image hashes. Re-deploy to pin everything to the new registry. |
| `space_id` | Same as namespace; flagged but not auto-migrated. Use carefully. |

A `space_id` change is usually a "we're moving from cluster A to cluster B" event. Coordinate with whoever owns DNS and load balancing on top.

## Org cascade

Deleting an organisation cascades, which:

- Soft-deletes every project owned by the org
- Soft-deletes every environment inside those projects
- Soft-deletes every deployment + revision inside those environments
- Wipes org settings (including default dashboards, default cluster, etc.)

Spaces are **not** deregistered on org deletion; the underlying cluster is yours, not ZopDay's. If you also want to remove the cluster-side artefacts, run a teardown per deployment before the org cascade.
