Skip to main content

Instalasi Gitea di Kubernetes

Pengenalan

Panduan ini menjelaskan cara deploy Gitea di Kubernetes cluster menggunakan manifests yang production-ready. Gitea adalah self-hosted Git service yang ringan dan powerful, sangat cocok untuk deployment di Kubernetes dengan high availability dan scalability.

Mengapa Gitea di Kubernetes?

Keuntungan Deployment Kubernetes

  • 🚀 Scalability - Easy horizontal scaling
  • 🔄 High Availability - Multi-replica deployment
  • 📦 Container Native - Optimized untuk container workloads
  • 🔧 Easy Management - Declarative configuration
  • 💾 Persistent Storage - StatefulSet dengan PVC
  • 🌐 Service Discovery - Built-in DNS dan load balancing
  • 🔒 Security - Network policies dan RBAC

Prerequisites

Cluster Requirements

  • Kubernetes: v1.24+ (K3s, K8s, atau managed Kubernetes)
  • kubectl: Configured dan connected ke cluster
  • Storage Class: Default atau custom storage class tersedia
  • Ingress Controller: Nginx, Traefik, atau Envoy Gateway
  • Cert-Manager: Untuk SSL/TLS certificates (optional)

External Dependencies

  • PostgreSQL Database: Production-grade database
    • Host: Accessible dari cluster
    • Database: Created dan ready
    • User: Dengan proper permissions
  • DNS: Domain name untuk akses external

Resource Requirements

Minimum untuk Gitea:

  • CPU: 512m request, 1 core limit
  • Memory: 1Gi request, 2Gi limit
  • Storage: 10Gi untuk repositories dan data

Arsitektur Deployment

┌─────────────────────────────────────────────────────────┐
│ Internet Traffic │
└────────────────────┬────────────────────────────────────┘
│ HTTPS (443)

┌─────────────────────────────────────────────────────────┐
│ Ingress / Gateway │
│ (Nginx / Envoy / Traefik) │
└────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ Gitea Service (ClusterIP) │
│ Port: 3000 │
└────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ Gitea StatefulSet (Pod) │
│ ┌──────────────────────────────────────────────┐ │
│ │ Container: gitea/gitea:latest │ │
│ │ - HTTP Port: 3000 │ │
│ │ - SSH Port: 22 │ │
│ │ - Volume: /data (10Gi PVC) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ Host: postgres.namespace:5432 │
└─────────────────────────────────────────────────────────┘

Installation Steps

Step 1: Persiapan Namespace

Buat dedicated namespace untuk Gitea:

# Create namespace
kubectl create namespace gitea

# Or using manifest
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: gitea
labels:
name: gitea
app: gitea
EOF

Step 2: Setup Database Secret

Buat secret untuk PostgreSQL credentials:

# Create secret from literals
kubectl create secret generic postgres-secret \
--from-literal=POSTGRES_DB=gitea \
--from-literal=POSTGRES_USER=gitea \
--from-literal=POSTGRES_PASSWORD=your_secure_password \
-n gitea

# Verify secret
kubectl get secret postgres-secret -n gitea

Atau menggunakan manifest:

# postgres-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: gitea
type: Opaque
stringData:
POSTGRES_DB: gitea
POSTGRES_USER: gitea
POSTGRES_PASSWORD: your_secure_password

Apply:

kubectl apply -f postgres-secret.yaml

Step 3: Persistent Storage

Buat PersistentVolumeClaim untuk Gitea data:

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-data
namespace: gitea
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path # Adjust ke storage class Anda

Apply:

kubectl apply -f pvc.yaml

# Verify PVC status
kubectl get pvc -n gitea

Step 4: Deploy Gitea StatefulSet

Buat StatefulSet untuk Gitea:

# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: gitea
namespace: gitea
labels:
app: gitea
spec:
serviceName: gitea
replicas: 1
selector:
matchLabels:
app: gitea
template:
metadata:
labels:
app: gitea
spec:
containers:
- name: gitea
image: gitea/gitea:1.21.5 # Use specific version
imagePullPolicy: IfNotPresent

ports:
- name: http
containerPort: 3000
protocol: TCP
- name: ssh
containerPort: 22
protocol: TCP

env:
# User/Group ID
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"

# Database configuration
- name: GITEA__database__DB_TYPE
value: postgres
- name: GITEA__database__HOST
value: postgres.gitea:5432 # Adjust ke PostgreSQL service
- name: GITEA__database__NAME
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_DB
- name: GITEA__database__USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_USER
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_PASSWORD

# Server configuration
- name: GITEA__server__DOMAIN
value: git.example.com # Your domain
- name: GITEA__server__ROOT_URL
value: https://git.example.com
- name: GITEA__server__HTTP_PORT
value: "3000"
- name: GITEA__server__SSH_PORT
value: "22"

volumeMounts:
- name: data
mountPath: /data

resources:
requests:
cpu: 512m
memory: 1Gi
limits:
cpu: 1
memory: 2Gi

livenessProbe:
httpGet:
path: /api/healthz
port: http
initialDelaySeconds: 120
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

readinessProbe:
httpGet:
path: /api/healthz
port: http
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3

volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path

Apply:

kubectl apply -f statefulset.yaml

# Watch pod creation
kubectl get pods -n gitea -w

# Check logs
kubectl logs -n gitea -l app=gitea -f

Step 5: Create Service

Buat Service untuk internal access:

# service.yaml
apiVersion: v1
kind: Service
metadata:
name: gitea
namespace: gitea
labels:
app: gitea
spec:
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 3000
targetPort: 3000
- name: ssh
protocol: TCP
port: 22
targetPort: 22
selector:
app: gitea

Apply:

kubectl apply -f service.yaml

# Verify service
kubectl get svc -n gitea

Step 6: Configure Ingress

Option A: Nginx Ingress

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea
namespace: gitea
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "500m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
tls:
- hosts:
- git.example.com
secretName: gitea-tls
rules:
- host: git.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea
port:
number: 3000

Option B: Gateway API (Envoy)

# httproute.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: gitea
namespace: gitea
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: envoy-gateway
namespace: staging
hostnames:
- git.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- group: ''
kind: Service
name: gitea
port: 3000
weight: 1
timeouts:
request: 360s

Apply:

kubectl apply -f ingress.yaml
# or
kubectl apply -f httproute.yaml

# Verify
kubectl get ingress -n gitea
# or
kubectl get httproute -n gitea

Step 7: SSL Certificate (Cert-Manager)

Jika menggunakan Cert-Manager:

# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gitea-cert
namespace: gitea
spec:
secretName: gitea-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- git.example.com

Apply:

kubectl apply -f certificate.yaml

# Check certificate status
kubectl get certificate -n gitea
kubectl describe certificate gitea-cert -n gitea

Verification

Check Deployment Status

# Check all resources
kubectl get all -n gitea

# Check StatefulSet
kubectl get statefulset -n gitea
kubectl describe statefulset gitea -n gitea

# Check pods
kubectl get pods -n gitea
kubectl logs -n gitea gitea-0

# Check PVC
kubectl get pvc -n gitea

# Check services
kubectl get svc -n gitea

# Check ingress
kubectl get ingress -n gitea

Test Internal Access

# Port forward untuk testing
kubectl port-forward -n gitea svc/gitea 3000:3000

# Access di browser
# http://localhost:3000

Test External Access

# Check DNS resolution
nslookup git.example.com

# Test HTTPS
curl -I https://git.example.com

# Access di browser
# https://git.example.com

Initial Configuration

First Time Setup

  1. Access Gitea URL: https://git.example.com

  2. Initial Configuration Form:

    Database Type: PostgreSQL
    Host: postgres.gitea:5432
    Username: gitea
    Password: [from secret]
    Database Name: gitea

    Site Title: Your Organization Git
    Repository Root Path: /data/git/repositories
    LFS Root Path: /data/git/lfs
    Run As User: git

    Server Domain: git.example.com
    SSH Server Port: 22
    HTTP Port: 3000
    Application URL: https://git.example.com
  3. Administrator Account:

    Username: admin
    Password: [strong password]
    Email: admin@example.com
  4. Click "Install Gitea"

Configuration Management

Update Configuration via ConfigMap

# gitea-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: gitea-config
namespace: gitea
data:
app.ini: |
[server]
DOMAIN = git.example.com
ROOT_URL = https://git.example.com
HTTP_PORT = 3000
DISABLE_SSH = false
SSH_PORT = 22

[database]
DB_TYPE = postgres
HOST = postgres.gitea:5432

[security]
INSTALL_LOCK = true

[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = false

[repository]
ROOT = /data/git/repositories

Mount ConfigMap in StatefulSet:

volumeMounts:
- name: config
mountPath: /data/gitea/conf
volumes:
- name: config
configMap:
name: gitea-config

Scaling Considerations

Single Replica (Current)

  • Pros: Simple, no synchronization issues
  • Cons: No high availability, downtime during updates

Multi-Replica (Advanced)

Untuk multi-replica deployment:

spec:
replicas: 3 # Multiple replicas

Requirements:

  • ReadWriteMany storage (NFS, CephFS, etc.)
  • Session persistence (Redis/Memcached)
  • Database connection pooling
  • Load balancer configuration

Backup Strategy

Backup Script

#!/bin/bash
# backup-gitea.sh

NAMESPACE="gitea"
POD_NAME=$(kubectl get pod -n $NAMESPACE -l app=gitea -o jsonpath='{.items[0].metadata.name}')
BACKUP_DIR="/backup/gitea/$(date +%Y%m%d-%H%M%S)"

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup data volume
kubectl exec -n $NAMESPACE $POD_NAME -- tar czf - /data > $BACKUP_DIR/data.tar.gz

# Backup database
kubectl exec -n $NAMESPACE $POD_NAME -- pg_dump -h postgres.gitea -U gitea gitea > $BACKUP_DIR/database.sql

echo "Backup completed: $BACKUP_DIR"

Automated Backup with CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
name: gitea-backup
namespace: gitea
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
# Backup script here
restartPolicy: OnFailure

Troubleshooting

Pod Not Starting

# Check pod status
kubectl get pods -n gitea

# Check events
kubectl describe pod -n gitea gitea-0

# Check logs
kubectl logs -n gitea gitea-0

# Common issues:
# - PVC not bound
# - Secret not found
# - Database connection failed

Database Connection Issues

# Test database connectivity
kubectl run -it --rm debug --image=postgres:15 --restart=Never -- \
psql -h postgres.gitea -U gitea -d gitea

# Check secret values
kubectl get secret postgres-secret -n gitea -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d

Storage Issues

# Check PVC status
kubectl get pvc -n gitea

# Check PV
kubectl get pv

# Check storage class
kubectl get storageclass

# Describe PVC for events
kubectl describe pvc gitea-data -n gitea

Ingress Not Working

# Check ingress
kubectl describe ingress gitea -n gitea

# Check ingress controller logs
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller

# Test internal service
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
curl http://gitea.gitea:3000

Security Best Practices

1. Use Specific Image Versions

image: gitea/gitea:1.21.5  # Not :latest

2. Non-Root User

securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

3. Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gitea-netpol
namespace: gitea
spec:
podSelector:
matchLabels:
app: gitea
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
egress:
- to:
- namespaceSelector:
matchLabels:
name: gitea
ports:
- protocol: TCP
port: 5432

4. Resource Limits

Always set resource limits untuk prevent resource exhaustion.

5. Regular Updates

# Update to new version
kubectl set image statefulset/gitea gitea=gitea/gitea:1.21.6 -n gitea

# Monitor rollout
kubectl rollout status statefulset/gitea -n gitea

Monitoring

Add Prometheus Annotations

metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"

ServiceMonitor for Prometheus Operator

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: gitea
namespace: gitea
spec:
selector:
matchLabels:
app: gitea
endpoints:
- port: http
path: /metrics
interval: 30s

Next Steps

Setelah Gitea berhasil di-deploy:

  1. Gitea Usage Guide - Learn basic operations
  2. Setup Gitea Runner - Configure CI/CD runner
  3. CI/CD Implementation - Create pipelines
  4. Best Practices - Production guidelines

Congratulations! Gitea Anda sudah running di Kubernetes! 🎉