Update Helm chart to use unified app image (API + Frontend)

Changes:
- Replaced separate api and frontend deployments with single unified app deployment
- Updated values.yaml: Changed from api/frontend configs to single app config
- Renamed templates: api-deployment.yaml → app-deployment.yaml
- Removed frontend-deployment.yaml and frontend-service.yaml (no longer needed)
- Updated app image to warehouse13/app (multi-stage Docker build)
- Combined resource allocations: 384Mi memory, 350m CPU (up from separate totals)
- Updated all example values files (dev, production, air-gapped)
- Updated NOTES.txt to reflect single service on port 8000
- Updated ingress to route all traffic to single app service
- Added ARCHITECTURE.md documenting the unified container approach

Architecture:
The application now uses a multi-stage Docker build:
1. Stage 1: Builds Angular frontend with Node
2. Stage 2: Python FastAPI backend that serves static frontend from /static

Benefits:
- Simplified deployment (1 container instead of 2)
- Reduced resource usage (no separate nginx)
- Easier scaling (1 deployment to manage)
- Consistent versioning (frontend/backend always match)

Access pattern:
- http://localhost:8000     → Angular frontend
- http://localhost:8000/api → FastAPI REST API
- http://localhost:8000/docs → API documentation
- http://localhost:8000/health → Health check

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-16 17:11:39 -05:00
parent 59001222a0
commit 4641cbb3fa
11 changed files with 355 additions and 251 deletions

View File

@@ -0,0 +1,281 @@
# Warehouse13 Architecture
## Overview
Warehouse13 uses a **unified application container** that includes both the frontend and backend in a single Docker image using a multi-stage build.
## Docker Build Strategy
### Multi-Stage Dockerfile
```dockerfile
# Stage 1: Build Angular Frontend
FROM node:24-alpine AS frontend-build
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build:prod
# Stage 2: Python Backend with Static Frontend
FROM python:3.11-alpine
WORKDIR /app
# Install Python dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy backend code
COPY app/ ./app/
# Copy built frontend from stage 1
COPY --from=frontend-build /frontend/dist/frontend/browser ./static/
# Run FastAPI server
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```
### Benefits
1. **Simplified Deployment** - Single container to manage
2. **Reduced Resource Usage** - No separate nginx container needed
3. **Easier Scaling** - Scale one deployment instead of two
4. **Consistent Versioning** - Frontend and backend versions always match
5. **Faster Deployments** - Fewer containers to orchestrate
## Service Architecture
```
┌─────────────────────────────────────────┐
│ warehouse13-app │
│ ┌────────────────────────────────────┐ │
│ │ FastAPI Backend (Port 8000) │ │
│ │ ├── /api/* → REST API │ │
│ │ ├── /health → Health check │ │
│ │ ├── /docs → API documentation │ │
│ │ └── /* → Angular SPA │ │
│ │ │ │
│ │ Static Files: /static/ │ │
│ │ └── Angular build output │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
├────────────────┐
↓ ↓
┌──────────┐ ┌────────────┐
│PostgreSQL│ │ MinIO │
│(Metadata)│ │ (Blobs) │
└──────────┘ └────────────┘
```
## Helm Chart Structure
### Single Application Deployment
The Helm chart creates:
1. **1 Deployment**: `warehouse13-app`
- Runs the unified container
- Configurable replicas (default: 2)
- Health checks on `/health` endpoint
2. **1 Service**: `warehouse13-app`
- Exposes port 8000
- Routes all traffic to the application
3. **Optional Ingress**
- All paths route to `warehouse13-app:8000`
- FastAPI handles routing internally
### Kubernetes Resources
```yaml
# warehouse13-app Deployment
- Replicas: 2 (configurable)
- Port: 8000
- Health checks: /health
- Environment: DATABASE_URL, MINIO_* vars
# warehouse13-app Service
- Type: ClusterIP
- Port: 8000 → 8000
# Ingress (optional)
- Path: / → warehouse13-app:8000
```
## Configuration
### Image Configuration
In `values.yaml`:
```yaml
app:
enabled: true
image:
repository: warehouse13/app # Single unified image
tag: latest
pullPolicy: IfNotPresent
replicas: 2
resources:
requests:
memory: "384Mi" # Combined frontend + backend
cpu: "350m"
limits:
memory: "768Mi"
cpu: "750m"
```
### Accessing the Application
**Via Port Forward:**
```bash
kubectl port-forward svc/warehouse13-app 8000:8000
```
Then access:
- Frontend: http://localhost:8000
- API: http://localhost:8000/api
- API Docs: http://localhost:8000/docs
- Health: http://localhost:8000/health
**Via Ingress:**
```yaml
ingress:
enabled: true
hosts:
- host: warehouse13.example.com
paths:
- path: /
pathType: Prefix
backend: app # All traffic to one service
```
## Migration from Separate Services
If you previously had separate `api` and `frontend` deployments:
### Before (Old Architecture)
```yaml
# values.yaml (old)
api:
image: warehouse13/api
replicas: 2
frontend:
image: warehouse13/frontend
replicas: 2
# Two deployments, two services
```
### After (Current Architecture)
```yaml
# values.yaml (current)
app:
image: warehouse13/app # Unified image
replicas: 2
# One deployment, one service
```
### Migration Steps
1. **Update values.yaml** - Change from `api`/`frontend` to `app`
2. **Update image references** - Use `warehouse13/app` instead of separate images
3. **Update ingress** - Point all paths to `app` backend
4. **Deploy** - Helm will handle the transition
5. **Verify** - Check that both frontend and API work through single service
## Development Workflow
### Building the Image
```bash
# Build unified image
docker build -t warehouse13/app:dev .
# Or for air-gapped environments with custom registry
docker build \
--build-arg NPM_REGISTRY=https://registry.npmjs.org/ \
-t warehouse13/app:v1.0.0 .
```
### Testing Locally
```bash
docker run -p 8000:8000 \
-e DATABASE_URL=postgresql://user:pass@host/db \
-e MINIO_ENDPOINT=minio:9000 \
warehouse13/app:dev
```
Access:
- Frontend: http://localhost:8000
- API: http://localhost:8000/docs
## Performance Considerations
### Resource Allocation
The unified container combines both frontend serving and API processing:
- **Memory**: Angular assets (~50MB) + Python runtime (~100MB) + working memory
- **CPU**: Primarily used for API requests; static file serving is lightweight
- **Recommended Minimum**: 384Mi memory, 350m CPU
- **Production**: 768Mi memory, 750m CPU per replica
### Scaling Strategy
Scale horizontally by increasing replicas:
```bash
# Scale to 5 replicas
kubectl scale deployment warehouse13-app --replicas=5
# Or via Helm
helm upgrade warehouse13 ./helm/warehouse13 --set app.replicas=5
```
### Caching
FastAPI serves static files efficiently with:
- ETag support
- Browser caching headers
- Gzip compression (if enabled in FastAPI config)
## Troubleshooting
### Frontend Not Loading
```bash
# Check if static files exist in container
kubectl exec -it warehouse13-app-xxx -- ls -la /app/static/
# Should see: index.html, *.js, *.css files
```
### API Not Working
```bash
# Check API health
kubectl exec -it warehouse13-app-xxx -- curl http://localhost:8000/health
# Check logs
kubectl logs warehouse13-app-xxx -f
```
### Both Frontend and API Issues
```bash
# Check if app is running
kubectl get pods -l app.kubernetes.io/component=app
# Check service
kubectl get svc warehouse13-app
# Test connectivity
kubectl port-forward svc/warehouse13-app 8000:8000
curl http://localhost:8000/health
```
## Summary
The unified architecture simplifies deployment and operations while maintaining the same functionality. All routing, caching, and API requests are handled by a single FastAPI application that serves both the Angular SPA and the REST API endpoints.

View File

@@ -19,18 +19,13 @@ Namespace: {{ .Release.Namespace }}
DEPLOYMENT INFORMATION:
{{- if .Values.frontend.enabled }}
Frontend:
Service: warehouse13-frontend
Replicas: {{ .Values.frontend.replicas }}
Image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}
{{- end }}
{{- if .Values.api.enabled }}
API:
Service: warehouse13-api
Replicas: {{ .Values.api.replicas }}
Image: {{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}
{{- if .Values.app.enabled }}
Application (Unified API + Frontend):
Service: warehouse13-app
Replicas: {{ .Values.app.replicas }}
Image: {{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}
Port: {{ .Values.app.service.port }}
Note: Multi-stage build includes both Angular frontend and FastAPI backend
{{- end }}
{{- if .Values.postgres.enabled }}
@@ -62,13 +57,12 @@ ACCESSING YOUR APPLICATION:
1. Using Port Forwarding:
# Frontend
kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-frontend 4200:80
Then visit: http://localhost:4200
# API
kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-api 8000:8000
Then visit: http://localhost:8000/docs
# Application (Frontend + API)
kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-app 8000:8000
Then visit:
- Frontend: http://localhost:8000
- API Docs: http://localhost:8000/docs
- Health: http://localhost:8000/health
# MinIO Console
kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-minio 9001:9001
@@ -91,8 +85,7 @@ CHECKING STATUS:
kubectl get svc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}
# View logs
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=api -f
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=frontend -f
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=app -f
---

View File

@@ -1,32 +1,32 @@
{{- if .Values.api.enabled }}
{{- if .Values.app.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: warehouse13-api
name: warehouse13-app
labels:
{{- include "warehouse13.labels" . | nindent 4 }}
app.kubernetes.io/component: api
app.kubernetes.io/component: app
spec:
replicas: {{ .Values.api.replicas }}
replicas: {{ .Values.app.replicas }}
selector:
matchLabels:
{{- include "warehouse13.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: api
app.kubernetes.io/component: app
template:
metadata:
labels:
{{- include "warehouse13.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: api
app.kubernetes.io/component: app
spec:
serviceAccountName: {{ include "warehouse13.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: api
- name: app
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}"
imagePullPolicy: {{ .Values.app.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
@@ -57,26 +57,30 @@ spec:
secretKeyRef:
name: warehouse13-secrets
key: minio-root-password
- name: MINIO_BUCKET_NAME
value: "test-artifacts"
- name: MINIO_SECURE
value: "false"
- name: DEPLOYMENT_MODE
valueFrom:
configMapKeyRef:
name: warehouse13-config
key: DEPLOYMENT_MODE
resources:
{{- toYaml .Values.api.resources | nindent 10 }}
{{- if .Values.api.healthCheck.enabled }}
{{- toYaml .Values.app.resources | nindent 10 }}
{{- if .Values.app.healthCheck.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.api.healthCheck.liveness.path }}
path: {{ .Values.app.healthCheck.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.api.healthCheck.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.api.healthCheck.liveness.periodSeconds }}
initialDelaySeconds: {{ .Values.app.healthCheck.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.app.healthCheck.liveness.periodSeconds }}
readinessProbe:
httpGet:
path: {{ .Values.api.healthCheck.readiness.path }}
path: {{ .Values.app.healthCheck.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.api.healthCheck.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.api.healthCheck.readiness.periodSeconds }}
initialDelaySeconds: {{ .Values.app.healthCheck.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.app.healthCheck.readiness.periodSeconds }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:

View File

@@ -1,19 +1,19 @@
{{- if .Values.api.enabled }}
{{- if .Values.app.enabled }}
apiVersion: v1
kind: Service
metadata:
name: warehouse13-api
name: warehouse13-app
labels:
{{- include "warehouse13.labels" . | nindent 4 }}
app.kubernetes.io/component: api
app.kubernetes.io/component: app
spec:
type: {{ .Values.api.service.type }}
type: {{ .Values.app.service.type }}
ports:
- port: {{ .Values.api.service.port }}
- port: {{ .Values.app.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "warehouse13.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: api
app.kubernetes.io/component: app
{{- end }}

View File

@@ -1,73 +0,0 @@
{{- if .Values.frontend.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: warehouse13-frontend
labels:
{{- include "warehouse13.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
spec:
replicas: {{ .Values.frontend.replicas }}
selector:
matchLabels:
{{- include "warehouse13.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: frontend
template:
metadata:
labels:
{{- include "warehouse13.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: frontend
spec:
serviceAccountName: {{ include "warehouse13.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: frontend
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
env:
- name: DEPLOYMENT_MODE
valueFrom:
configMapKeyRef:
name: warehouse13-config
key: DEPLOYMENT_MODE
- name: STORAGE_BACKEND
valueFrom:
configMapKeyRef:
name: warehouse13-config
key: STORAGE_BACKEND
resources:
{{- toYaml .Values.frontend.resources | nindent 10 }}
{{- if .Values.frontend.healthCheck.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.frontend.healthCheck.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.frontend.healthCheck.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.frontend.healthCheck.liveness.periodSeconds }}
readinessProbe:
httpGet:
path: {{ .Values.frontend.healthCheck.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.frontend.healthCheck.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.frontend.healthCheck.readiness.periodSeconds }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@@ -1,19 +0,0 @@
{{- if .Values.frontend.enabled }}
apiVersion: v1
kind: Service
metadata:
name: warehouse13-frontend
labels:
{{- include "warehouse13.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
spec:
type: {{ .Values.frontend.service.type }}
ports:
- port: {{ .Values.frontend.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "warehouse13.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: frontend
{{- end }}

View File

@@ -35,7 +35,7 @@ spec:
service:
name: {{ printf "warehouse13-%s" .backend }}
port:
number: {{ if eq .backend "frontend" }}{{ $.Values.frontend.service.port }}{{ else }}{{ $.Values.api.service.port }}{{ end }}
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -50,37 +50,21 @@ minio:
memory: "2Gi"
cpu: "1000m"
# API with custom registry
api:
# Application with custom registry (unified API + Frontend)
app:
enabled: true
image:
repository: harbor.internal.example.com/warehouse13/api
repository: harbor.internal.example.com/warehouse13/app
tag: v1.0.0
pullPolicy: IfNotPresent
replicas: 2
resources:
requests:
memory: "512Mi"
cpu: "500m"
memory: "768Mi"
cpu: "750m"
limits:
memory: "1Gi"
cpu: "1000m"
# Frontend with custom registry
frontend:
enabled: true
image:
repository: harbor.internal.example.com/warehouse13/frontend
tag: v1.0.0
pullPolicy: IfNotPresent
replicas: 2
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
memory: "1536Mi"
cpu: "1500m"
# Ingress disabled for air-gapped - use NodePort or port-forward
ingress:

View File

@@ -44,37 +44,20 @@ minio:
memory: "512Mi"
cpu: "250m"
api:
app:
enabled: true
image:
repository: warehouse13/api
repository: warehouse13/app
tag: dev
pullPolicy: Always # Always pull latest dev image
replicas: 1
resources:
requests:
memory: "256Mi"
cpu: "250m"
memory: "384Mi"
cpu: "350m"
limits:
memory: "512Mi"
cpu: "500m"
healthCheck:
enabled: true
frontend:
enabled: true
image:
repository: warehouse13/frontend
tag: dev
pullPolicy: Always # Always pull latest dev image
replicas: 1
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
memory: "768Mi"
cpu: "750m"
healthCheck:
enabled: true

View File

@@ -48,37 +48,20 @@ minio:
memory: "4Gi"
cpu: "2000m"
api:
app:
enabled: true
image:
repository: warehouse13/api
repository: warehouse13/app
tag: v1.0.0
pullPolicy: IfNotPresent
replicas: 3
resources:
requests:
memory: "512Mi"
cpu: "500m"
memory: "768Mi"
cpu: "750m"
limits:
memory: "1Gi"
cpu: "1000m"
healthCheck:
enabled: true
frontend:
enabled: true
image:
repository: warehouse13/frontend
tag: v1.0.0
pullPolicy: IfNotPresent
replicas: 3
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
memory: "1536Mi"
cpu: "1500m"
healthCheck:
enabled: true
@@ -94,10 +77,7 @@ ingress:
paths:
- path: /
pathType: Prefix
backend: frontend
- path: /api
pathType: Prefix
backend: api
backend: app
tls:
- secretName: warehouse13-tls
hosts:

View File

@@ -58,11 +58,14 @@ minio:
apiPort: 9000
consolePort: 9001
# API Backend
api:
# Application (Unified API + Frontend)
# The application uses a multi-stage Docker build:
# - Stage 1: Builds Angular frontend
# - Stage 2: Python FastAPI backend that serves the frontend from /static
app:
enabled: true
image:
repository: warehouse13/api
repository: warehouse13/app
tag: latest
pullPolicy: IfNotPresent
replicas: 2
@@ -72,11 +75,11 @@ api:
minioEndpoint: "warehouse13-minio:9000"
resources:
requests:
memory: "256Mi"
cpu: "250m"
memory: "384Mi"
cpu: "350m"
limits:
memory: "512Mi"
cpu: "500m"
memory: "768Mi"
cpu: "750m"
service:
type: ClusterIP
port: 8000
@@ -87,39 +90,10 @@ api:
initialDelaySeconds: 30
periodSeconds: 10
readiness:
path: /ready
path: /health
initialDelaySeconds: 10
periodSeconds: 5
# Frontend
frontend:
enabled: true
image:
repository: warehouse13/frontend
tag: latest
pullPolicy: IfNotPresent
replicas: 2
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
service:
type: ClusterIP
port: 80
healthCheck:
enabled: true
liveness:
path: /
initialDelaySeconds: 10
periodSeconds: 10
readiness:
path: /
initialDelaySeconds: 5
periodSeconds: 5
# Ingress
ingress:
enabled: false
@@ -131,10 +105,7 @@ ingress:
paths:
- path: /
pathType: Prefix
backend: frontend
- path: /api
pathType: Prefix
backend: api
backend: app # All traffic goes to unified app (serves both API and frontend)
tls: []
# - secretName: warehouse13-tls
# hosts: