> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plane

> Deploy Plane Community Edition, the open-source project tracker, on Control Plane. One location with bundled PostgreSQL, Redis, RabbitMQ and object storage, private first. Needs an existing one-location GVC and five prerequisite secrets.

## Overview

Plane is an open-source project tracker, a Jira and Linear alternative: work items, cycles, modules, roadmaps and wiki pages with a real-time collaborative editor. This template deploys Plane Community Edition with its PostgreSQL, Redis, RabbitMQ and an object store for attachments (bundled, or your own AWS S3 or S3-compatible bucket). Plane runs in exactly one location of your GVC and installs **private**: you claim the instance in God Mode over a port-forward, then publish.

> [!NOTE]
>
> This template deploys into an existing [GVC](https://docs.controlplane.com/reference/gvc.md) that you already have — it does not create or manage a GVC. The `location` you set must already be one of that GVC's locations; if it is not, the Plane workloads start nothing.

### What Gets Created

| Resource | Name | Purpose | Optional |
| - | - | - | - |
| Workload | `RELEASE_NAME-plane` | The HTTP tier, six containers (standard, `plane.replicas` replicas): `proxy` (Caddy, the only exposed port), `web`, `space`, `admin` (God Mode), `live` (collaborative editor) and `api` | — |
| Workload | `RELEASE_NAME-plane-worker` | Celery workers for notifications, activity, imports, exports and invites | — |
| Workload | `RELEASE_NAME-plane-beat` | Celery scheduler, always one replica. Also applies the database migrations on every start | — |
| Workload | `RELEASE_NAME-plane-redis` | Valkey, the cache and the live editor's channel (stateful) | — |
| Workload | `RELEASE_NAME-plane-mq` | RabbitMQ, the Celery broker (stateful) | — |
| Workload | `RELEASE_NAME-plane-minio` | Single-node object store for attachments (stateful) | `storage.type: minio` (default) |
| Workload | `RELEASE_NAME-postgres` | Bundled PostgreSQL 16 from the [postgres](https://docs.controlplane.com/template-catalog/templates/postgres.md) template (stateful, 1 replica) | — |
| Volume set | `RELEASE_NAME-pg-vs` | PostgreSQL data directory | — |
| Volume set | `RELEASE_NAME-plane-redis-vs` | Redis data | — |
| Volume set | `RELEASE_NAME-plane-mq-vs` | RabbitMQ data | — |
| Volume set | `RELEASE_NAME-plane-minio-vs` | Object store data | `storage.type: minio` (default) |
| Identity | `RELEASE_NAME-plane-identity` | Bound to every Plane workload and datastore. With `storage.type: s3` and no `storage.s3.endpoint` it carries the AWS cloud account binding and your bucket policy | — |
| Policy | `RELEASE_NAME-plane-policy` | Grants that identity `reveal` on exactly the secrets Plane reads | — |
| Policy | `RELEASE_NAME-plane-gvc-policy` | Grants that identity `view` on the install GVC so the scheduler can log a warning when the GVC has other locations | — |
| Identity | `RELEASE_NAME-pg-identity` | Bound to the bundled PostgreSQL | — |
| Policy | `RELEASE_NAME-pg-policy` | Grants the PostgreSQL identity `reveal` on its credentials secret only | — |
| Secret | `RELEASE_NAME-plane-caddyfile`, `RELEASE_NAME-plane-admin-nginx`, `RELEASE_NAME-plane-mq-conf` | Proxy, God Mode and RabbitMQ configuration files (contain no credentials) | — |

Your application keys, database, Redis, RabbitMQ and object store credentials are **not** created by the template — see [Prerequisites](#prerequisites).

## Prerequisites

A GVC with **one location**, and these [dictionary secrets](https://docs.controlplane.com/guides/create-secret/dictionary.md) created **before** you install. The database, Redis and RabbitMQ values go into connection URLs, so use letters, digits, `-`, `_` and `.` only. Secret names are org-wide, so give each release its own.

**1. Create the application keys secret**

```bash theme={null}
cpln secret create-dictionary --name APP_KEYS_SECRET_NAME \
  --entry SECRET_KEY="$(openssl rand -hex 32)" \
  --entry LIVE_SERVER_SECRET_KEY="$(openssl rand -hex 32)"
```

Set `secrets.name` to this name. `SECRET_KEY` signs sessions and encrypts the instance settings Plane stores (SMTP password, OAuth secrets), so back it up outside Control Plane and never replace it: replacing it signs everyone out and makes those settings unreadable.

**2. Create the database credentials secret**

```bash theme={null}
cpln secret create-dictionary --name DB_CREDENTIALS_SECRET_NAME \
  --entry username=plane \
  --entry password="$(openssl rand -hex 24)" \
  --entry database=plane
```

Set `postgres.config.credentialsSecretName` to this name.

**3. Create the Redis password secret**

```bash theme={null}
cpln secret create-dictionary --name REDIS_SECRET_NAME \
  --entry password="$(openssl rand -hex 24)"
```

Set `redis.credentialsSecretName` to this name.

**4. Create the RabbitMQ login secret**

```bash theme={null}
cpln secret create-dictionary --name MQ_SECRET_NAME \
  --entry username=plane \
  --entry password="$(openssl rand -hex 24)"
```

Set `rabbitmq.credentialsSecretName` to this name. RabbitMQ reads it on its first boot only.

**5. Create the object store keys secret**

Only with the default `storage.type: minio`. `accessKey` needs 3 or more characters and `secretKey` 8 or more:

```bash theme={null}
cpln secret create-dictionary --name MINIO_SECRET_NAME \
  --entry accessKey=plane \
  --entry secretKey="$(openssl rand -hex 24)"
```

Set `storage.minio.credentialsSecretName` to this name. For `storage.type: s3` see [Storage Setup](#storage-setup) instead.

> [!WARNING]
>
> Create every secret **before** installing. Installing without one reports success, but the deployment never becomes ready and `cpln logs` stays empty; `status.versions[].message` from the command below names the missing secret:
>
> ```bash theme={null}
> cpln workload get-deployments RELEASE_NAME-plane --gvc GVC_NAME -o yaml
> ```
>
> A secret created after install is not picked up on its own: see [Rotating a Secret](#rotating-a-secret).

## Installation

Once the secrets exist, install from the marketplace registry, pointing each key at its secret and `location` at a location of your GVC:

```bash theme={null}
cpln helm install RELEASE_NAME oci://ghcr.io/controlplane-com/templates/plane \
  --version 1.0.0 --gvc GVC_NAME \
  --set location=LOCATION_NAME \
  --set secrets.name=APP_KEYS_SECRET_NAME \
  --set postgres.config.credentialsSecretName=DB_CREDENTIALS_SECRET_NAME \
  --set redis.credentialsSecretName=REDIS_SECRET_NAME \
  --set rabbitmq.credentialsSecretName=MQ_SECRET_NAME \
  --set storage.minio.credentialsSecretName=MINIO_SECRET_NAME
```

The first install applies the database migrations before the API turns ready, so allow several minutes. Other ways to install and manage the template:

> [card] [UI](https://docs.controlplane.com/template-catalog/install-manage/ui.md)
>
> Browse, install, and manage templates visually

> [card] [CLI](https://docs.controlplane.com/template-catalog/install-manage/cli.md)
>
> Manage templates from your terminal

> [card] [Terraform](https://docs.controlplane.com/template-catalog/install-manage/terraform.md)
>
> Declare templates in your Terraform configurations

> [card] [Pulumi](https://docs.controlplane.com/template-catalog/install-manage/pulumi.md)
>
> Declare templates in your Pulumi programs

## Configuration

### Location and Plane

```yaml theme={null}
location: aws-us-east-1   # the ONE location of your GVC that Plane runs in; it must already be a GVC location

plane:
  # ONE tag for all six Plane images (proxy, frontend, space, admin, live, backend);
  # they are released together and never mixed.
  imageTag: v1.4.3
  replicas: 1             # the whole HTTP tier; 2+ keeps Plane up through a rolling restart
  appUrl: ""              # empty = the canonical endpoint; a custom domain WITH its scheme, e.g. https://plane.example.com
  fileSizeLimit: 5242880  # max upload in bytes (5 MiB), enforced by the proxy and the object store
  api:
    gunicornWorkers: 1    # gunicorn worker processes; each needs ~1 more CPU and ~300Mi
  resources:
    proxy:                # Caddy, the single public entry point
      minCpu: 50m
      maxCpu: 200m
      minMemory: 64Mi
      maxMemory: 256Mi
    ui:                   # applied to EACH of web, space and admin
      minCpu: 50m
      maxCpu: 250m
      minMemory: 96Mi
      maxMemory: 384Mi
    live:                 # collaborative-editing server
      minCpu: 100m
      maxCpu: 500m
      minMemory: 192Mi
      maxMemory: 512Mi
    api:                  # Django + gunicorn
      minCpu: 250m
      maxCpu: 1000m
      minMemory: 512Mi
      maxMemory: 1536Mi

secrets:
  name: my-plane-secrets

worker:
  replicas: 1             # Celery workers: notifications, imports, exports, invites
  concurrency: 4          # prefork children PER replica
  resources:
    minCpu: 200m
    maxCpu: 1000m
    minMemory: 384Mi
    maxMemory: 2Gi        # sized for `concurrency` children plus the parent; raise it with concurrency

beat:
  resources:
    minCpu: 50m
    maxCpu: 1000m
    minMemory: 192Mi
    maxMemory: 512Mi
```

The scheduler is always one replica and runs the database migrations, so `beat.resources.maxCpu` sets how fast they apply. Changing `plane.appUrl` changes the address every sign-in provider redirects to; see [Authentication](#authentication).

### Storage

```yaml theme={null}
storage:
  type: minio             # minio (bundled) | s3 (your bucket)
  minio:                  # bundled single-node object store (Silo, the maintained MinIO fork)
    image: pgsty/silo:RELEASE.2026-10-18T00-00-00Z
    credentialsSecretName: my-plane-minio-credentials
    bucket: uploads
    resources:
      cpu: 500m
      memory: 512Mi
    volumeset:
      capacity: 10        # GiB (minimum 10)
  s3:                     # external S3 or S3-compatible bucket (storage.type: s3)
    bucket: my-plane-bucket
    region: us-east-1
    endpoint: ""          # S3-compatible servers only; users' browsers must reach it, e.g. https://s3.example.com
    cloudAccountName: my-s3-cloud-account   # AWS keyless: a Control Plane cloud account
    policyName: my-plane-s3-policy          # AWS keyless: your bucket-scoped IAM policy (see Storage Setup)
    auth:
      secretName: ""      # S3-compatible only: dictionary with AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY, e.g. my-plane-s3-keys
```

### Datastores

```yaml theme={null}
postgres:
  image: postgres:16.15
  config:
    credentialsSecretName: my-plane-db-credentials
  resources:
    minCpu: 250m
    maxCpu: 1000m
    minMemory: 512Mi
    maxMemory: 1Gi
  volumeset:
    capacity: 10          # GiB (minimum 10)

redis:
  image: valkey/valkey:8.1.11
  credentialsSecretName: my-plane-redis-credentials
  resources:
    cpu: 250m
    memory: 512Mi
  volumeset:
    capacity: 10          # GiB (minimum 10)

rabbitmq:
  image: rabbitmq:4.3.7-management-alpine
  credentialsSecretName: my-plane-mq-credentials
  vhost: plane
  resources:
    cpu: 500m
    memory: 768Mi
  volumeset:
    capacity: 10          # GiB (minimum 10)
```

Plane runs on PostgreSQL 15 and 16, so the bundled database stays on 16 and no highly available database is offered.

### Access

```yaml theme={null}
publicAccess:
  enabled: false          # the first visitor to /god-mode claims the instance: claim it over port-forward, then set true
internalAccess:
  type: same-gvc          # same-gvc | same-org | workload-list
  workloads: []           # workload-list only; this release's own workloads are always added
  # workloads:
  #   - //gvc/GVC_NAME/workload/WORKLOAD_NAME
```

The bundled PostgreSQL has its own `postgres.internalAccess`. With `workload-list` there, list `//gvc/GVC_NAME/workload/RELEASE_NAME-plane`, `RELEASE_NAME-plane-worker` and `RELEASE_NAME-plane-beat` in the same form.

### Storage Setup

Only for `storage.type: s3`. Browsers upload to and download from the bucket directly, so it needs a CORS rule for your Plane origin (replace `PLANE_URL` with the instance URL, scheme included):

```json theme={null}
[{ "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
   "AllowedOrigins": ["PLANE_URL"], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3000 }]
```

**AWS S3 (keyless).** Create the bucket and its CORS rule, create a Control Plane [cloud account](https://docs.controlplane.com/guides/create-cloud-account.md) for AWS, and create this IAM policy (replace `BUCKET_NAME`). Set `storage.s3.bucket`, `region`, `cloudAccountName` and `policyName` (the IAM policy's name); leave `auth.secretName` empty.

```json theme={null}
{ "Version": "2012-10-17", "Statement": [
  { "Sid": "PlaneBucketObjects", "Effect": "Allow", "Resource": "arn:aws:s3:::BUCKET_NAME/*",
    "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"] },
  { "Sid": "PlaneBucketList", "Effect": "Allow", "Resource": "arn:aws:s3:::BUCKET_NAME",
    "Action": ["s3:ListBucket", "s3:GetBucketLocation", "s3:ListBucketMultipartUploads"] } ] }
```

**S3-compatible servers** (MinIO, SeaweedFS, Google Cloud Storage [interoperability](https://cloud.google.com/storage/docs/interoperability) with an HMAC key). Create the bucket and its CORS rule, store the keys, and set `storage.s3.bucket`, `storage.s3.endpoint` and `storage.s3.auth.secretName` to the secret:

```bash theme={null}
cpln secret create-dictionary --name S3_CREDENTIALS_SECRET_NAME \
  --entry AWS_ACCESS_KEY_ID=ACCESS_KEY \
  --entry AWS_SECRET_ACCESS_KEY=SECRET_KEY
```

Plane gives browsers presigned URLs built from `endpoint`, so it must be a full URL that users' browsers can reach, such as a public HTTPS endpoint. An internal `*.cpln.local` address serves the API but not browser uploads and downloads.

## Connecting

Plane has no bootstrap credential: whoever completes `/god-mode` first becomes the instance admin, so the install starts private.

| What | Address | Notes |
| - | - | - |
| Private access (default) | `localhost:8080` after `cpln port-forward RELEASE_NAME-plane 8080:80 --gvc GVC_NAME` | God Mode at `/god-mode/` |
| Plane UI | `status.canonicalEndpoint` from `cpln workload get RELEASE_NAME-plane --gvc GVC_NAME -o yaml` | Needs `publicAccess.enabled: true` |
| God Mode | `PLANE_URL/god-mode/` | Instance administration |
| Published views | `PLANE_URL/spaces/` | Read-only public views |
| REST API | `PLANE_URL/api/v1/` | With a personal API token from the UI's settings |
| Internal | `RELEASE_NAME-plane.GVC_NAME.cpln.local:80` | From other workloads in the GVC |
| PostgreSQL | `RELEASE_NAME-postgres.GVC_NAME.cpln.local:5432` | Values from the database credentials secret (`cpln secret reveal DB_CREDENTIALS_SECRET_NAME -o yaml`) |

### First Run

1. Install with `publicAccess.enabled: false` (the default) and wait for `RELEASE_NAME-plane` to become ready.
2. Run `cpln port-forward RELEASE_NAME-plane 8080:80 --gvc GVC_NAME`, open `localhost:8080/god-mode/` in a browser and create the instance admin. Then open `localhost:8080` to create your workspace.
3. Set up email and sign-in methods in God Mode (below) while the instance is still private.
4. Publish with `publicAccess.enabled: true`; the public firewall can take a few minutes to open:

```bash theme={null}
cpln helm upgrade RELEASE_NAME oci://ghcr.io/controlplane-com/templates/plane \
  --version 1.0.0 --gvc GVC_NAME \
  --set location=LOCATION_NAME \
  --set secrets.name=APP_KEYS_SECRET_NAME \
  --set postgres.config.credentialsSecretName=DB_CREDENTIALS_SECRET_NAME \
  --set redis.credentialsSecretName=REDIS_SECRET_NAME \
  --set rabbitmq.credentialsSecretName=MQ_SECRET_NAME \
  --set storage.minio.credentialsSecretName=MINIO_SECRET_NAME \
  --set publicAccess.enabled=true
```

### God Mode

God Mode (`PLANE_URL/god-mode/`) holds the instance settings. They are stored in the database, not in chart values, so they survive restarts and upgrades. Sign in with the admin you created in First Run.

#### Email

Under God Mode, Email, set the SMTP host, port, sender address and security. Email is required for invites, notifications and unique-code sign-in. A relay in the same GVC is reached at `SMTP_WORKLOAD_NAME.GVC_NAME.cpln.local`; set the security option to match what the relay speaks (none for a plain relay). The SMTP password is encrypted with `SECRET_KEY`.

#### Authentication

Under God Mode, Authentication:

* **Unique codes** emails a sign-in code (magic link). It needs Email to be set up first.
* **Password** sign-in with email and password.
* **Allow anyone to sign up** is on by default: an unknown email address registers itself. Turn it off to limit access to invited users.
* **GitHub, Google and GitLab** sign-in each need an OAuth app registered with the provider. God Mode shows the exact callback to register: `PLANE_URL/auth/github/callback/`, `PLANE_URL/auth/google/callback/` or `PLANE_URL/auth/gitlab/callback/`, plus the origin URL for Google. Enter the client ID and secret in God Mode. Client secrets are stored encrypted.

The callback is built from the address the instance is served on. If the URL changes, for example when you set `plane.appUrl` to a custom domain, register the new callback with each provider.

#### General

Under God Mode, General, set the instance name, manage instance admins and turn telemetry off. Plane sends usage telemetry to `telemetry.plane.so` until you do.

> [!NOTE]
>
> `SECRET_KEY` encrypts the settings stored here. Never replace it, or the saved SMTP password and OAuth secrets become unreadable.

### Webhooks and Exports

* **Webhooks** (workspace settings, Webhooks) must point at a public URL; internal and private addresses, including `*.cpln.local` workloads, are refused. Plane generates each webhook's signing secret and signs the request body in the `x-plane-signature` header (HMAC-SHA256, hex, keyed with that secret).
* **Exports** appear as download links under workspace settings, Exports. No email is sent.
* **Uploads** larger than `plane.fileSizeLimit` are refused with HTTP 413. The limit counts the whole upload request, so the largest usable file is a couple of kilobytes smaller.

## Operations

### Backing Up

The bundled PostgreSQL has no scheduled backups here, because Plane runs on PostgreSQL 15 and 16 and the template backups need PostgreSQL 17. Dump the database with `pg_dump` from the `postgresql` container, and back up the object store too: attachments live there, not in the database.

```bash theme={null}
cpln workload exec RELEASE_NAME-postgres --gvc GVC_NAME --container postgresql --quiet \
  -- sh -c 'pg_dump -U USERNAME -Fc DATABASE | base64' | base64 -d > dump.pgc
```

Use the `username` and `database` values from the database credentials secret. For `storage.type: s3`, back up the bucket with your provider's tools; for the bundled store, snapshot its volume set:

```bash theme={null}
cpln volumeset snapshot create RELEASE_NAME-plane-minio-vs --gvc GVC_NAME --snapshot-name SNAPSHOT_NAME --location LOCATION_NAME --volume-index 0
```

This template provides no restore procedure.

### Rotating a Secret

Secrets are read when a replica starts, and a paused deployment does not retry on its own. After creating a missing prerequisite secret late, changing one, or an upgrade that changes which secrets the release reads, run `cpln workload force-redeployment` with `--gvc GVC_NAME` on `RELEASE_NAME-plane`, `RELEASE_NAME-plane-worker`, `RELEASE_NAME-plane-beat` and the datastore whose secret it is (`RELEASE_NAME-postgres`, `RELEASE_NAME-plane-redis`, `RELEASE_NAME-plane-mq` or `RELEASE_NAME-plane-minio`):

```bash theme={null}
cpln workload force-redeployment WORKLOAD_NAME --gvc GVC_NAME
```

RabbitMQ creates its user on first boot only, and PostgreSQL its user on first initialization, so changing those credentials in the secret alone does not change the login inside the datastore. Never replace `SECRET_KEY`.

### Scaling and Availability

`plane.replicas` and `worker.replicas` scale freely; with two or more `plane.replicas` the UI stays up through a rolling restart. The scheduler is always one replica. PostgreSQL, Redis, RabbitMQ and the object store are single instances.

### Troubleshooting

> [accordion] Deployment never becomes ready and the logs are empty
>
> **Symptom:** `cpln helm install` reported success, `cpln logs` returns nothing, and `cpln workload get-deployments RELEASE_NAME-plane --gvc GVC_NAME -o yaml` names a secret under `status.versions[].message`.
>
> **Cause:** A prerequisite secret does not exist.
>
> **Fix:** Create it (see [Prerequisites](#prerequisites)), then force a redeployment as in [Rotating a Secret](#rotating-a-secret).

> [accordion] Every Plane workload reports it is deactivated
>
> **Symptom:** The deployment message reads `deactivated because maxScale is set to 0` and nothing is logged.
>
> **Cause:** `location` is not one of the GVC's locations.
>
> **Fix:** Set `location` to a location your GVC already has and run `cpln helm upgrade` with your install values.

> [accordion] The API stays unready for several minutes after the first install
>
> **Symptom:** `RELEASE_NAME-plane` is not ready and the `api` container logs `Waiting for database migrations to complete...`.
>
> **Cause:** `RELEASE_NAME-plane-beat` applies the migrations on its first start and the API waits for it.
>
> **Fix:** Wait for the scheduler to finish; follow its progress with the command below. If the message never clears, the scheduler is not running: check it and its database credentials.
>
> ```bash theme={null}
> cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-plane-beat"}' --limit 50 --since 10m
> ```

> [accordion] Password authentication failed for the database
>
> **Symptom:** `RELEASE_NAME-plane-beat` logs `password authentication failed` and the API and worker stay unready.
>
> **Cause:** The password in the credentials secret no longer matches the one PostgreSQL initialized with.
>
> **Fix:** Restore the original password in the secret and force a redeployment of `RELEASE_NAME-plane`, `RELEASE_NAME-plane-worker` and `RELEASE_NAME-plane-beat`.

> [accordion] Notifications, exports and invites never arrive
>
> **Symptom:** The UI reports success but nothing is delivered.
>
> **Cause:** The worker is not running or is out of memory.
>
> **Fix:** Check `RELEASE_NAME-plane-worker`, and raise `worker.resources.maxMemory` together with `worker.concurrency`.

> [accordion] Uploads fail with HTTP 413
>
> **Symptom:** An attachment upload fails with 413 and no message.
>
> **Cause:** The file exceeds `plane.fileSizeLimit`.
>
> **Fix:** Raise `plane.fileSizeLimit` (bytes) and run `cpln helm upgrade` with your install values.

> [accordion] Render refuses the install
>
> **Symptom:** `cpln helm install` fails with a message beginning `plane:`. The messages and their fixes:
>
> * ``plane: `location` is required``, ``plane: `location` must be a single location NAME`` and ``plane: `location` must be a bare location NAME like`` `aws-us-east-1`: `location` is empty, a list, or a link form such as `//location/aws-us-east-1`.
> * ``plane: `locations` (plural) is not a key of this chart``: use the singular `location`.
> * `plane: secrets.name is required`, `plane: redis.credentialsSecretName is required`, `plane: rabbitmq.credentialsSecretName is required`, `plane: postgres.config.credentialsSecretName is required` and `plane: storage.minio.credentialsSecretName is required with storage.type minio`: a prerequisite secret name is empty.
> * `plane: postgres.credentials is not a key of this chart`: database credentials are a secret, not values.
> * `plane: beat.replicas is not a key of this chart`: the scheduler is always one replica.
> * `plane: plane.replicas must be at least 1`, `plane: worker.replicas must be at least 1`, `plane: worker.concurrency must be at least 1` and `plane: plane.api.gunicornWorkers must be at least 1`: raise the value.
> * `plane: plane.appUrl must start with https:// or http://`: add the scheme.
> * `plane: storage.type must be 'minio' or 's3'`, `plane: storage.s3.bucket is required when storage.type is s3`, `plane: s3 storage needs credentials` and `plane: static keys (storage.s3.auth.secretName) are only for S3-compatible servers`: AWS S3 uses `cloudAccountName` and `policyName`; an S3-compatible server uses `endpoint` and `auth.secretName`.
> * ``plane: internalAccess.type `none` would cut the API, worker and scheduler off`` and `plane: postgres.internalAccess.type must not be 'none'`: use `same-gvc` or `workload-list`.
> * ``plane: internalAccess.workloads may only be set when internalAccess.type is `workload-list` ``: remove the list or switch the type.
> * `plane: postgres.internalAccess.type is 'workload-list' but the list does not include`: add `RELEASE_NAME-plane`, `RELEASE_NAME-plane-worker` and `RELEASE_NAME-plane-beat`.
>
> **Cause:** A value is missing or invalid.
>
> **Fix:** Correct the value the message names and reinstall.

## Important Notes

* **Install into a GVC with one location** and set `location` to it; the bundled PostgreSQL is not pinned to `location` and starts a copy in every other location of the GVC.
* **Claim `/god-mode` privately first**: the first visitor becomes the instance admin.
* **Never replace `SECRET_KEY`**: it encrypts the stored SMTP and OAuth settings.
* **Re-register OAuth callbacks** whenever the instance URL changes.
* **An S3-compatible `storage.s3.endpoint` must be reachable from users' browsers**, and the bucket needs the CORS rule above.
* **No highly available database and no scheduled backups**: dump the database with `pg_dump` and back up the object store yourself.
* **Turn telemetry off** in God Mode, General, if you do not want Plane to report usage.
* **Plane's integrations service, Intake and Monitor are not part of the Community Edition.**

## External References

> [card] [Plane](https://plane.so)
>
> Product site

> [card] [Self-hosting overview](https://developers.plane.so/self-hosting/overview)
>
> Upstream self-hosting documentation

> [card] [Plane architecture](https://developers.plane.so/self-hosting/plane-architecture)
>
> How Plane's services fit together

> [card] [REST API reference](https://developers.plane.so/api-reference/introduction)
>
> Personal API tokens and endpoints

> [card] [Plane on GitHub](https://github.com/makeplane/plane)
>
> Source code and releases
