> ## 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.

# Gitea

> Deploy Gitea, a lightweight self-hosted Git service, on Control Plane. Covers repositories, pull requests, issues, the package registry, PostgreSQL backing, public HTTPS access, and optional Git-over-SSH.

## Overview

Gitea is a lightweight, self-hosted Git service — repositories, pull requests, issues, and a built-in package registry — backed by PostgreSQL. This template deploys a single Gitea server with persistent storage, an automatically wired PostgreSQL database, a public HTTPS web UI with Git-over-HTTPS, and an optional Git-over-SSH endpoint.

### Architecture

* **Gitea** — Stateful, single-replica workload running the rootless image. Serves the web UI, Git-over-HTTPS, and the package registry on port 3000, plus the built-in SSH server on 2222.
* **PostgreSQL** — Backing database provisioned from the `postgres` template as a subchart and connected to Gitea on startup.

### What Gets Created

* **Stateful Gitea Workload** — The Gitea server with configurable CPU and memory, bootstrapped with an admin account on first boot.
* **Stateful PostgreSQL Workload** — Single-replica Postgres, automatically connected to Gitea.
* **Volume Sets** — A persistent volume set for Gitea (`/var/lib/gitea`: repositories, LFS objects, attachments, and SSH host keys) and one for PostgreSQL data, both with optional autoscaling.
* **Secrets** — A dictionary secret holding the stable app secrets and admin credentials, an opaque secret holding the admin-bootstrap script, and the PostgreSQL credentials secret.
* **Identity & Policy** — An identity bound to the Gitea workload with `reveal` access scoped to exactly its two secrets plus the PostgreSQL config secret.
* **Direct Load Balancer** *(optional)* — A raw-TCP port for Git-over-SSH, created only when `ssh.enabled` is `true`.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

None for a default install. Before installing, change the admin password and generate the three security values (see [Important Notes](#important-notes)).

Install using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

Key configuration values (see the template's `values.yaml` for the complete set):

```yaml theme={null}
image: gitea/gitea:1.27.1-rootless  # rootless variant: runs as UID 1000, built-in SSH server on 2222

resources:
  minCpu: 250m
  minMemory: 512Mi
  maxCpu: 1000m
  maxMemory: 1024Mi

# Storage (repos, LFS, attachments, SSH host keys)
volumeset:
  capacity: 20 # initial capacity in GiB (minimum is 10)
  autoscaling:
    enabled: false
    maxCapacity: 200
    minFreePercentage: 10
    scalingFactor: 1.2

# Gitea admin + app secrets (template-scoped — CHANGE THESE)
gitea:
  admin:
    username: gitea_admin
    password: change-me-admin-pass   # CHANGE before install — the first admin login
    email: admin@example.com
  security:
    # Stable per install. CHANGE THESE. Generate each with:
    # gitea generate secret SECRET_KEY | INTERNAL_TOKEN | JWT_SECRET
    # Do NOT rotate after install — changing SECRET_KEY makes existing encrypted data unreadable.
    secretKey: REPLACE_WITH_gitea_generate_secret_SECRET_KEY
    internalToken: REPLACE_WITH_gitea_generate_secret_INTERNAL_TOKEN
    jwtSecret: REPLACE_WITH_gitea_generate_secret_JWT_SECRET
  disableRegistration: true  # true = admin-invite only; false = allow open self-registration

# Access
publicAccess:
  enabled: true  # HTTPS web UI + Git-over-HTTPS on the auto *.cpln.app endpoint

# Git-over-SSH (optional, OFF by default — see Important Notes)
ssh:
  enabled: false     # false = Git-over-HTTPS only (public web UI stays up); true = public SSH takes the endpoint
  externalPort: 22   # public port clients connect to (also advertised in SSH clone URLs)
  domain: ""         # advertised SSH host in clone URLs; empty = use the web domain

internalAccess:
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads:  # only used when type is same-gvc or workload-list
    #- //gvc/GVC_NAME/workload/WORKLOAD_NAME

# Backing database (postgres subchart)
postgres:
  image: postgres:18
  config:
    username: gitea
    password: change-me-db-pass
    database: gitea
  resources:
    minCpu: 200m
    minMemory: 256Mi
    maxCpu: 500m
    maxMemory: 512Mi
  volumeset:
    capacity: 10 # initial capacity in GiB (minimum is 10)
  internalAccess:
    type: same-gvc
```

### Image and Resources

* `image` — The Gitea image. The template uses the **rootless** variant, which runs as UID 1000 and serves SSH on port 2222 inside the container.
* `resources` — Min/max CPU and memory bounds for the Gitea workload.

### Storage

* `volumeset.capacity` — Initial Gitea volume size in GiB (minimum 10). Holds repositories, LFS objects, attachments, and SSH host keys.
* `volumeset.autoscaling.enabled` — Automatically expand the volume as it fills. When enabled:
  * `maxCapacity` — Maximum volume size in GiB.
  * `minFreePercentage` — Trigger a scale-up when free space drops below this percentage.
  * `scalingFactor` — Multiply the current capacity by this factor when scaling up.

### Admin and Security

* `gitea.admin.username` / `gitea.admin.password` / `gitea.admin.email` — The site administrator account, bootstrapped on first boot. **Change the password before installing.**
* `gitea.security.secretKey` / `internalToken` / `jwtSecret` — Stable per-install app secrets. Generate each with `gitea generate secret SECRET_KEY` (and `INTERNAL_TOKEN`, `JWT_SECRET`). These are values (not auto-generated) so they stay stable across upgrades. **Never rotate `secretKey` after install** — doing so makes all encrypted data (2FA secrets, tokens, mirror credentials) permanently unreadable.
* `gitea.disableRegistration` — `true` (default) restricts new accounts to admin invites; `false` allows open self-registration.

### Access

* `publicAccess.enabled` — Exposes the HTTPS web UI, Git-over-HTTPS, and the package registry on the auto-assigned `*.cpln.app` canonical endpoint.
* `ssh.enabled` — Exposes Git-over-SSH via a direct TCP load balancer. **OFF by default** — see [Important Notes](#important-notes) for the trade-off with the public web UI.
* `ssh.externalPort` — The public SSH port clients connect to; also advertised in SSH clone URLs (default `22`).
* `ssh.domain` — The SSH host advertised in clone URLs. Empty uses the web domain.
* `internalAccess.type` — Controls which workloads can reach Gitea over the internal network (`none`, `same-gvc`, `same-org`, or `workload-list`).

### Backing Database

* `postgres.config.username` / `password` / `database` — Credentials for the bundled PostgreSQL, applied on first startup. **Change the password before deploying to production.**
* `postgres.resources` — Min/max CPU and memory bounds for the PostgreSQL workload.
* `postgres.volumeset.capacity` — Initial Postgres volume size in GiB (minimum 10).
* `postgres.internalAccess.type` — Controls which workloads can reach PostgreSQL.

<Note>
  PostgreSQL credentials are only applied on first startup when the data directory is empty. Changing them after the initial deployment has no effect on the running database — use PostgreSQL's native commands (e.g. `ALTER USER`) instead.
</Note>

## Connecting

| Access                             | Endpoint                                | Notes                                                                                                                              |
| ---------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Web UI + Git-over-HTTPS + registry | `https://<canonical>.cpln.app`          | Auto-assigned when `publicAccess.enabled`. Find it under `status.canonicalEndpoint` (`cpln workload get <release>-gitea -o yaml`). |
| Git-over-SSH                       | direct-LB address on `ssh.externalPort` | Only when `ssh.enabled`. The reachable host is the `loadBalancer.direct` address from the workload status.                         |
| Internal (in-GVC)                  | `<release>-gitea.<gvc>.cpln.local:3000` | Reachable from other workloads per `internalAccess.type`.                                                                          |
| Credentials                        | admin login                             | `gitea.admin.username` / `gitea.admin.password`.                                                                                   |

Open the canonical endpoint in a browser and sign in with the admin credentials to create repositories, users, and organizations. Clone and push over HTTPS using the same endpoint.

## The SSH / HTTPS Endpoint Trade-off

A Control Plane workload has exactly **one** `*.cpln.app` canonical endpoint, and it can serve **either** the HTTPS web UI (web UI + Git-over-HTTPS on port 443) **or** a raw-TCP load balancer for SSH (port 22) — **not both**. Enabling `ssh.enabled` repoints that single endpoint to SSH, which takes the public web UI on 443 offline.

Because of this, **`ssh.enabled` is `false` by default**, which keeps the public web UI and Git-over-HTTPS working out of the box — all most users need. Git-over-HTTPS supports full clone, push, and pull, so SSH is not required for normal use.

<Warning>
  Enable `ssh.enabled: true` only if you either (a) serve the web UI through a **custom domain** (so the canonical endpoint is free for SSH), or (b) only need Git-over-SSH. Turning it on repoints the public `*.cpln.app` endpoint to SSH on port 22 and makes the public HTTPS web UI on 443 unreachable.
</Warning>

## Important Notes

* **Change `gitea.admin.password` and the three `gitea.security.*` values before installing.** The shipped defaults are illustrative placeholders and are insecure as-is. Generate each security value with `gitea generate secret SECRET_KEY` (and `INTERNAL_TOKEN`, `JWT_SECRET`).
* **Never rotate `gitea.security.secretKey` after install.** Changing it makes all encrypted data (2FA secrets, tokens, mirror credentials) permanently unreadable. Because it is a value rather than auto-generated, `helm upgrade` keeps it stable.
* **Public SSH and the public web UI cannot share one endpoint** — SSH is off by default. See [The SSH / HTTPS Endpoint Trade-off](#the-ssh-%2F-https-endpoint-trade-off) above.
* **Single replica only.** A rolling restart or upgrade incurs brief downtime. Do not raise the workload scale above 1 — replicas would each get separate repo volumes and corrupt state.
* **Data lives on the volume set** and survives redeploys under the same release name. Change admin credentials after first boot in the Gitea UI, not via values — the data directory keeps the original account. To fully reset, `helm uninstall` (which deletes the volume set) then reinstall.

## External References

<CardGroup cols={2}>
  <Card title="Gitea Documentation" href="https://docs.gitea.com/" icon="book">
    Official Gitea product documentation
  </Card>

  <Card title="Configuration Cheat Sheet" href="https://docs.gitea.com/administration/config-cheat-sheet" icon="sliders">
    Full reference of Gitea configuration options
  </Card>

  <Card title="Package Registry" href="https://docs.gitea.com/usage/packages/overview" icon="box">
    Using Gitea's built-in package registry
  </Card>

  <Card title="Gitea Template" href="https://github.com/controlplane-com/templates/tree/main/gitea" icon="github">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
