# FlowerCore.Library.Web — library circulation / catalog / OPAC service. # # Deployment + Service + PVC + Certificate + IngressRoute. ArgoCD-managed. # Cloned from apps/worldbuilder/worldbuilder.yaml (proven from-scratch .Web pattern). # # Image build (BLUEJAY-WS): # dotnet.exe publish src/FlowerCore.Library.Web -c Release -r linux-x64 --self-contained -o deploy/app -p:NoIncremental=true # podman build -t localhost/fc-library:v -f deploy/Dockerfile.deploy deploy # podman save localhost/fc-library:v -o /tmp/fc-library-v.tar # for ip in 10.0.56.11 10.0.56.12; do # 2-node cluster (agent2 retired 2026-06-01) # scp -o IdentitiesOnly=yes -i ~/.ssh/fcadmin_ed25519 /tmp/fc-library-v.tar fcadmin@$ip:/tmp/ # ssh -o IdentitiesOnly=yes -i ~/.ssh/fcadmin_ed25519 fcadmin@$ip "sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import /tmp/fc-library-v.tar" # done # # DNS preflight: library.iamworkin.lan -> 10.0.56.200 already resolves in pfSense Unbound. --- apiVersion: v1 kind: Namespace metadata: name: fc-library labels: app.kubernetes.io/name: fc-library app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: library-data namespace: fc-library labels: app.kubernetes.io/name: library-data app.kubernetes.io/component: storage app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 5Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: library-web namespace: fc-library labels: app.kubernetes.io/name: library-web app.kubernetes.io/component: web app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra annotations: flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard spec: replicas: 1 revisionHistoryLimit: 3 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: library-web template: metadata: labels: app.kubernetes.io/name: library-web app.kubernetes.io/component: web app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/metrics/prometheus" flowercore.io/audit-trace-id: "library-web-runtime" spec: securityContext: fsGroup: 1654 fsGroupChangePolicy: OnRootMismatch containers: - name: web image: localhost/fc-library:v202606031925 imagePullPolicy: Never ports: - containerPort: 8080 name: http env: - name: ASPNETCORE_URLS value: "http://+:8080" - name: ASPNETCORE_ENVIRONMENT value: "Production" - name: DOTNET_RUNNING_IN_CONTAINER value: "true" - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT value: "false" - name: FlowerCore__Database__Provider value: "Sqlite" - name: FlowerCore__Database__ConnectionStrings__Sqlite value: "Data Source=/data/library.db" resources: requests: cpu: 25m memory: 256Mi limits: cpu: 1000m memory: 768Mi startupProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 30 readinessProbe: httpGet: path: /healthz port: 8080 periodSeconds: 10 failureThreshold: 3 livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 30 periodSeconds: 30 failureThreshold: 3 securityContext: runAsNonRoot: true runAsUser: 1654 runAsGroup: 1654 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: data mountPath: /data - name: tmp mountPath: /tmp - name: logs mountPath: /app/logs volumes: - name: data persistentVolumeClaim: claimName: library-data - name: tmp emptyDir: {} - name: logs emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: library-web namespace: fc-library labels: app.kubernetes.io/name: library-web app.kubernetes.io/component: web app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra spec: type: ClusterIP selector: app.kubernetes.io/name: library-web ports: - name: http port: 80 targetPort: 8080 --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: library-web-tls namespace: fc-library labels: app.kubernetes.io/name: library-web-tls app.kubernetes.io/component: ingress app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra spec: secretName: library-web-tls issuerRef: name: step-ca-acme kind: ClusterIssuer dnsNames: - library.iamworkin.lan duration: 720h # 30d (step-ca cap) renewBefore: 240h # 10d --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: library-web namespace: fc-library labels: app.kubernetes.io/name: library-web app.kubernetes.io/component: ingress app.kubernetes.io/part-of: flowercore app.kubernetes.io/managed-by: argocd flowercore.io/tenant-id: system flowercore.io/created-by: bluejay-infra spec: entryPoints: - websecure routes: - match: Host(`library.iamworkin.lan`) kind: Rule services: - name: library-web port: 80 tls: secretName: library-web-tls