Skip to main content

Overview

Gitea is a lightweight self-hosted Git service — repositories, pull requests, issues and a built-in package registry. This template deploys one Gitea server (the rootless image) on a persistent volume, a bundled PostgreSQL database from the PostgreSQL template, a public HTTPS endpoint serving the web UI, Git-over-HTTPS and the package registry, and optional Git-over-SSH. The site administrator’s login and Gitea’s three long-lived signing keys are not template values. They come from one dictionary secret you create before installing, so they never pass through Helm and never land in the release. The bundled database’s credentials are internal plumbing: the template builds that secret for you from plain values.
This template deploys into an existing GVC that you already have. It does not create or manage a GVC.

What Gets Created

The template creates no credential secret for Gitea itself — the admin login and signing keys live only in the auth secret you create.

Prerequisites

One secret must exist before you install. Secrets are org-level, so no GVC flag is involved.
1

Create the auth secret

A dictionary secret holding exactly six keys. The three admin* keys create the first site-admin account on first boot; the other three are Gitea’s signing keys:
Set gitea.auth.secretName to this name. Read it back later with cpln secret reveal GITEA_AUTH_SECRET_NAME -o yaml — a bare cpln secret reveal prints only a summary table, not the values.
jwtSecret has a format constraint the other keys do not. It must be base64url of exactly 32 bytes, or Gitea refuses to start. That is what openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' produces, and it is why the command pipes through tr. Do not substitute a hex string or a passphrase for this key.
Create the secret before installing, or the deployment wedges silently. The chart refuses to render when gitea.auth.secretName is blank, but a name pointing at a secret that does not exist installs successfully and then never starts. The container never runs, so cpln logs returns zero lines. The one place the reason appears is status.versions[].message:
Use get-deployments — plain cpln workload get has no versions key. Creating the missing secret repairs the deployment on its own within several minutes, or run cpln workload force-redeployment RELEASE_NAME-gitea --gvc GVC_NAME to skip the wait.
The bundled database needs nothing created in advance: the template writes its credentials secret from postgres.credentials.*. Pick a database password at install time (see Installation). Database backups are optional and need a bucket plus, for AWS or GCP, a Control Plane cloud account — see Backing Up.

Installation

Install the chart from the marketplace registry, pointing it at the auth secret and replacing the published database-password placeholder:
For a second Gitea release in the same org, also set postgres.config.credentialsSecretName to a name unique to that release — secret names are org-wide, and a second release left on the default is refused at install (see Troubleshooting). Or follow the instructions for your preferred method:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Each block below is the shipped default for that part of values.yaml.

Image and Resources

On a stateful workload the ratio of maxCpu to minCpu may not exceed 4:1. Raising maxCpu without raising minCpu can cross that limit and is rejected when the workload is applied.

Storage

The volume holds repositories, LFS objects, attachments and the built-in SSH server’s host keys. Packages in the registry and LFS objects are stored here too, so size it for your artifacts, not just your source.

Gitea

gitea.auth.secretName names the prerequisite dictionary secret from Prerequisites. Its admin* keys are consumed once, at first boot: editing the secret later does not change the existing administrator account — see Rotating the Admin Password.

Public Access

Public access is on by default: git clone and git push from laptops and CI are the point of the service, self-registration is closed by default, and the admin login is a credential you created rather than a published default. Set publicAccess.enabled: false to keep Gitea inside the GVC and reach the UI through cpln port-forward (see Connecting). A change to publicAccess can take several minutes to take effect — re-test before concluding it did not apply.

Git-over-SSH

A workload has exactly one *.cpln.app canonical endpoint. With ssh.enabled: true that endpoint serves Git-over-SSH on ssh.externalPort instead of HTTPS, so the public web UI and Git-over-HTTPS are no longer reachable at it. SSH is therefore off by default, and Git-over-HTTPS supports full clone, push and pull, so most installs never need it.
Enable ssh.enabled: true only if you either serve the web UI through a custom domain, which leaves the canonical endpoint free for SSH, or only need Git-over-SSH. Turning it on makes the public HTTPS web UI unreachable at the *.cpln.app endpoint; the UI stays reachable through cpln port-forward.
When SSH is enabled, Gitea’s built-in SSH server listens on container port 2222 behind a direct TCP load balancer on ssh.externalPort, and the SSH user is git. Clone URLs advertise ssh.domain, or the web host when it is empty — with the default of 22 the repository page shows the familiar form:
Copy the exact URL from the repository’s clone widget rather than assembling it, and add your public key under Settings → SSH / GPG Keys first. Set ssh.domain to a custom domain that points at the SSH endpoint if you want clean git@ URLs that do not change with the endpoint.
With ssh.enabled: false, Gitea’s REST API still reports a placeholder ssh://git@localhost:2222/... clone URL on repository objects. It is cosmetic — the web UI’s clone widget offers only the HTTPS URL — and affects only scripts that read ssh_url from the API.

Internal Access

Bundled Database

The database is internal plumbing: it serves Gitea only, is unreachable from outside the GVC, and no human types its password into a client — so the credentials stay plain values and the template writes them into the dictionary secret named by postgres.config.credentialsSecretName. The password is used as-is, so change it before installing.
PostgreSQL applies postgres.credentials.* only when its volume is first initialized. Changing them afterwards rewrites the secret but not the running database’s password — rotate inside PostgreSQL first, as described in the PostgreSQL template, then update the value and force a redeployment of RELEASE_NAME-gitea so it reconnects with the new password.

Database Backup

Every knob of the PostgreSQL template is available under postgres.*, including its scheduled backup. It is off by default; enabling it adds one cron workload that runs pg_dumpall and uploads a gzip-compressed dump to your bucket. The job authenticates with the chart-created database credentials secret, and the database identity picks up the bucket-scoped policy or cloud account automatically.
The bucket, cloud account and IAM policy steps for each provider are in the PostgreSQL template’s Backing Up section — the keys are identical, nested under postgres.backup. A database dump alone is not a full Gitea backup: repositories, LFS objects and attachments live on the Gitea volume. See Backing Up.

Connecting

To verify from your own machine, read the canonical endpoint and call Gitea’s health endpoint on it — the same path the readiness probe uses:
With public access off, open a tunnel instead and use http://localhost:3000 in place of the canonical endpoint:

First Run

There is no web install wizard — the chart locks installation and creates the site administrator from your auth secret on first boot.
  1. Open the canonical endpoint (or http://localhost:3000 through the tunnel) and sign in with adminUsername and adminPassword from the auth secret.
  2. Self-registration is closed by default (gitea.disableRegistration: true). Create accounts under Site Administration → Identity & Access → User Accounts, or set gitea.disableRegistration: false and upgrade the release to allow open sign-up.
  3. Create a repository and push to it over HTTPS. The clone widget gives the URL; authenticate with your Gitea username and password, or generate an access token under Settings → Applications.
The administrator account exists in the database from this point on. Changing its password later is done in the Gitea UI, not in the secret — see Rotating the Admin Password.

Operations

Backing Up

The chart schedules no backups of its own. A complete Gitea backup has two halves: the database (users, issues, pull requests, settings) and the Gitea volume (repositories, LFS objects, attachments, packages). Database. Enable the bundled PostgreSQL’s scheduled pg_dumpall with postgres.backup.enabled: true as described under Database Backup, or take a one-off dump through the database container. The dump is base64-encoded in transit because cpln workload exec is not binary-safe:
Gitea volume. Take a snapshot of the volume set before upgrades or risky changes:
Portable full backup. Gitea’s own gitea dump bundles the database and the volume contents into one archive. The commands below are built from the image’s paths (GITEA_APP_INI is set in the container; the container user owns /var/lib/gitea) but have not been exercised against this template — rehearse on a throwaway release first. Write the archive, then stream it out base64-encoded:
Back up the auth secret as well: secretKey is the only way to read the 2FA secrets, access tokens and mirror credentials in any database dump.

Restoring a Backup

No restore path has been exercised end to end against this template. The steps are built from the chart’s configuration and the upstream procedure, which upstream itself describes as manual. Verify on a throwaway release before relying on them.
Restore into a fresh release that points at the same auth secret the backup was taken under — a different secretKey makes the restored tokens and 2FA secrets unreadable. Then:
  1. Database. For a scheduled pg_dumpall object, follow the PostgreSQL template’s Restoring a Backup against RELEASE_NAME-postgres.GVC_NAME.cpln.local. For a pg_dump archive taken as above, load it through the database container (--stdin is required):
  2. Gitea volume. Either restore the volume set snapshot taken earlier (cpln volumeset snapshot restore — check --help; not exercised here), or unpack a gitea dump archive and copy its repos/ contents to /var/lib/gitea/git/repositories and its data/ contents to /var/lib/gitea/data in the Gitea container, as described in the upstream backup and restore guide.
  3. Regenerate hooks and restart. After repositories are in place, run the upstream post-restore step and restart Gitea so it re-reads the database:

Rotating the Admin Password

The bootstrap script creates the administrator once; on later starts the account already exists and the secret’s adminPassword is not re-applied. Change the password in the Gitea UI: sign in as the administrator and use Settings → Account. Updating adminPassword in the secret afterwards keeps your records consistent but has no effect on the live account. Never change secretKey or jwtSecret — the first makes every stored 2FA secret, access token and mirror credential permanently unreadable, the second invalidates every issued OAuth2 token.

Upgrading from 1.0.0

Template version 1.0.0 took the admin login and all three security values as plain Helm values and shipped published defaults for every one of them. Version 1.1.0 moved them into the prerequisite auth secret and rejects each old key at render with a message naming its replacement, so the upgrade fails before anything is applied and the running release is left untouched. There are no compatibility fallbacks.
Put your existing values into the secret — do not generate new ones for a live install. Changing secretKey makes every 2FA secret, access token and mirror credential already in the database permanently unreadable, and changing jwtSecret invalidates every OAuth2 token already issued.If your install still carries the published 1.0.0 defaults, everything it has encrypted is protected by values printed in a public repository — and rotating them in place is destructive in exactly the way above. The safe path is a fresh install with new key material and a repository migration onto it. At minimum, change the administrator’s password immediately.
1

Create the auth secret from your current values

Follow Prerequisites, using the gitea.admin.* and gitea.security.* values your 1.0.0 release was installed with.
2

Remove the six old keys from your values

Delete gitea.admin.* and gitea.security.*, and set gitea.auth.secretName to the secret’s name.
3

Apply the 1.1.0 database rename too

A jump straight to 1.2.0 also needs the change under Upgrading from 1.1.0. Then upgrade the release.

Upgrading from 1.1.0

Version 1.2.0 adopted the PostgreSQL template 3.4.1 as its bundled database. That template no longer takes credentials as values, and Gitea absorbed the change rather than passing it on: there is no new prerequisite, only a rename. The database keeps the credentials it was initialized with, so carry the same three values over under the new keys. The chart now writes those values into a dictionary secret named by postgres.config.credentialsSecretName (default my-gitea-db-credentials) and hands only the name to the database. Secret names are org-wide, so give each Gitea release its own.
A values file that still carries the old keys fails with the PostgreSQL template’s message, not this template’s. It tells you to create a dictionary secret yourself — ignore that advice here, because this template creates the secret for you. Move the three keys and the upgrade proceeds; the auth secret is unchanged.

Scaling and Availability

  • Gitea is pinned to one replica. Do not raise it — each replica would get its own repository volume, and there is no shared storage between them.
  • The first cpln helm upgrade after an install can restart the bundled database, so expect Gitea to be briefly unreachable during it. Treat every upgrade as a short planned outage.
  • A node failure reschedules the server and reattaches the same volume. The exposure is minutes of downtime, not data loss. The Gitea volume set takes one final snapshot when it is deleted, kept for seven days; it is not snapshotted on a schedule, so use the backup steps above for point-in-time copies.

Troubleshooting

Symptom: the install succeeds, RELEASE_NAME-gitea stays not ready indefinitely, and cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-gitea"}' --limit 50 --since 10m returns zero lines.Cause: gitea.auth.secretName names a secret that does not exist, so the container is never started. The reason is visible only in status.versions[].message:
Fix: create the secret as in Prerequisites. The deployment recovers on its own within several minutes, or run cpln workload force-redeployment RELEASE_NAME-gitea --gvc GVC_NAME.
Symptom: the container starts, exits, and cpln logs shows Gitea failing while decoding the OAuth2 JWT_SECRET.Cause: the jwtSecret key in your auth secret is not base64url of exactly 32 bytes — a hex string or passphrase was used.Fix: this key can be changed safely before any OAuth2 token has been issued. Regenerate it with openssl rand -base64 32 | tr '+/' '-_' | tr -d '=', update the secret, and run cpln workload force-redeployment RELEASE_NAME-gitea --gvc GVC_NAME — a running replica does not pick up a changed secret on its own.
Symptom: cpln helm upgrade stops before touching any resource:
gitea.admin.password, gitea.admin.email, gitea.security.secretKey, gitea.security.internalToken and gitea.security.jwtSecret each produce the same kind of message under their own name.Cause: your values still carry keys that were removed in 1.1.0.Fix: follow Upgrading from 1.0.0. Your running release is unaffected until the upgrade succeeds.
Symptom: cpln helm upgrade to 1.2.0 stops at render with a message from the bundled PostgreSQL chart:
Cause: your values still set postgres.config.username, postgres.config.password or postgres.config.database, which moved to postgres.credentials.* in 1.2.0.Fix: rename the three keys as in Upgrading from 1.1.0. Do not create a database secret yourself — this template creates it.
Symptom: installing a second Gitea release in the same org fails and creates nothing.Cause: both releases render the same database credentials secret name, postgres.config.credentialsSecretName (default my-gitea-db-credentials), and secret names are org-wide. The first release owns it; nothing is shared, overwritten or deleted.Fix: install the second release with --set postgres.config.credentialsSecretName=UNIQUE_NAME.
Symptom: you updated adminPassword in the auth secret, but Gitea still accepts the previous password.Cause: the bootstrap script creates the administrator only once; afterwards the password lives in the database, not the secret.Fix: change it in the Gitea UI — see Rotating the Admin Password.
Symptom: after setting ssh.enabled: true, HTTPS requests to the *.cpln.app endpoint no longer reach Gitea, while git clone over SSH works.Cause: by design — the single canonical endpoint now serves SSH instead of HTTPS. See Git-over-SSH.Fix: serve the web UI through a custom domain, reach it through cpln port-forward RELEASE_NAME-gitea 3000:3000 --gvc GVC_NAME, or set ssh.enabled: false again to get the public web UI back. Allow several minutes for the change to take effect.

Important Notes

  • Create the auth secret before installing. A reference to a secret that does not exist wedges the workload with no log output; see Prerequisites for the one command that shows the reason.
  • Never rotate secretKey or jwtSecret after install. The first makes stored 2FA secrets, tokens and mirror credentials permanently unreadable; the second invalidates issued OAuth2 tokens.
  • jwtSecret must be base64url of exactly 32 bytes — use the command in Prerequisites.
  • Change postgres.credentials.password before installing. The shipped change-me-gitea-db is a published placeholder used as-is.
  • Give each Gitea release its own postgres.config.credentialsSecretName. Secret names are org-wide; a second release on the default name is refused at install.
  • Public SSH and the public web UI cannot share the canonical endpoint — SSH is off by default. See Git-over-SSH.
  • Do not scale past one replica. Replicas would each get a separate repository volume.
  • Editing the auth secret after first boot does not change the administrator account. Change the password in Gitea — see Rotating the Admin Password.
  • cpln helm uninstall deletes both volume sets — repositories and database alike. Your auth secret is yours and is left alone.

External References

Gitea Documentation

Official Gitea product documentation

Configuration Cheat Sheet

Full reference of Gitea configuration options

Backup and Restore

Upstream guidance on gitea dump and the manual restore procedure

Package Registry

Using Gitea’s built-in package registry

Gitea Template

View the source files, default values, and chart definition