Gitea
Deploy Gitea on Kubernetes — a lightweight self-hosted Git service with a full web UI, issue tracking, CI/CD (Gitea Actions), package registry, and SSH access. The chart uses the official rootless image (UID 1000) for improved security.
Gitea uses gitea.rootUrl to construct all clone URLs, webhook callback addresses, and OAuth redirect URIs. If this
value is wrong or missing, users will see incorrect clone URLs and webhooks will fail silently. Always set it to the
full public URL (e.g. https://git.example.com/).
Key Features
- Rootless official image — runs as UID 1000 for improved pod security
- Dual service — separate HTTP (3000) and SSH (2222) services
- Three database backends — SQLite (default), PostgreSQL, MySQL with auto-detection
- Admin bootstrap Job — optional first-run Job to create the admin user
GITEA__env configuration — allapp.inikeys configurable via environment variables- Database-aware backup —
tarfor SQLite,pg_dump/mysqldumpfor SQL databases
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install gitea helmforge/gitea -f values.yaml
OCI registry:
helm install gitea oci://ghcr.io/helmforgedev/helm/gitea -f values.yaml
Deployment Examples
# values.yaml — Gitea with SQLite (zero database configuration)
gitea:
rootUrl: 'https://git.example.com/' # required; wrong value breaks all clone URLs
sshDomain: git.example.com
sshPort: 2222
disableRegistration: true # private instance
requireSignIn: true # require login to view any page
admin:
existingSecret: gitea-admin-credentials
# secret keys: admin-username, admin-password, admin-email
persistence:
enabled: true
size: 50Gi # repositories + LFS objects + config + SQLite
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: git.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gitea-tls
hosts:
- git.example.com# values.yaml — Gitea with bundled PostgreSQL (recommended for production)
gitea:
rootUrl: 'https://git.example.com/'
sshDomain: git.example.com
disableRegistration: true
requireSignIn: true
admin:
existingSecret: gitea-admin-credentials
postgresql:
enabled: true
auth:
database: gitea
username: gitea
password: 'strong-db-password'
primary:
persistence:
enabled: true
size: 20Gi
persistence:
enabled: true
size: 100Gi # repositories + LFS objects (no SQLite with PostgreSQL active)
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: gitea-backups
existingSecret: gitea-s3-credentials
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: git.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Gitea with SSH exposed via NodePort for external Git access
gitea:
rootUrl: 'https://git.example.com/'
sshDomain: k8s-node.example.com # public hostname or IP of the Kubernetes node
sshPort: 30022 # must match service.ssh.nodePort
admin:
existingSecret: gitea-admin-credentials
service:
http:
type: ClusterIP
port: 3000
ssh:
enabled: true
type: NodePort
port: 2222
nodePort: 30022 # expose SSH on node port 30022
postgresql:
enabled: true
auth:
password: 'strong-db-password'
persistence:
enabled: true
size: 100Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: git.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Gitea with external PostgreSQL and SMTP via GITEA__ env
gitea:
rootUrl: 'https://git.example.com/'
sshDomain: git.example.com
disableRegistration: true
requireSignIn: true
extraEnv:
- name: GITEA__mailer__ENABLED
value: 'true'
- name: GITEA__mailer__FROM
value: 'gitea@example.com'
- name: GITEA__mailer__SMTP_ADDR
value: smtp.example.com
- name: GITEA__mailer__SMTP_PORT
value: '587'
- name: GITEA__mailer__USER
valueFrom:
secretKeyRef:
name: gitea-smtp-credentials
key: username
- name: GITEA__mailer__PASSWD
valueFrom:
secretKeyRef:
name: gitea-smtp-credentials
key: password
admin:
existingSecret: gitea-admin-credentials
database:
mode: external
external:
vendor: postgres
host: postgres.database.svc.cluster.local
name: gitea
username: gitea
existingSecret: gitea-db-credentials
existingSecretPasswordKey: database-password
persistence:
enabled: true
size: 100Gi
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: gitea-backups
existingSecret: gitea-s3-credentialsConfiguration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
replicaCount | integer | 1 | Pod replicas. SQLite supports 1 only. |
nameOverride | string | "" | Override the chart name. |
fullnameOverride | string | "" | Override the full release name. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository | string | docker.io/gitea/gitea | Gitea image. |
image.tag | string | "1.25.5-rootless" | Rootless image tag (UID 1000). |
Gitea Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
gitea.appName | string | "Gitea: ..." | Application display name. |
gitea.runMode | string | prod | Run mode: dev, prod, test. |
gitea.rootUrl | string | "" | Required. Full public URL (e.g. https://git.example.com/). |
gitea.sshDomain | string | "" | SSH domain shown in clone URLs. Defaults to Ingress host. |
gitea.sshPort | integer | 2222 | SSH listen port (rootless image default). |
gitea.lfsEnabled | boolean | true | Enable Git Large File Storage. |
gitea.disableRegistration | boolean | false | Disable user self-registration. Set true for private instances. |
gitea.requireSignIn | boolean | false | Require login to view any page. |
gitea.extraEnv | array | [] | Extra env vars in GITEA__SECTION__KEY format for full app.ini control. |
Gitea supports overriding any app.ini configuration key via environment variables using the
pattern GITEA__<SECTION>__<KEY>. For example, to enable SMTP:
GITEA__mailer__ENABLED=true
GITEA__mailer__SMTP_ADDR=smtp.example.comPass these via gitea.extraEnv or gitea.existingSecret for sensitive values.
Admin User
| Parameter | Type | Default | Description |
|---|---|---|---|
admin.username | string | "" | Admin username. Triggers post-install Job to create user if set. |
admin.password | string | "" | Admin password. Ignored when existingSecret is set. |
admin.email | string | "" | Admin email address. |
admin.existingSecret | string | "" | Existing secret. Keys: admin-username, admin-password, admin-email. |
Database
Auto-detection precedence (database.mode: auto):
| Priority | Condition | Result |
|---|---|---|
| 1 | database.external.host or external.existingSecret | External DB |
| 2 | postgresql.enabled: true | PostgreSQL subchart |
| 3 | mysql.enabled: true | MySQL subchart |
| 4 | None of the above | SQLite (default) |
| Parameter | Type | Default | Description |
|---|---|---|---|
database.mode | string | auto | Mode: auto, sqlite, external, postgresql, mysql. |
database.sqlite.file | string | /var/lib/gitea/data/gitea.db | SQLite file path inside the data volume. |
database.external.vendor | string | postgres | External DB vendor: postgres or mysql. |
database.external.host | string | "" | External database hostname. |
database.external.existingSecret | string | "" | Existing secret with database password. |
Subcharts
| Parameter | Type | Default | Description |
|---|---|---|---|
postgresql.enabled | boolean | false | Deploy the bundled PostgreSQL subchart. |
postgresql.auth.password | string | "" | Password. Auto-generated if empty. |
postgresql.primary.persistence.size | string | 8Gi | PVC size for PostgreSQL. |
mysql.enabled | boolean | false | Deploy the bundled MySQL subchart. |
mysql.primary.persistence.size | string | 8Gi | PVC size for MySQL. |
Persistence
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled | boolean | true | Enable PVC for /var/lib/gitea (repositories, LFS, config, SQLite). |
persistence.size | string | 10Gi | PVC size. Size based on repository count and LFS usage. |
persistence.storageClass | string | "" | StorageClass for the PVC. |
persistence.existingClaim | string | "" | Use an existing PVC. |
Service
| Parameter | Type | Default | Description |
|---|---|---|---|
service.http.type | string | ClusterIP | HTTP service type. |
service.http.port | integer | 3000 | HTTP service port. |
service.ssh.enabled | boolean | true | Enable a separate SSH service. |
service.ssh.type | string | ClusterIP | SSH service type (ClusterIP, NodePort, LoadBalancer). |
service.ssh.port | integer | 2222 | SSH service port. |
service.ssh.nodePort | string | "" | NodePort for SSH (only when type: NodePort). |
Ingress and Probes
| Parameter | Type | Default | Description |
|---|---|---|---|
ingress.enabled | boolean | false | Enable an Ingress for HTTP traffic. |
ingress.ingressClassName | string | "" | Ingress class name. |
startupProbe.enabled | boolean | true | Startup probe on /api/healthz. |
livenessProbe.enabled | boolean | true | Liveness probe on /api/healthz. |
readinessProbe.enabled | boolean | true | Readiness probe on /api/healthz. |
resources | object | {} | CPU and memory requests/limits. |
extraManifests | array | [] | Extra Kubernetes manifests. |
Backup
Database-aware backup: SQLite archives /var/lib/gitea (repositories + LFS + SQLite). PostgreSQL
uses pg_dump. MySQL uses mysqldump.
When using PostgreSQL or MySQL, the backup CronJob only captures the database via pg_dump or mysqldump. Git
repository data and LFS objects stored in the /var/lib/gitea PVC are not included. Back up the PVC separately
using Velero, NFS snapshots, or storage provider snapshots. SQLite mode uses tar to archive the full
/var/lib/gitea, covering repositories and the database.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled | boolean | false | Enable scheduled S3 backup CronJob. |
backup.schedule | string | "0 3 * * *" | Cron schedule. |
backup.s3.endpoint | string | "" | S3-compatible endpoint URL. |
backup.s3.bucket | string | "" | Target bucket name. |
backup.s3.existingSecret | string | "" | Existing secret with S3 credentials. |
backup.database.postgresDumpArgs | string | "" | Extra arguments for pg_dump. |
backup.database.mysqlDumpArgs | string | --single-transaction ... | Extra mysqldump arguments. |