GitHub Issue #66 - Updated Description with NFS Storage
Executive Summary
This issue documents the comprehensive implementation plan for deploying Gitea as a GitHub repository mirror on the ocp-home OpenShift cluster. The primary objective is disaster recovery and backup of all morey-tech organization repositories (public and private) with automated hourly synchronization.
📝 Update 2026-01-25: Storage architecture updated to use NFS-backed storage for repositories, following the proven pattern from immich and paperless deployments. See detailed plan.
Key Objectives
- Mirror ALL morey-tech GitHub repositories (public and private) to local Gitea instance
- Automated hourly sync via Kubernetes CronJob
- Disaster recovery and backup capability
- Leverage existing ocp-home infrastructure (CloudNativePG, Velero, External Secrets, NFS)
Resource Requirements
- Storage: 120Gi total (100Gi repositories on NFS + 20Gi database on LVMS)
- Compute: 250m CPU, 640Mi memory
Infrastructure Analysis
Current ocp-home Cluster Capabilities
GitOps Architecture:
- ArgoCD ApplicationSets with app-of-apps pattern
- Automatic discovery from
kubernetes/ocp-home/applications/ and kubernetes/ocp-home/system/
- Kustomize + Helm chart deployment pattern (see immich, paperless)
- Automated sync, prune, and self-heal enabled
Available Operators:
- CloudNativePG (v1.28.0): PostgreSQL database management
- External Secrets: Bitwarden integration for secret management
- Velero: Backup and restore with NFS backend
- Cert-Manager: Let's Encrypt certificates with Cloudflare DNS-01
- External-DNS: Cloudflare DNS automation
- LVM Storage: NVMe-backed persistent volumes
Storage:
- NFS:
192.168.6.20:/storage-mass/ocp-home/ for large data volumes (immich, paperless)
- LVMS:
lvms-lvm-nvme1n1-vg (NVMe-backed, thin provisioning, XFS) for performance-critical workloads
- CSI snapshot support via VolumeSnapshotClass
Networking:
- OpenShift Routes with wildcard TLS:
*.apps.ocp-home.rh-lab.morey.tech
- MetalLB with BGP peering for load balancing
- External-DNS for automatic Cloudflare record creation
Deployment Architecture
Storage Strategy
Hybrid NFS + LVMS Approach:
| Component |
Storage Type |
Size |
Access Mode |
Rationale |
| Repository Data |
NFS |
100Gi |
ReadWriteMany |
Consistent with immich/paperless; supports scaling; network-accessible DR |
| PostgreSQL Database |
LVMS |
20Gi |
ReadWriteOnce |
Low-latency I/O for transactional workloads |
NFS Configuration:
- Server:
192.168.6.20
- Path:
/storage-mass/ocp-home/gitea
- Protocol: NFSv4 with mount options:
hard, intr
- Pattern: Follows immich and paperless
Components
1. Gitea Application
- Source: Official Gitea Helm chart v12.x from https://dl.gitea.com/charts/
- Deployment Pattern: Kustomize wrapper around Helm chart (follow immich pattern)
- Configuration:
- Rootless image for OpenShift SCC compatibility
- HTTPS-only access (SSH disabled for simplicity)
- LFS support enabled
- In-memory cache (Valkey disabled for single instance)
- External PostgreSQL connection
- Resource Limits: 100m-1000m CPU, 256Mi-1Gi memory
2. PostgreSQL Database (CloudNativePG)
- Pattern: Follow immich CloudNativePG cluster configuration
- Specification:
- Single instance (sufficient for homelab)
- Standard PostgreSQL 16 (no special extensions needed)
- 20Gi storage on LVMS NVMe
- Service endpoint:
gitea-postgresql-rw:5432
- Managed role:
gitea user with superuser privileges
- Reference:
kubernetes/ocp-home/applications/immich/postgresql-cluster.yaml
3. Repository Storage (Updated)
PersistentVolume (NFS-backed):
apiVersion: v1
kind: PersistentVolume
metadata:
name: gitea-repositories
labels:
type: nfs
app: gitea
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- nfsvers=4
- hard
- intr
nfs:
server: 192.168.6.20
path: /storage-mass/ocp-home/gitea
PersistentVolumeClaim:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-repositories
labels:
app: gitea
backup.velero.io/schedule: daily
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
volumeName: gitea-repositories
storageClassName: ""
References: immich/pv-data.yaml, paperless/pv.yaml
4. Automated Mirror Sync
- Implementation: Kubernetes Deployment using
ghcr.io/raylabshq/gitea-mirror:latest
- Schedule: Internal scheduler with 8-hour sync interval
- Functionality:
- Queries GitHub API for all morey-tech org repos
- Automatically creates mirrors in Gitea for new repos
- Syncs existing mirrors (pull updates)
- Supports both public and private repositories
- Metadata mirroring: Issues, PRs, labels, milestones, wiki
- Git LFS support: Large file mirroring
- Auto-cleanup: Archives deleted repos
- Environment Variables:
GITHUB_TOKEN: Organization token from ExternalSecret
GITHUB_USER: morey-tech
GITEA_URL: https://git.apps.ocp-home.rh-lab.morey.tech
GITEA_TOKEN: Admin user API token
AUTO_IMPORT_REPOS: true
CLEANUP_DELETE_IF_NOT_IN_GITHUB: archive
- Reference: https://github.com/RayLabsHQ/gitea-mirror
5. Ingress/TLS
- Route:
git.apps.ocp-home.rh-lab.morey.tech
- Pattern: OpenShift Ingress with
ingressClassName: openshift-default
- TLS: Edge termination with wildcard certificate (automatic)
- DNS: External-DNS creates Cloudflare A record automatically
- Reference:
kubernetes/ocp-home/applications/home-assistant/ingresses.yaml
6. Secret Management
Three ExternalSecrets pulling from Bitwarden ClusterSecretStores:
a) GitHub Organization Token:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: github-org-token
spec:
target:
name: github-org-token
data:
- secretKey: token
sourceRef:
storeRef:
name: bitwarden-fields
kind: ClusterSecretStore
remoteRef:
key: <bitwarden-uuid>
property: github-token
b) Gitea Admin Credentials:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gitea-admin-credentials
spec:
target:
name: gitea-admin-credentials
data:
- secretKey: username
sourceRef:
storeRef:
name: bitwarden-login
kind: ClusterSecretStore
remoteRef:
key: <bitwarden-uuid>
property: username
- secretKey: password
sourceRef:
storeRef:
name: bitwarden-login
kind: ClusterSecretStore
remoteRef:
key: <bitwarden-uuid>
property: password
c) PostgreSQL Credentials:
- Follow pattern from
kubernetes/ocp-home/applications/immich/postgresql-credentials-secret.yaml
7. Backup Strategy (Updated)
Dual-Layer Backup Approach:
Layer 1 - Velero Kubernetes Backup:
- Provider: Velero with daily schedule
- Namespace Label:
backup.velero.io/schedule: daily
- PVC Label:
backup.velero.io/schedule: daily
- Retention: 7 days (168h) per existing daily schedule
- Backup Targets:
- All namespace resources (Gitea deployment, services, ingress)
- Repository PVC metadata (NFS mount point)
- PostgreSQL data with CSI snapshots
- Reference:
kubernetes/ocp-home/system/velero/schedule-daily.yaml
Layer 2 - NFS-Level Backup:
- Advantages:
- File-level restore capability
- Independent from Kubernetes cluster
- Efficient deduplication/compression
- Survives cluster failures
- Recommended Methods:
- ZFS snapshots (if NFS backend supports)
- rsync-based incremental backups
- Velero Restic for file-level backup
- Retention: 30 days (configurable on NFS server)
Directory Structure
kubernetes/ocp-home/applications/gitea/
├── kustomization.yaml # Main Kustomize config with Helm chart
├── namespace.yaml # Namespace with backup labels
├── pv-repositories.yaml # NFS PersistentVolume
├── pvc-repositories.yaml # PVC with static binding
├── postgresql-cluster.yaml # CloudNativePG cluster (LVMS)
├── postgresql-credentials-secret.yaml # ExternalSecret for DB credentials
├── gitea-admin-secret.yaml # ExternalSecret for admin credentials
├── github-org-token-secret.yaml # ExternalSecret for GitHub token
├── ingress.yaml # OpenShift route configuration
├── deployment-scc-fix.yaml # Security context patches for OpenShift
├── gitea-mirror-deployment.yaml # RayLabsHQ gitea-mirror Deployment
├── gitea-mirror-secret.yaml # ExternalSecret for mirror auth
└── README.md # Documentation and maintenance notes
Implementation Details
Helm Chart Configuration
Key values in kustomization.yaml valuesInline section:
valuesInline:
# Use rootless image (OpenShift compatible)
image:
rootless: true
pullPolicy: IfNotPresent
# Disable built-in PostgreSQL (use CloudNativePG)
postgresql-ha:
enabled: false
postgresql:
enabled: false
# Disable Valkey for single-instance
valkey-cluster:
enabled: false
# External PostgreSQL configuration
gitea:
config:
database:
DB_TYPE: postgres
HOST: gitea-postgresql-rw:5432
NAME: gitea
USER: gitea
PASSWD: # From secret
SCHEMA: public
SSL_MODE: disable
session:
PROVIDER: memory # Use memory for single instance
cache:
ADAPTER: memory
repository:
ROOT: /data/git/repositories
server:
DOMAIN: git.apps.ocp-home.rh-lab.morey.tech
ROOT_URL: https://git.apps.ocp-home.rh-lab.morey.tech
DISABLE_SSH: true # Use HTTPS only (simpler for homelab)
LFS_START_SERVER: true # Enable LFS support
# Persistence configuration (NFS-backed)
persistence:
enabled: true
existingClaim: gitea-repositories
size: 100Gi
storageClass: "" # Static binding
accessModes:
- ReadWriteMany # NFS supports RWX
# Ingress disabled (use separate OpenShift Ingress resource)
ingress:
enabled: false
# Service configuration
service:
http:
type: ClusterIP
port: 3000
# Resource limits
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
OpenShift SCC Compatibility
Security Context Patch (deployment-scc-fix.yaml):
- op: remove
path: /spec/template/spec/securityContext/fsGroup
Rationale:
- OpenShift restricted SCC assigns random UID (100000+)
- Helm chart defaults include fsGroup: 1000 which conflicts
- Rootless Gitea image handles permissions correctly without fsGroup
- Pattern follows
kubernetes/ocp-home/applications/paperless/deployment-patch.yaml
Mirror Automation Deployment
Deployment Specification (gitea-mirror-deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-mirror
spec:
replicas: 1
selector:
matchLabels:
app: gitea-mirror
template:
metadata:
labels:
app: gitea-mirror
spec:
containers:
- name: gitea-mirror
image: ghcr.io/raylabshq/gitea-mirror:latest
env:
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: github-org-token
key: token
- name: GITHUB_USER
value: "morey-tech"
- name: GITEA_URL
value: "https://git.apps.ocp-home.rh-lab.morey.tech"
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: gitea-admin-credentials
key: api-token
- name: AUTO_IMPORT_REPOS
value: "true"
- name: CLEANUP_DELETE_IF_NOT_IN_GITHUB
value: "archive"
- name: GITEA_MIRROR_INTERVAL
value: "8h"
- name: SCHEDULE_ENABLED
value: "true"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Pre-Deployment Steps
1. NFS Server Setup
Create NFS Export:
# On NFS server (192.168.6.20)
sudo mkdir -p /storage-mass/ocp-home/gitea
sudo chown -R nobody:nogroup /storage-mass/ocp-home/gitea
sudo chmod 777 /storage-mass/ocp-home/gitea
# Add to /etc/exports
echo "/storage-mass/ocp-home/gitea *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
# Reload NFS exports
sudo exportfs -ra
# Verify export
showmount -e 192.168.6.20
Verify NFS Mount:
# From any ocp-home cluster node
sudo mount -t nfs4 -o hard,intr 192.168.6.20:/storage-mass/ocp-home/gitea /mnt
ls -la /mnt
sudo umount /mnt
2. Bitwarden Setup
Create Gitea Admin Credentials Entry:
- Username: admin
- Password: [Generate strong password]
- Note the Bitwarden UUID for ExternalSecret reference
Create GitHub Fine-Grained Personal Access Token:
- Navigate to GitHub Settings → Developer settings → Fine-grained tokens
- Repository access: All repositories
- Organization permissions:
- Metadata: Read-only
- Contents: Read-only
- Expiration: 1 year (set calendar reminder)
- Store token in Bitwarden
- Note the Bitwarden UUID for ExternalSecret reference
3. Storage Verification
Check NFS Available Capacity:
# On NFS server
df -h /storage-mass
# Verify at least 100Gi available
Check LVMS Available Capacity:
oc get lvmcluster -n openshift-storage -o yaml
# Verify /dev/nvme1n1 has at least 20Gi available
Query GitHub API for Repo Sizes:
gh api /orgs/morey-tech/repos --paginate | jq '[.[].size] | add / 1024'
# Returns total size in MB
4. DNS Verification
Verify External-DNS is Running:
oc get pods -n external-dns-system
Verify Cloudflare API Access:
oc logs -n external-dns-system deployment/external-dns | grep -i cloudflare
Critical Files to Reference
Implementation should follow these existing patterns:
-
Helm + Kustomize Pattern:
-
NFS PersistentVolume:
-
NFS PersistentVolumeClaim:
-
CloudNativePG PostgreSQL:
-
ExternalSecret Pattern:
-
OpenShift SCC Fixes:
-
OpenShift Route/Ingress:
-
Velero Backup Schedule:
Post-Deployment Validation
Troubleshooting Guide
NFS-Specific Issues
1. PVC Stuck in Pending
- Symptom:
oc get pvc gitea-repositories shows Pending
- Solution:
# Check PV exists and is Available
oc get pv gitea-repositories
# Verify PV and PVC names match exactly
oc describe pvc -n gitea gitea-repositories | grep -A 3 "Volume:"
# Check PV isn't already bound
oc get pv gitea-repositories -o yaml | grep claimRef
2. NFS Mount Fails in Pod
- Symptom: Pod fails with
mount.nfs: access denied or timeout
- Solution:
# Verify NFS export from cluster node
ssh <ocp-node> "showmount -e 192.168.6.20"
# Test manual mount
ssh <ocp-node> "sudo mount -t nfs4 192.168.6.20:/storage-mass/ocp-home/gitea /mnt"
# Check network connectivity
oc debug node/<node-name>
chroot /host
ping 192.168.6.20
telnet 192.168.6.20 2049
3. Permission Denied Errors
- Symptom: Gitea pod logs show "permission denied" when writing to
/data/git/repositories
- Solution:
# On NFS server - verify directory permissions
ls -la /storage-mass/ocp-home/gitea/
# Check pod security context UID
oc get pod -n gitea <pod> -o yaml | grep -A 3 securityContext
# Fix NFS permissions (if needed)
sudo chown -R 1000:1000 /storage-mass/ocp-home/gitea
4. Slow Performance
- Symptom: Git operations are slow, Gitea UI sluggish
- Solution:
# Check NFS server load
# On NFS server
nfsstat -s
# Verify network latency
oc debug node/<node-name>
chroot /host
ping -c 10 192.168.6.20
# Test I/O performance
oc exec -n gitea deployment/gitea -- dd if=/dev/zero of=/data/git/test bs=1M count=100
Common Issues
1. SCC Permission Errors
- Symptom: Pod fails to start with "unable to validate against any security context constraint"
- Solution: Verify fsGroup removed from Deployment securityContext
- Check:
oc get pod <pod> -o yaml | grep -A 5 securityContext
2. Database Connection Failed
- Symptom: Gitea logs show "database connection failed"
- Solution:
- Verify CloudNativePG cluster is ready:
oc get cluster gitea-postgresql
- Check credentials in ExternalSecret match PostgreSQL cluster
- Confirm service name is
gitea-postgresql-rw:5432
3. Mirror Sync Failures
- Symptom: Mirror deployment fails or repos not appearing in Gitea
- Solution:
- Verify GitHub token has not expired
- Check token has correct scopes (repo read access)
- Review deployment logs:
oc logs -n gitea deployment/gitea-mirror
- Confirm GitHub API rate limits not exceeded
4. Storage Full
- Symptom: "no space left on device" errors
- Solution:
# Check NFS server capacity
# On NFS server
df -h /storage-mass
# Expand PVC size if needed
oc edit pvc gitea-repositories -n gitea
# Clean up old/archived mirrors if needed
Security Considerations
NFS Security
Network Security:
- ✅ Internal Network: NFS server on isolated network (192.168.6.20)
- ⚠️ NFSv4 Kerberos: Consider enabling for authentication (advanced)
- ✅ Export Restrictions: Limit NFS exports to OpenShift cluster IPs
File Permissions:
- ⚠️ no_root_squash: Required for OpenShift, but increases security risk
- ✅ Alternative: Use
all_squash with specific anonuid/anongid
- ✅ Directory Permissions: Set minimal required permissions
Data at Rest:
- ⚠️ NFS Encryption: Not enabled by default
- ✅ Storage-Level Encryption: If NFS backend supports it (e.g., LUKS, ZFS encryption)
Application Security
- GitHub Token: Read-only organization access (minimal privilege principle)
- Storage: All tokens in Bitwarden, pulled via ExternalSecrets
- Rotation: GitHub fine-grained tokens support max 1-year expiration
- Audit: No secrets in Git repository (verified via .gitignore)
Network Security
- HTTPS Only: SSH service disabled (reduced attack surface)
- TLS Termination: At OpenShift router with Let's Encrypt certificate
- Internal Communication: PostgreSQL connection not exposed externally
- Ingress: Single controlled entry point via OpenShift Route
Access Control
- Gitea Admin: Via Bitwarden-stored credentials only
- User Registration: Consider disabling (backup-only use case)
- Mirror Repositories: Read-only (prevents accidental local commits)
- API Access: Gitea admin token for mirror automation only
Resource Estimates
Storage Resources
| Component |
Storage Type |
Size |
Access Mode |
IOPS Profile |
| Gitea Repositories |
NFS |
100Gi |
RWX |
Low (batch sync) |
| Git LFS Objects |
NFS |
(included) |
RWX |
Low (occasional) |
| PostgreSQL Data |
LVMS (NVMe) |
20Gi |
RWO |
High (transactional) |
| Total NFS |
- |
100Gi |
- |
- |
| Total LVMS |
- |
20Gi |
- |
- |
Compute Resources
| Component |
CPU Request |
CPU Limit |
Memory Request |
Memory Limit |
| Gitea |
100m |
1000m |
256Mi |
1Gi |
| PostgreSQL |
100m |
500m |
256Mi |
512Mi |
| Mirror (RayLabsHQ) |
50m |
500m |
128Mi |
512Mi |
| Total |
250m |
2000m |
640Mi |
2Gi |
Network Resources
- Bandwidth: Initial sync will download all repos (estimate based on GitHub API query)
- Ongoing: Incremental updates only (minimal bandwidth)
- Ingress: Standard HTTPS traffic for Gitea UI access
Monitoring and Maintenance
Regular Tasks
Weekly:
- Monitor NFS repository storage growth:
df -h /storage-mass/ocp-home/gitea
- Monitor database storage usage:
oc exec -n gitea gitea-postgresql-1 -- psql -U gitea -c "SELECT pg_size_pretty(pg_database_size('gitea'));"
- Review mirror sync success rate:
oc logs -n gitea deployment/gitea-mirror --tail=100
Monthly:
- Check for Gitea Helm chart updates:
helm repo update gitea-charts && helm search repo gitea
- Update Gitea version if stable release available
- Review mirror sync logs for patterns
- Verify NFS backup completion
Quarterly:
- Test Velero backup restoration to temporary namespace
- Test NFS-level backup restoration
- Verify all critical repos are mirrored
- Review and clean up archived/deleted repo mirrors
- Check PostgreSQL performance (may need tuning)
Annually:
- Rotate GitHub fine-grained token (1-year expiration)
- Review storage capacity planning
- Update documentation
Metrics to Track
| Metric |
Command |
Target |
| Mirrored Repos Count |
oc exec -n gitea <pod> -- gitea admin repo list | wc -l |
Match morey-tech repo count |
| Repository Storage Used |
oc exec -n gitea <pod> -- df -h /data/git/repositories |
< 80% of 100Gi |
| NFS Server Storage |
df -h /storage-mass/ocp-home/gitea |
< 80% of 100Gi |
| Database Size |
oc exec -n gitea gitea-postgresql-1 -- psql -U gitea -c "SELECT pg_size_pretty(pg_database_size('gitea'));" |
< 15Gi of 20Gi |
| Mirror Sync Success |
oc logs -n gitea deployment/gitea-mirror --tail=50 |
No errors |
| Backup Completion |
velero backup get --selector backup.velero.io/schedule=daily | grep gitea |
Daily completion |
Alternative Approaches Considered
1. Pure LVMS Approach (Original Plan)
Approach: Use LVMS for both repository and database storage
Pros: Higher performance, simpler configuration, lower network dependency
Cons:
- Inconsistent with existing infrastructure (immich, paperless use NFS)
- Limited capacity (constrained by NVMe size)
- ReadWriteOnce limitation (no multi-pod scaling)
- Single point of failure (host-dependent)
Decision: ❌ Rejected - Inconsistent pattern, limited scalability
2. All-NFS Approach
Approach: Use NFS for both repository and database storage
Pros: Consistent storage pattern, maximum capacity, simple backup
Cons:
- Poor database performance (network latency)
- Not recommended by CloudNativePG operator
- IOPS bottleneck for transactional workloads
Decision: ❌ Rejected - Database performance critical
3. Hybrid NFS + LVMS (Selected)
Approach: NFS for repositories (100Gi RWX), LVMS for database (20Gi RWO)
Pros:
- ✅ Leverages strengths of both storage types
- ✅ Consistent with existing infrastructure
- ✅ Optimal performance for each workload
- ✅ Maximum capacity for repository data
- ✅ Supports future scaling (RWX)
Cons:
- More complex architecture (two storage backends)
- Requires NFS server setup
Decision: ✅ Selected - Best balance of performance, capacity, and consistency
4. Manual Mirror Setup
Approach: Use Gitea UI to manually create mirrors for each repo
Pros: Simple, no additional automation complexity
Cons:
- Doesn't scale for all morey-tech repos
- Manual effort to keep in sync
- Risk of missing new repos
Decision: ❌ Rejected - automation required for all repos
5. jaedle/mirror-to-gitea
Approach: Use jaedle/mirror-to-gitea container for automation
Pros: Simpler configuration
Cons:
- Only mirrors Git repositories (no issues, PRs, labels)
- No auto-discovery or cleanup features
- Less configuration flexibility
Decision: ❌ Rejected - RayLabsHQ/gitea-mirror provides metadata mirroring
Success Criteria
Must Have (MVP)
Should Have
Nice to Have
References
Official Documentation
Third-Party Tools
OpenShift Resources
Community Resources
Next Steps
- Review and Approve: Review this updated plan for completeness and accuracy
- Assign: Assign to appropriate team member or self-assign
- Milestone: Add to appropriate milestone/sprint
- Branch: Create feature branch
feat/gitea-mirror-nfs-storage
- Phase 1: Configure NFS server export
- Phase 2: Create Kubernetes resources (PV, PVC, namespace)
- Phase 3: Deploy Gitea application
- Phase 4: Deploy mirror automation
- Phase 5: Validation and testing
- Documentation: Update homelab documentation
- Closure: Close issue with summary of deployment results
Related Documentation
This issue was generated as part of the homelab infrastructure improvement initiative. Updated 2026-01-25 to use NFS-backed storage for repositories, following proven patterns from immich and paperless deployments.
GitHub Issue #66 - Updated Description with NFS Storage
Executive Summary
This issue documents the comprehensive implementation plan for deploying Gitea as a GitHub repository mirror on the ocp-home OpenShift cluster. The primary objective is disaster recovery and backup of all morey-tech organization repositories (public and private) with automated hourly synchronization.
Key Objectives
Resource Requirements
Infrastructure Analysis
Current ocp-home Cluster Capabilities
GitOps Architecture:
kubernetes/ocp-home/applications/andkubernetes/ocp-home/system/Available Operators:
Storage:
192.168.6.20:/storage-mass/ocp-home/for large data volumes (immich, paperless)lvms-lvm-nvme1n1-vg(NVMe-backed, thin provisioning, XFS) for performance-critical workloadsNetworking:
*.apps.ocp-home.rh-lab.morey.techDeployment Architecture
Storage Strategy
Hybrid NFS + LVMS Approach:
NFS Configuration:
192.168.6.20/storage-mass/ocp-home/giteahard,intrComponents
1. Gitea Application
2. PostgreSQL Database (CloudNativePG)
gitea-postgresql-rw:5432giteauser with superuser privilegeskubernetes/ocp-home/applications/immich/postgresql-cluster.yaml3. Repository Storage (Updated)
PersistentVolume (NFS-backed):
PersistentVolumeClaim:
References: immich/pv-data.yaml, paperless/pv.yaml
4. Automated Mirror Sync
ghcr.io/raylabshq/gitea-mirror:latestGITHUB_TOKEN: Organization token from ExternalSecretGITHUB_USER: morey-techGITEA_URL: https://git.apps.ocp-home.rh-lab.morey.techGITEA_TOKEN: Admin user API tokenAUTO_IMPORT_REPOS: trueCLEANUP_DELETE_IF_NOT_IN_GITHUB: archive5. Ingress/TLS
git.apps.ocp-home.rh-lab.morey.techingressClassName: openshift-defaultkubernetes/ocp-home/applications/home-assistant/ingresses.yaml6. Secret Management
Three ExternalSecrets pulling from Bitwarden ClusterSecretStores:
a) GitHub Organization Token:
b) Gitea Admin Credentials:
c) PostgreSQL Credentials:
kubernetes/ocp-home/applications/immich/postgresql-credentials-secret.yaml7. Backup Strategy (Updated)
Dual-Layer Backup Approach:
Layer 1 - Velero Kubernetes Backup:
backup.velero.io/schedule: dailybackup.velero.io/schedule: dailykubernetes/ocp-home/system/velero/schedule-daily.yamlLayer 2 - NFS-Level Backup:
Directory Structure
Implementation Details
Helm Chart Configuration
Key values in
kustomization.yamlvaluesInlinesection:OpenShift SCC Compatibility
Security Context Patch (
deployment-scc-fix.yaml):Rationale:
kubernetes/ocp-home/applications/paperless/deployment-patch.yamlMirror Automation Deployment
Deployment Specification (
gitea-mirror-deployment.yaml):Pre-Deployment Steps
1. NFS Server Setup
Create NFS Export:
Verify NFS Mount:
# From any ocp-home cluster node sudo mount -t nfs4 -o hard,intr 192.168.6.20:/storage-mass/ocp-home/gitea /mnt ls -la /mnt sudo umount /mnt2. Bitwarden Setup
Create Gitea Admin Credentials Entry:
Create GitHub Fine-Grained Personal Access Token:
3. Storage Verification
Check NFS Available Capacity:
Check LVMS Available Capacity:
oc get lvmcluster -n openshift-storage -o yaml # Verify /dev/nvme1n1 has at least 20Gi availableQuery GitHub API for Repo Sizes:
4. DNS Verification
Verify External-DNS is Running:
Verify Cloudflare API Access:
oc logs -n external-dns-system deployment/external-dns | grep -i cloudflareCritical Files to Reference
Implementation should follow these existing patterns:
Helm + Kustomize Pattern:
NFS PersistentVolume:
NFS PersistentVolumeClaim:
CloudNativePG PostgreSQL:
ExternalSecret Pattern:
OpenShift SCC Fixes:
OpenShift Route/Ingress:
Velero Backup Schedule:
Post-Deployment Validation
oc get pv gitea-repositoriesoc exec -n gitea <pod> -- df -hTroubleshooting Guide
NFS-Specific Issues
1. PVC Stuck in Pending
oc get pvc gitea-repositoriesshowsPending2. NFS Mount Fails in Pod
mount.nfs: access deniedor timeout3. Permission Denied Errors
/data/git/repositories4. Slow Performance
Common Issues
1. SCC Permission Errors
oc get pod <pod> -o yaml | grep -A 5 securityContext2. Database Connection Failed
oc get cluster gitea-postgresqlgitea-postgresql-rw:54323. Mirror Sync Failures
oc logs -n gitea deployment/gitea-mirror4. Storage Full
Security Considerations
NFS Security
Network Security:
File Permissions:
all_squashwith specificanonuid/anongidData at Rest:
Application Security
Network Security
Access Control
Resource Estimates
Storage Resources
Compute Resources
Network Resources
Monitoring and Maintenance
Regular Tasks
Weekly:
df -h /storage-mass/ocp-home/giteaoc exec -n gitea gitea-postgresql-1 -- psql -U gitea -c "SELECT pg_size_pretty(pg_database_size('gitea'));"oc logs -n gitea deployment/gitea-mirror --tail=100Monthly:
helm repo update gitea-charts && helm search repo giteaQuarterly:
Annually:
Metrics to Track
oc exec -n gitea <pod> -- gitea admin repo list | wc -loc exec -n gitea <pod> -- df -h /data/git/repositoriesdf -h /storage-mass/ocp-home/giteaoc exec -n gitea gitea-postgresql-1 -- psql -U gitea -c "SELECT pg_size_pretty(pg_database_size('gitea'));"oc logs -n gitea deployment/gitea-mirror --tail=50velero backup get --selector backup.velero.io/schedule=daily | grep giteaAlternative Approaches Considered
1. Pure LVMS Approach (Original Plan)
Approach: Use LVMS for both repository and database storage
Pros: Higher performance, simpler configuration, lower network dependency
Cons:
Decision: ❌ Rejected - Inconsistent pattern, limited scalability
2. All-NFS Approach
Approach: Use NFS for both repository and database storage
Pros: Consistent storage pattern, maximum capacity, simple backup
Cons:
Decision: ❌ Rejected - Database performance critical
3. Hybrid NFS + LVMS (Selected)
Approach: NFS for repositories (100Gi RWX), LVMS for database (20Gi RWO)
Pros:
Cons:
Decision: ✅ Selected - Best balance of performance, capacity, and consistency
4. Manual Mirror Setup
Approach: Use Gitea UI to manually create mirrors for each repo
Pros: Simple, no additional automation complexity
Cons:
Decision: ❌ Rejected - automation required for all repos
5. jaedle/mirror-to-gitea
Approach: Use jaedle/mirror-to-gitea container for automation
Pros: Simpler configuration
Cons:
Decision: ❌ Rejected - RayLabsHQ/gitea-mirror provides metadata mirroring
Success Criteria
Must Have (MVP)
Should Have
Nice to Have
References
Official Documentation
Third-Party Tools
OpenShift Resources
Community Resources
Next Steps
feat/gitea-mirror-nfs-storageRelated Documentation
This issue was generated as part of the homelab infrastructure improvement initiative. Updated 2026-01-25 to use NFS-backed storage for repositories, following proven patterns from immich and paperless deployments.