---
title: "Environments and GitOps"
description: "Connect a folder of a Git repository to a cluster as an environment; CI writes each image digest into it, the cluster's Frazer GitOps syncs it, and production moves by pull request."
url: https://frazer.ai/docs/gitops
index: https://frazer.ai/docs/llms.txt
tokens: 4391
---

> For agents: this page is indexed at https://frazer.ai/docs/llms.txt, and every page at https://frazer.ai/llms.txt. Copy commands from this file exactly.

# Environments and GitOps

An environment is a folder in a Git repository, connected to a [Kubernetes](https://frazer.ai/docs/kubernetes) cluster. Your CI writes each new image digest into the folder, the cluster’s own Frazer GitOps syncs it, and admission checks the digest’s signature. Staging moves on a commit, production on a pull request you approve. One cluster can run any number of environments from any number of repositories, each inside its own fence.

Product overview: [Environments and GitOps](https://frazer.ai/gitops).

## How it works

Keep code and deployment configuration in separate repositories. A **code repository** holds source, tests and the CI workflow; a commit there builds, signs and pushes an image to [Frazer Registry](https://frazer.ai/docs/registry). An **environment repository** holds what runs where: Helm values, image digests, databases and raw manifests, one folder per environment. A commit there is synced by the cluster.

```text
acme/storefront ──CI: test, build, sign──► Frazer Registry  api@sha256:…
     │
     └──CI commits the digest──► acme/platform-envs  envs/staging/apps/core/api/images.yaml
                                        │  webhook
                                        ▼  cluster acme-staging-1: GitOps syncs; admission checks the signature
```

The console follows each image by its digest through five stages: **Built**, **Written to Git**, **Synced**, **Rolled out**, **Verified**. Nobody presses “new app”: a new service is a new folder in the environment repository.

Frazer reads your repositories through the read-only **Frazer Reader** GitHub App, installed on the repositories you pick. Each cluster gets a 60-minute read token, refreshed every 20 minutes, that covers only the repositories connected to it.

-   **Environment**: a repository, a revision (branch, tag or commit), a path, a set of namespaces, a permission profile, the gateways and hostnames its routes may use, and a sync policy. An API resource on a cluster.
-   **Namespace ownership**: a namespace belongs to exactly one environment. The plan fails if two environments claim the same one.
-   **Fence**: each environment gets its own GitOps namespace `env-<name>` and AppProject `env-<name>`, which see only its repository and Frazer Registry, and only its namespaces.
-   **Permission profile**: what kinds of objects the environment may create: `standard`, `platform` or `preview`.
-   **Mode**: `managed` (Frazer reads the folder convention) or `repo-defined` (your own ApplicationSets, applied inside the fence).

### Lay out your repositories

| Layout | Good for |
| --- | --- |
| One repository, a folder per environment (`envs/staging`, `envs/production`) | Most teams, up to dozens of services. The default. |
| One repository per team (`payments-envs`, `data-platform-envs`) | Several teams with their own owners and approval rules. |
| One repository per environment (`envs-staging`, `envs-production`) | A hard production boundary, where only release managers merge to production. |

Environments are folders on one branch, not branches. Branches drift through merge order, cherry-picks and fixes that land in one branch only; a pull request between folders shows exactly what changes in production. You can still pin an environment’s revision to a tag or a commit, but Frazer never creates environment branches. A `deploy/` folder inside a code repository also works for a team with one service.

## Connect an environment

You need a cluster ([create one](https://frazer.ai/docs/quickstarts/kubernetes-cluster)) and an environment repository on GitHub.

1.  Install the **Frazer Reader** GitHub App on the environment repository. In the console, open your organization’s **Settings › Integrations**, choose **GitHub** and select the repositories Frazer may read. It asks for _Contents: read_ and _Metadata: read_, nothing else.
    
2.  Lay out the environment’s folder:
    
    ```text
    envs/production/                    # the path you connect
    ├── environment.yaml                # optional: labels, namespace defaults
    ├── _shared-values.yaml             # environment-wide Helm values
    ├── apps/<family>/<app>/
    │   ├── values.yaml                 # written by people
    │   └── images.yaml                 # written by CI only
    ├── databases/<name>/database.yaml  # Postgres, with backups
    └── manifests/<name>/               # raw YAML or a Kustomization: operators, tools
    ```
    
3.  Connect the path to the cluster. Frazer computes a plan from the repository’s current contents.
    
    ### CLI
    
    ```sh
    frazer releases environments connect data --project acme-production --cluster acme-production-1 \
      --repo acme/data-platform-envs --path envs/production \
      --namespaces data-airflow,data-clickhouse --profile platform --dry-run
    ```
    
    ```text title="Output"
    Plan: connect environment "data" to cluster acme-production-1 (acme-production)
      + Namespaces      env-data, data-airflow, data-clickhouse
      + AppProject      env-data   source acme/data-platform-envs + Frazer Registry · profile platform (elevated)
                                   cluster-scoped allowed: CRDs clickhouse.altinity.com
      + Applications    4 from commit 3f9c2e1: clickhouse-operator, clickhouse, airflow, superset
      ~ Git token       acme-production-1 also reads acme/data-platform-envs
      ✓ Namespaces      no conflict (environment "platform" owns checkout-api, web)
      ✓ Policy          Policy dry run: 0 violations · schemas valid
      Cost (expected)   +6 vCPU, +24 GiB requested on acme-production-1; no fee (own servers)
      Approval          org admin (profile platform)
    ```
    
    Read the plan, then run the command again without `--dry-run` to connect.
    
    ### Console
    
    Open the cluster, go to **Environments** and choose **Connect**. Pick the repository from the Frazer Reader installation (or extend the installation), then the branch, path, namespaces, permission profile, sync policy and hostnames. Review the plan and apply it.
    
4.  If the plan says **Approval: org admin**, an organization admin approves it. The `platform` profile is elevated; `standard` needs no approval.
    
5.  Watch the first sync under **Engineering › Environments**. Each app folder becomes a GitOps Application in the environment’s AppProject.
    

## Folders and values

What Frazer reads in `managed` mode:

-   **A family is a chart.** Each `apps/<family>/<app>/` folder becomes one Application from the family’s chart. Frazer ships a general `app` chart, and you can push your own chart families to Frazer Registry.
-   **Values are layered**, lowest first: the chart’s defaults, then `_shared-values.yaml`, then the app’s `values.yaml`, then `images.yaml`. Each app is an Application with two sources: the chart from Frazer Registry and your repository as `$values`.
-   **`images.yaml` is CI’s.** People edit `values.yaml`; CI writes only image digests. (Writing the digest into `values.yaml` also works.)
-   **Databases and manifests.** `databases/<name>/database.yaml` creates a Postgres database with backups through Frazer’s database chart. `manifests/<name>/` holds raw YAML or a Kustomization for operators and extra tools.

In `repo-defined` mode, the repository’s own ApplicationSets are applied into `env-<name>` instead, and any Application that names another GitOps project is refused.

## Roll out a new image from CI

Your CI builds once, pushes to Frazer Registry with a short-lived token ([how](https://frazer.ai/docs/registry#push-from-github-actions-without-a-password)), and Frazer signs the digest on push. Then CI writes the digest into the environment repository: to staging by commit, to production by pull request.

```yaml title="envs/staging/apps/microservice-grpc/billing/images.yaml"
# written by CI only; production gets the same digest by pull request
app:
  image:
    repository: registry.frazer.ai/acme-staging/build-images/billing
    tag: v0.1.46
    digest: sha256:c3c9…
```

Frazer GitOps syncs on the repository’s webhook, and admission verifies the signature before a pod starts. The rollout appears under **Engineering › Rollouts**, joined to the commit, the digest and whoever wrote it. Syncs and rollouts also reach your [webhooks](https://frazer.ai/docs/webhooks), for example `frazer.rollout.succeeded.v1`; the full list is under [Reference](#reference).

## Promote to production

Promotion is a pull request that copies a digest, or a values change, from `envs/staging` to `envs/production`. The diff is exactly what changes in production.

**Engineering › Rollouts** shows staging and production side by side for each service. When staging is ahead, **Promote** opens that pull request for you. Opening pull requests needs the opt-in **Frazer** writer GitHub App; without it, open the pull request yourself.

To roll back, revert the commit or the pull request. Git moves forward, Frazer GitOps follows, and the rollback is in the audit log like any other change.

## Permission profiles

| Profile | What the environment may create | Who chooses it |
| --- | --- | --- |
| `standard` (default) | Namespaced kinds in its own namespaces. No cluster-scoped kinds, no Gateways, no network-plugin, gateway-controller or GitOps kinds. | Developer |
| `platform` | Also an allowlist of cluster-scoped kinds: CRDs, ClusterRoles bound to its namespaces, its operators’ webhooks. Components Frazer guarantees stay blocked. | Organization admin; the plan marks it elevated |
| `preview` | What a preview policy runs: one namespace per pull request, deleted on merge, close or TTL. See [Templates and previews](https://frazer.ai/docs/previews). | Developer |

## Check pull requests before they merge

On every pull request that touches a connected path, the **Frazer Reviewer** GitHub App posts one check per environment, such as `Frazer / acme-production / data`. Frazer asks the target cluster’s own Frazer GitOps to render the pull request’s head commit and compares it with the live state, so the check uses the same chart versions and plugins as the real sync. The check shows:

-   the objects added, changed and deleted, per Application, with the full diff in the details;
-   anything the environment may not do: a Gateway, another environment’s namespace, a cluster-scoped kind under `standard`;
-   a `Secret` with data in it;
-   Frazer Policy dry-run results;
-   whether each new image digest is signed;
-   the change in requested resources and cost.

Make the check required in your repository’s branch rules to stop a change that fails it from merging. A changed source policy runs in evaluate mode first: for a set time it lists the deploys it would have refused, then it enforces.

## Keep secrets out of Git

Git holds `ExternalSecret` objects only. The values live in [Frazer Secrets](https://frazer.ai/docs/secrets) and reach the cluster through its own identity, with the `frazer-secrets` ClusterSecretStore:

```yaml title="envs/production/manifests/checkout-api-secrets/stripe.yaml"
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: stripe
  namespace: checkout-api
spec:
  secretStoreRef:
    kind: ClusterSecretStore
    name: frazer-secrets
  target:
    name: stripe
  data:
    - secretKey: STRIPE_SECRET_KEY
      remoteRef:
        key: payments/stripe-secret-key
```

The pull request check and an admission policy both reject a `Secret` with `data` or `stringData`; empty placeholders on an allowlist pass. Frazer does not keep encrypted secrets in Git: encrypted values would stay in Git history forever, and every cluster would need one more key. Git, registry and image pull tokens are minted per cluster and never stored in Git.

## Sync defaults

-   Automated sync with prune and self-heal, Server-Side Apply and server-side diff.
-   `FailOnSharedResource`, so no two Applications own the same object.
-   Namespaces, PersistentVolumeClaims, CRDs and databases are never pruned or deleted without a confirm (`Prune=confirm`, `Delete=false`).
-   Replicas owned by an autoscaler are ignored in the diff.
-   Charts and remote bases are pinned, never `latest`.
-   Sync waves: `manifests/` first, then `databases/`, then apps.
-   Production can add a **sync window** that refuses automated syncs outside the maintenance window; a manual sync still works.

> **Note:** Frazer never changes image tags in a cluster on its own: digests move only through commits to the environment repository, so every rollout is in Git.

## Access and audit

-   **Developer**: connects an environment with the `standard` or `preview` profile.
-   **Organization admin**: approves the `platform` profile.
-   **CI**: writes image digests into `images.yaml`, nothing else.
-   **Frazer**: reads the selected repositories through the read-only **Frazer Reader** App. Writing pull requests (promote and restore) takes the separate **Frazer** App; agent branches and pull requests take the **Frazer Agents** App; checks and review comments come from **Frazer Reviewer**. You install each one only if you want it.

Frazer logs connecting, changing and disconnecting an environment, each as a plan, and every rollout with its digest, commit and author. Git itself records every change to what runs.

## Limits and quotas

| Setting | Default |
| --- | --- |
| Environments per cluster | Any number, from any number of repositories |
| Owners per namespace | Exactly one environment |
| Cluster Git read token | 60 minutes, refreshed every 20 minutes, limited to connected repositories |
| Prune of namespaces, volume claims, CRDs, databases | Waits for a confirm |
| Sync window | Off; production can add one |

The plan shows the vCPU and memory an environment adds to the cluster, and its quota impact, before you apply.

## Pricing

Environments have no fee of their own: every cluster comes with Frazer GitOps, gateways, DNS and certificates. On servers you bring, what your environments run is part of the per-core server fee.

| Kubernetes | On your servers | On the Frazer fleet |
| --- | --- | --- |
| Price | $0 per cluster | $0 per cluster |

Every line, with the estimator: [pricing](https://frazer.ai/pricing#lines).

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| The connect plan fails with a namespace conflict | Another environment already owns one of the namespaces. | Pick other namespaces, or move the objects and disconnect the other environment first. |
| The repository is not in the connect list | The Frazer Reader App is not installed on it. | Extend the installation to the repository under **Settings › Integrations**. |
| The pull request check flags a Gateway or a cluster-scoped kind | The environment’s profile is `standard`. | Remove the object, or have an organization admin approve the `platform` profile. |
| An Application is refused in `repo-defined` mode | It names a GitOps project other than the environment’s. | Set its `project` to the environment’s own AppProject, `env-<name>`. |
| A pod is refused at admission | The image digest is not signed by an accepted signer, or the deploy policy blocks a fixable Critical. | Check the digest with `frazer registry images describe`. See [Registry](https://frazer.ai/docs/registry#scanning-and-signing). |
| A sync does not start in production | A sync window refuses automated syncs outside maintenance. | Wait for the window, or start a manual sync. |
| A deletion is pending | Namespaces, volume claims, CRDs and databases wait for a confirm. | Confirm it in the console if the deletion is intended. |
| A `Secret` in the repository is rejected | Secret values are not allowed in Git. | Store the value in Frazer Secrets and commit an `ExternalSecret`. |

## Reference

### CLI

| Command | What it does |
| --- | --- |
| `frazer releases environments connect` | Connect a repository path to a cluster as an environment, with its namespaces, permission profile and sync policy, as a plan. |

All of them: [CLI reference](https://frazer.ai/docs/cli).

### Events

| Event type | When it is sent |
| --- | --- |
| `frazer.environment.synced.v1` | An environment finished a sync. |
| `frazer.environment.sync_failed.v1` | An environment's sync failed. |
| `ai.frazer.releases.rollout.v1.started` | A rollout started in an environment. |
| `ai.frazer.releases.rollout.v1.succeeded` | A rollout finished successfully. |
| `ai.frazer.releases.rollout.v1.failed` | A rollout failed. |
| `ai.frazer.releases.rollout.v1.rolledback` | A rollout was rolled back. |

All of them: [Events reference](https://frazer.ai/docs/webhooks).

## Related

-   [Connect your environment repo](https://frazer.ai/docs/quickstarts/environment-repo): the quickstart.
-   [Kubernetes](https://frazer.ai/docs/kubernetes): the clusters environments run on.
-   [Registry](https://frazer.ai/docs/registry): push and sign the images environments run.
-   [Templates and previews](https://frazer.ai/docs/previews): a preview environment per pull request.
-   [Reviewer](https://frazer.ai/docs/reviewer): the pull request checks.
-   [Environments and GitOps product page](https://frazer.ai/gitops)

## Pages in Build and ship

- [Overview](https://frazer.ai/docs/source.md)
- [GitHub runners](https://frazer.ai/docs/runners/github.md)
- [Cache](https://frazer.ai/docs/cache.md)
- [Registry](https://frazer.ai/docs/registry.md)
- [Environments and GitOps](https://frazer.ai/docs/gitops.md) (this page)
- [Overview](https://frazer.ai/docs/previews.md)
