# Garage — S3-compatible object storage, single-node # Metadata (LMDB, mmap-heavy → NFS-hostile) on local-path PVC # Data blobs on NAS via nas-nfs (subdir "garage" on /volume1/samantha-private) # Anti-affinity excludes game-worker-hdd (slow spinning disk) # # Ports (NodePort only for S3 API): # 3900 — S3 API → NodePort 32390 # 3901 — inter-node RPC (not exposed; single-node) # 3902 — S3 web hosting (not exposed for now) # 3903 — admin API (bound to 127.0.0.1 inside pod; kubectl exec to use) # # Deploy: # kubectl create secret generic garage-secret \ # --from-literal=rpc-secret="$(openssl rand -hex 32)" \ # --from-literal=admin-token="$(openssl rand -hex 32)" \ # --from-literal=metrics-token="$(openssl rand -hex 32)" # kubectl apply -f garage.yaml # # First-time layout init (run once after pod is Ready): # kubectl exec -n default deploy/garage -- garage status # # copy the node ID from the output, then: # kubectl exec -n default deploy/garage -- \ # garage layout assign -z dc1 -c 500G # kubectl exec -n default deploy/garage -- \ # garage layout apply --version 1 --- apiVersion: v1 kind: ConfigMap metadata: name: garage-config data: garage.toml: | metadata_dir = "/meta" data_dir = "/data" db_engine = "lmdb" replication_factor = 1 consistency_mode = "consistent" rpc_bind_addr = "[::]:3901" rpc_public_addr = "127.0.0.1:3901" [s3_api] api_bind_addr = "[::]:3900" s3_region = "garage" root_domain = ".s3.garage.homelab" [s3_web] bind_addr = "[::]:3902" root_domain = ".page.sjasoft.com" index = "index.html" [admin] api_bind_addr = "[::]:3903" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: garage-meta-pvc spec: accessModes: [ReadWriteOnce] storageClassName: local-path resources: requests: storage: 10Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: garage-data-pvc annotations: nfs.io/storage-path: "garage" spec: accessModes: [ReadWriteMany] storageClassName: nas-nfs resources: requests: storage: 500Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: garage spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: garage template: metadata: labels: app: garage spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: NotIn values: - game-worker-hdd containers: - name: garage image: dxflrs/garage:v2.3.0 command: ["/garage"] args: ["server"] env: - name: GARAGE_RPC_SECRET valueFrom: secretKeyRef: name: garage-secret key: rpc-secret - name: GARAGE_ADMIN_TOKEN valueFrom: secretKeyRef: name: garage-secret key: admin-token - name: GARAGE_METRICS_TOKEN valueFrom: secretKeyRef: name: garage-secret key: metrics-token - name: GARAGE_CONFIG_FILE value: /etc/garage/garage.toml ports: - containerPort: 3900 name: s3 - containerPort: 3901 name: rpc - containerPort: 3902 name: web - containerPort: 3903 name: admin volumeMounts: - name: config mountPath: /etc/garage - name: meta mountPath: /meta - name: data mountPath: /data volumes: - name: config configMap: name: garage-config - name: meta persistentVolumeClaim: claimName: garage-meta-pvc - name: data persistentVolumeClaim: claimName: garage-data-pvc --- apiVersion: v1 kind: Service metadata: name: garage spec: selector: app: garage ports: - name: s3 port: 3900 targetPort: 3900 nodePort: 32390 - name: web port: 3902 targetPort: 3902 type: NodePort --- # Admin API — ClusterIP only (not publicly reachable). # Use via: kubectl port-forward -n default svc/garage-admin 3903:3903 # Then hit http://localhost:3903 with bearer $(pass show homelab/GARAGE_ADMIN_TOKEN) apiVersion: v1 kind: Service metadata: name: garage-admin spec: selector: app: garage ports: - name: admin port: 3903 targetPort: 3903 type: ClusterIP