Skip to content

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.rootUrl is required — wrong value breaks all clone URLs and webhooks

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 — all app.ini keys configurable via environment variables
  • Database-aware backuptar for SQLite, pg_dump/mysqldump for 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-credentials

Configuration Reference

Core

ParameterTypeDefaultDescription
replicaCountinteger1Pod replicas. SQLite supports 1 only.
nameOverridestring""Override the chart name.
fullnameOverridestring""Override the full release name.

Image

ParameterTypeDefaultDescription
image.repositorystringdocker.io/gitea/giteaGitea image.
image.tagstring"1.25.5-rootless"Rootless image tag (UID 1000).

Gitea Configuration

ParameterTypeDefaultDescription
gitea.appNamestring"Gitea: ..."Application display name.
gitea.runModestringprodRun mode: dev, prod, test.
gitea.rootUrlstring""Required. Full public URL (e.g. https://git.example.com/).
gitea.sshDomainstring""SSH domain shown in clone URLs. Defaults to Ingress host.
gitea.sshPortinteger2222SSH listen port (rootless image default).
gitea.lfsEnabledbooleantrueEnable Git Large File Storage.
gitea.disableRegistrationbooleanfalseDisable user self-registration. Set true for private instances.
gitea.requireSignInbooleanfalseRequire login to view any page.
gitea.extraEnvarray[]Extra env vars in GITEA__SECTION__KEY format for full app.ini control.
Configure any app.ini setting via GITEA__SECTION__KEY environment variables

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

Pass these via gitea.extraEnv or gitea.existingSecret for sensitive values.

Admin User

ParameterTypeDefaultDescription
admin.usernamestring""Admin username. Triggers post-install Job to create user if set.
admin.passwordstring""Admin password. Ignored when existingSecret is set.
admin.emailstring""Admin email address.
admin.existingSecretstring""Existing secret. Keys: admin-username, admin-password, admin-email.

Database

Auto-detection precedence (database.mode: auto):

PriorityConditionResult
1database.external.host or external.existingSecretExternal DB
2postgresql.enabled: truePostgreSQL subchart
3mysql.enabled: trueMySQL subchart
4None of the aboveSQLite (default)
ParameterTypeDefaultDescription
database.modestringautoMode: auto, sqlite, external, postgresql, mysql.
database.sqlite.filestring/var/lib/gitea/data/gitea.dbSQLite file path inside the data volume.
database.external.vendorstringpostgresExternal DB vendor: postgres or mysql.
database.external.hoststring""External database hostname.
database.external.existingSecretstring""Existing secret with database password.

Subcharts

ParameterTypeDefaultDescription
postgresql.enabledbooleanfalseDeploy the bundled PostgreSQL subchart.
postgresql.auth.passwordstring""Password. Auto-generated if empty.
postgresql.primary.persistence.sizestring8GiPVC size for PostgreSQL.
mysql.enabledbooleanfalseDeploy the bundled MySQL subchart.
mysql.primary.persistence.sizestring8GiPVC size for MySQL.

Persistence

ParameterTypeDefaultDescription
persistence.enabledbooleantrueEnable PVC for /var/lib/gitea (repositories, LFS, config, SQLite).
persistence.sizestring10GiPVC size. Size based on repository count and LFS usage.
persistence.storageClassstring""StorageClass for the PVC.
persistence.existingClaimstring""Use an existing PVC.

Service

ParameterTypeDefaultDescription
service.http.typestringClusterIPHTTP service type.
service.http.portinteger3000HTTP service port.
service.ssh.enabledbooleantrueEnable a separate SSH service.
service.ssh.typestringClusterIPSSH service type (ClusterIP, NodePort, LoadBalancer).
service.ssh.portinteger2222SSH service port.
service.ssh.nodePortstring""NodePort for SSH (only when type: NodePort).

Ingress and Probes

ParameterTypeDefaultDescription
ingress.enabledbooleanfalseEnable an Ingress for HTTP traffic.
ingress.ingressClassNamestring""Ingress class name.
startupProbe.enabledbooleantrueStartup probe on /api/healthz.
livenessProbe.enabledbooleantrueLiveness probe on /api/healthz.
readinessProbe.enabledbooleantrueReadiness probe on /api/healthz.
resourcesobject{}CPU and memory requests/limits.
extraManifestsarray[]Extra Kubernetes manifests.

Backup

Database-aware backup: SQLite archives /var/lib/gitea (repositories + LFS + SQLite). PostgreSQL uses pg_dump. MySQL uses mysqldump.

Backup covers the database only for PostgreSQL/MySQL — Git repositories reside in the PVC

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.

ParameterTypeDefaultDescription
backup.enabledbooleanfalseEnable scheduled S3 backup CronJob.
backup.schedulestring"0 3 * * *"Cron schedule.
backup.s3.endpointstring""S3-compatible endpoint URL.
backup.s3.bucketstring""Target bucket name.
backup.s3.existingSecretstring""Existing secret with S3 credentials.
backup.database.postgresDumpArgsstring""Extra arguments for pg_dump.
backup.database.mysqlDumpArgsstring--single-transaction ...Extra mysqldump arguments.

More Information